
package org.tio.jfinal.template.expr.ast;

import org.tio.jfinal.kit.StrKit;
import org.tio.jfinal.proxy.ProxyClassLoader;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 使用 jfinal proxy 机制消除 java.lang.reflect.Method.invoke(...)
 * 提升性能，并且同时支持动态类型的 field 表达式取值
 */
public class FastFieldGetter extends FieldGetter {
	
	protected static ProxyGenerator generator = new ProxyGenerator();
	protected static ProxyCompiler compiler = new ProxyCompiler();
	protected static ProxyClassLoader classLoader = new ProxyClassLoader();
	protected static Map<Class<?>, Proxy> cache = new ConcurrentHashMap<>(512, 0.25F);
	
	protected static boolean outputCompileError = false;
	
	protected Proxy proxy;
	protected java.lang.reflect.Method getterMethod;
	
	public FastFieldGetter(Proxy proxy, java.lang.reflect.Method getterMethod) {
		this.proxy = proxy;
		this.getterMethod = getterMethod;
	}
	
	/**
	 * 仅用于配置 Engine.addFieldGetter(0, new FastFieldGetter());
	 */
	public FastFieldGetter() {
		this(null, null);
	}
	
	public FieldGetter takeOver(Class<?> targetClass, String fieldName) {
		if (MethodKit.isForbiddenClass(targetClass)) {
			throw new RuntimeException("Forbidden class: " + targetClass.getName());
		}
		
		String getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
		java.lang.reflect.Method[] methodArray = targetClass.getMethods();
		for (java.lang.reflect.Method method : methodArray) {
			if (method.getName().equals(getterName) && method.getParameterCount() == 0) {
				
				Proxy proxy = cache.get(targetClass);
				if (proxy == null) {
					synchronized (targetClass) {
						proxy = cache.get(targetClass);
						if (proxy == null) {	
							try {
								proxy = createProxy(targetClass, fieldName);
							} catch (Throwable e) {
								return null;
							}
							cache.putIfAbsent(targetClass, proxy);
						}
					}
				}
				
				return new FastFieldGetter(proxy, method);
			}
		}
		
		return null;
	}
	
	public Object get(Object target, String fieldName) throws Exception {
		// return getterMethod.invoke(target, ExprList.NULL_OBJECT_ARRAY);
		return proxy.getValue(target, fieldName);
	}
	
	protected Proxy createProxy(Class<?> targetClass, String fieldName) {
		ProxyClass proxyClass = new ProxyClass(targetClass);
		String sourceCode = generator.generate(proxyClass);
		// System.out.println(sourceCode);
		
		proxyClass.setSourceCode(sourceCode);
		compiler.compile(proxyClass);
		Class<?> retClass = classLoader.loadProxyClass(proxyClass);
		proxyClass.setClazz(retClass);
		try {
			return (Proxy)retClass.newInstance();
		} catch (ReflectiveOperationException e) {
			throw new RuntimeException(e);
		}
	}
	
	public String toString() {
		return getterMethod.toString();
	}
	
	// ---------
	
	/**
	 * 代理接口
	 */
	public static interface Proxy {
		public Object getValue(Object target, String fieldName);
	}
	
	// ---------
	
	/**
	 * 代理类
	 */
	static class ProxyClass extends org.tio.jfinal.proxy.ProxyClass {
		
		private String name;		// 类名
		
		public ProxyClass(Class<?> target) {
			super(target);
			
			name = target.getSimpleName() + "$$EnhancerByJFinal_FieldGetter";
		}
		
		public String getName() {
			return name;
		}
	}
	
	// ---------
	
	/**
	 * 代理生成器
	 */
	static class ProxyGenerator {
		
		String generate(ProxyClass proxyClass) {
			StringBuilder ret = new StringBuilder(1024);
			
			Class<?> targetClass = proxyClass.getTarget();
			String className = proxyClass.getName();
			
			ret.append("package ").append(proxyClass.getPkg()).append(";\n\n");
			ret.append("import org.tio.jfinal.template.expr.ast.FastFieldGetter.Proxy;\n\n");
			ret.append("public class ").append(className).append(" implements Proxy {\n\n");
			ret.append("\tpublic Object getValue(Object target, String fieldName) {\n");
			ret.append("\t\tint hash = fieldName.hashCode();\n");
			ret.append("\t\tswitch (hash) {\n");
			
			java.lang.reflect.Method[] methodArray = targetClass.getMethods();
			for (java.lang.reflect.Method method : methodArray) {
				String mn = method.getName();
				if (method.getParameterCount() == 0 && mn.startsWith("get") && (!mn.equals("getClass"))) {
					String fieldName = StrKit.firstCharToLowerCase(mn.substring(3));
					ret.append("\t\tcase ").append(fieldName.hashCode()).append(" :\n");
					ret.append("\t\t\treturn ((").append(targetClass.getName()).append(")target).").append(mn).append("();\n");
				}
			}
			
			ret.append("\t\tdefault :\n");
			ret.append("\t\t\tthrow new RuntimeException(\"Can not access the field \\\"\" + target.getClass().getName() + \".\" + fieldName + \"\\\"\");\n");
			
			ret.append("\t\t}\n");
			ret.append("\t}\n");
			ret.append("}\n");
			
			return ret.toString();
		}	
	}
	
	// ---------
	
	public static void setOutputCompileError(boolean outputCompileError) {
		FastFieldGetter.outputCompileError = outputCompileError;
	}
	
	/**
	 * 代理编译器
	 */
	static class ProxyCompiler extends org.tio.jfinal.proxy.ProxyCompiler {
		@Override
		protected void outputCompileError(Boolean result, javax.tools.DiagnosticCollector<javax.tools.JavaFileObject> collector) {
			if (outputCompileError) {
				super.outputCompileError(result, collector);
			}
		}
	}
}

