package com.vgerbot.orm.influxdb.factory;

import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;

import com.vgerbot.orm.influxdb.InfluxDBDao;
import com.vgerbot.orm.influxdb.binding.MapperMethod;
import com.vgerbot.orm.influxdb.binding.MethodSignature;
import com.vgerbot.orm.influxdb.exec.Executor;
import com.vgerbot.orm.influxdb.param.ParameterValue;
import com.vgerbot.orm.influxdb.repo.InfluxDBRepository;
import com.vgerbot.orm.influxdb.result.ResultContext;
import com.vgerbot.orm.influxdb.result.ResultResolver;
import com.vgerbot.orm.influxdb.result.resolver.CompositeResultResolver;
import com.vgerbot.orm.influxdb.result.resolver.NativeResultResolver;
import com.vgerbot.orm.influxdb.result.resolver.SingleResultResolver;
import com.vgerbot.orm.influxdb.result.resolver.VoidResultResolver;
import com.vgerbot.orm.influxdb.result.resolver.WrapperResultResolver;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;

public class MapperFactoryBean<T extends InfluxDBDao<?>> implements FactoryBean<T>, InitializingBean {
	private Class<T> mapperInterface;
	private T object;
	private InfluxDBRepository repository;

	private ResultResolver<?> resultResolver;

	@Override
	public void afterPropertiesSet() throws Exception {
		ProxyFactory factory = new ProxyFactory();
		factory.setInterfaces(new Class[] { mapperInterface });
		@SuppressWarnings("unchecked")
		Class<T> cls = factory.createClass();
		T instance = cls.newInstance();
		((Proxy) instance).setHandler(new MapperMethodHandler());

		this.object = instance;

		CompositeResultResolver compositeResultResolver = new CompositeResultResolver();
		compositeResultResolver.setResolvers(defaultResolvers());

		resultResolver = compositeResultResolver;

	}

	private List<ResultResolver<?>> defaultResolvers() {
		List<ResultResolver<?>> resolvers = new LinkedList<>();

		resolvers.add(new NativeResultResolver());
		resolvers.add(new SingleResultResolver());
		resolvers.add(new VoidResultResolver());
		resolvers.add(new WrapperResultResolver());
		return resolvers;
	}

	public void setMapperInterface(Class<T> mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	public void setRepository(InfluxDBRepository repository) {
		this.repository = repository;
	}

	@Override
	public T getObject() throws Exception {
		return object;
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}

	private final class MapperMethodHandler implements MethodHandler {
		private final ConcurrentMap<Method, MapperMethod> methods = new ConcurrentHashMap<>();
		private final MapperObject object = new MapperObject();

		@Override
		public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
			Class<?> declaringClass = thisMethod.getDeclaringClass();
			if (declaringClass == Object.class) {
				return thisMethod.invoke(object, args);
			}
			MapperMethod mapperMethod = methods.get(thisMethod);
			if (mapperMethod == null) {
				MethodSignature signature = new MethodSignature(declaringClass, thisMethod);
				mapperMethod = new MapperMethod(mapperInterface, repository, signature);

				MapperMethod previous = methods.putIfAbsent(thisMethod, mapperMethod);
				if (previous != null) {
					mapperMethod = previous;
				}
			}
			Map<String, ParameterValue> parameters = mapperMethod.getProducer().produce(args);
			Executor executor = mapperMethod.getExecutor();
			ResultContext context = executor.execute(mapperMethod, parameters);

			return resultResolver.resolve(context, mapperMethod.getMethodSignature(), repository);
		}
	}

	private final class MapperObject {
		@Override
		public String toString() {
			return mapperInterface.getName() + '#' + Integer.toHexString(this.hashCode());
		}
	}
}
@@ -65,24 +65,59 @@
 ect.Method;%0A
+import java.lang.reflect.Modifier;%0A
 import java.
@@ -269,16 +269,113 @@
 ntMap;%0A%0A
+import com.vgerbot.orm.influxdb.utils.ResultResolvers;%0Aimport javassist.util.proxy.MethodFilter;%0A
 import o
@@ -1786,16 +1786,364 @@
 ace %7D);%0A
+%09%09factory.setFilter(new MethodFilter() %7B%0A            @Override%0A            public boolean isHandled(Method m) %7B%0A                Class%3C?%3E declaringClass = m.getDeclaringClass();%0A                boolean isAbstract = Modifier.isAbstract(m.getModifiers());%0A                return declaringClass != Object.class %7C%7C isAbstract;%0A            %7D%0A        %7D);%0A
 %09%09@Suppr
@@ -2455,188 +2455,54 @@
 ers(
-defaultResolvers());%0A%0A%09%09resultResolver = compositeResultResolver;%0A%0A%09%7D%0A%0A%09private List%3CResultResolver%3C?%3E%3E defaultResolvers() %7B%0A%09%09List%3CResultResolver%3C?%3E%3E resolvers = new Linked
+ResultResolvers.getDefaultResultResolver
 List
-%3C%3E
 (
+)
 );%0A%0A
@@ -2510,201 +2510,47 @@
 %09res
-olvers.add(new NativeResultResolver());%0A%09%09resolvers.add(new SingleResultResolver());%0A%09%09resolvers.add(new VoidResultResolver());%0A%09%09resolvers.add(new WrapperResultResolver());%0A%09%09return resolvers;
+ultResolver = compositeResultResolver;%0A
 %0A%09%7D%0A
@@ -3124,67 +3124,8 @@
 ();%0A
-%09%09private final MapperObject object = new MapperObject();%0A%0A
 %09%09@O
@@ -3302,98 +3302,8 @@
 ();%0A
-%09%09%09if (declaringClass == Object.class) %7B%0A%09%09%09%09return thisMethod.invoke(object, args);%0A%09%09%09%7D%0A
 %09%09%09M
@@ -3997,173 +3997,6 @@
 %7D%0A%09%7D
-%0A%0A%09private final class MapperObject %7B%0A%09%09@Override%0A%09%09public String toString() %7B%0A%09%09%09return mapperInterface.getName() + '#' + Integer.toHexString(this.hashCode());%0A%09%09%7D%0A%09%7D
 %0A%7D

set a method filter for ProxyFactory