/*
 * Copyright 2010-2012 VMware and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springsource.loaded.agent;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.springsource.loaded.Constants;

/**
 * This bytecode rewriter intercepts calls to generate made in the CGLIB framework and allows us to record what generator is called
 * to create the proxy for some type. The same generator can then be driven again if the type is reloaded.
 * 
 * @author Andy Clement
 * @since 0.8.3
 */
public class CglibPluginCapturing extends ClassVisitor implements Constants {

	public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndClassGeneratorMap = new HashMap<Class<?>, Object[]>();
	public static Map<Class<?>, Object[]> clazzToGeneratorStrategyAndFastClassGeneratorMap = new HashMap<Class<?>, Object[]>();
	public String prefix;
	
	public static byte[] catchGenerate(byte[] bytesIn) {
		ClassReader cr = new ClassReader(bytesIn);
		CglibPluginCapturing ca = new CglibPluginCapturing();
		cr.accept(ca, 0);
		byte[] newbytes = ca.getBytes();
		return newbytes;
	}

	private CglibPluginCapturing() {
		super(ASM5,new ClassWriter(0)); // TODO review 0 here
	}
	
	@Override
	public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
		// The name could be a repackaged form of cglib:
		// net/sf/cglib/core/AbstractClassGenerator
		// org/springframework/cglib/core/AbstractClassGenerator
		int index = name.indexOf("/cglib");
		prefix = name.substring(0,index);
		super.visit(version,access,name,signature,superName,interfaces);
	}

	public byte[] getBytes() {
		return ((ClassWriter) cv).toByteArray();
	}

	public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
		if (name.equals("create")) {
			MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
			return new CreateMethodInterceptor(mv);
		} else {
			return super.visitMethod(access, name, desc, signature, exceptions);
		}
	}

	class CreateMethodInterceptor extends MethodVisitor implements Constants {

		public CreateMethodInterceptor(MethodVisitor mv) {
			super(ASM5,mv);
		}

		@Override
		public void visitCode() {
		}

		/**
		 * Recognize a call to 'generate' being made. When we see it add some extra code after it that calls the record method in
		 * this type so that we can remember the generator used (and drive it again later when the related type is reloaded).
		 */
		@Override
		public void visitMethodInsn(final int opcode, final String owner, final String name, final String desc, final boolean itf) {
			super.visitMethodInsn(opcode, owner, name, desc, itf);
			if (name.equals("generate")) {
				// Code that calls generate:
				//	ALOAD 0
				//	GETFIELD net/sf/cglib/core/AbstractClassGenerator.strategy : Lnet/sf/cglib/core/GeneratorStrategy;
				//	ALOAD 0
				//	INVOKEINTERFACE net/sf/cglib/core/GeneratorStrategy.generate(Lnet/sf/cglib/core/ClassGenerator;)[B
				mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
				mv.visitFieldInsn(GETFIELD, prefix+"/cglib/core/AbstractClassGenerator", "strategy",
						"L"+prefix+"/cglib/core/GeneratorStrategy;");
				mv.visitVarInsn(ALOAD, 0); // AbstractClassGenerator instance
				mv.visitMethodInsn(INVOKESTATIC, "org/springsource/loaded/agent/CglibPluginCapturing", "record",
						"(Ljava/lang/Object;Ljava/lang/Object;)V", false);//Lnet/sf/cglib/core/GeneratorStrategy;Lnet/sf/cglib/core/AbstractClassGenerator);");			
			}
		}

	}

	/**
	 * The classloader for class artifacts is used to load the generated classes for call sites. We need to rewrite these classes
	 * because they may be either calling something that disappears on a later reload (so need to fail appropriately) or calling
	 * something that isnt there on the first load - in this latter case they are changed to route the dynamic executor method.
	 * 
	 * @param a the GeneratorStrategy being used
	 * @param b the AbstractClassGenerator
	 */
	public static void record(Object a, Object b) {
		// a is a Lnet/sf/cglib/core/GeneratorStrategy;
		// b is a Lnet/sf/cglib/core/AbstractClassGenerator (or specifically net/sf/cglib/reflect/FastClass$Generator)
		// a is something like 'UndeclaredThrowableStrategy'
		// b is an Enhancer:  namePrefix="example.Simple" superclass=example.Simple
		String generatorName = b.getClass().getName();
		if (generatorName.endsWith(".cglib.proxy.Enhancer")) {
			try {
				Field f = b.getClass().getDeclaredField("superclass");
				f.setAccessible(true);
				Class<?> clazz = (Class<?>) f.get(b);
				// System.out.println("Recording pair " + clazz.getName() + " > " + b);
				clazzToGeneratorStrategyAndClassGeneratorMap.put(clazz, new Object[] { a, b });
			} catch (Throwable re) {
				re.printStackTrace();
			}
		} else if (generatorName.endsWith(".cglib.reflect.FastClass$Generator")) {
			try {
				Field f = b.getClass().getDeclaredField("type");
				f.setAccessible(true);
				Class<?> clazz = (Class<?>) f.get(b);
				// System.out.println("Recording pair (fastclass) " + clazz.getName() + " > " + b);
				clazzToGeneratorStrategyAndFastClassGeneratorMap.put(clazz, new Object[] { a, b });
			} catch (Throwable re) {
				re.printStackTrace();
			}
		}
	}
}