package org.bench4q.agent.plugin;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.bench4q.agent.helper.ClassHelper;
import org.bench4q.agent.helper.ClassLoadRestriction;
import org.bench4q.agent.plugin.behavior.Behavior;
import org.bench4q.share.helper.ExceptionLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class PluginManager {
	private static final String PLUGIN_PACKAGE_NAME = "org.bench4q.agent.plugin";
	private ClassHelper classHelper;
	private TypeConverter typeConverter;
	private Map<String, Class<?>> plugins;
	private Logger logger = Logger.getLogger(PluginManager.class);

	@Autowired
	public PluginManager(ClassHelper classHelper, TypeConverter typeConverter) {
		this.setClassHelper(classHelper);
		this.setTypeConverter(typeConverter);
		this.setPlugins(this.getClassHelper().loadClasses(PLUGIN_PACKAGE_NAME,
				new ClassLoadRestriction() {
					public boolean accept(Class<?> classUnderTest) {
						return classUnderTest.isAnnotationPresent(Plugin.class);
					}

					public String getAcceptKey(Class<?> classUnderTest) {
						return classUnderTest.getAnnotation(Plugin.class)
								.value();
					}
				}));
	}

	private ClassHelper getClassHelper() {
		return classHelper;
	}

	private void setClassHelper(ClassHelper classHelper) {
		this.classHelper = classHelper;
	}

	private TypeConverter getTypeConverter() {
		return typeConverter;
	}

	private void setTypeConverter(TypeConverter typeConverter) {
		this.typeConverter = typeConverter;
	}

	public Map<String, Class<?>> getPlugins() {
		return plugins;
	}

	private void setPlugins(Map<String, Class<?>> plugins) {
		this.plugins = plugins;
	}

	public List<PluginInfo> getPluginInfo() {
		try {
			Map<String, Class<?>> plugins = this.getPlugins();
			List<PluginInfo> ret = new ArrayList<PluginInfo>();
			for (Class<?> plugin : plugins.values()) {
				PluginInfo pluginInfo = new PluginInfo();
				pluginInfo
						.setName((plugin.getAnnotation(Plugin.class)).value());
				pluginInfo
						.setParameters(this.getParameters(getConstructor(plugin
								.getConstructors())));
				Method[] behaviors = this.getBehaviors(plugin);
				pluginInfo.setBehaviors(new BehaviorInfo[behaviors.length]);
				int i = 0;
				for (i = 0; i < behaviors.length; i++) {
					Method behaviorMethod = behaviors[i];
					BehaviorInfo behaviorInfo = buildBehaviorInfo(behaviorMethod);
					pluginInfo.getBehaviors()[i] = behaviorInfo;
				}
				ret.add(pluginInfo);
			}
			return ret;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private BehaviorInfo buildBehaviorInfo(Method behaviorMethod)
			throws ClassNotFoundException {
		BehaviorInfo behaviorInfo = new BehaviorInfo();
		behaviorInfo.setName((behaviorMethod.getAnnotation(Behavior.class))
				.value());
		behaviorInfo.setParameters(this.getParameters(behaviorMethod));
		return behaviorInfo;
	}

	private ParameterInfo[] getParameters(Method behavior) {
		try {
			int parameterCount = behavior.getParameterTypes().length;
			Annotation[][] parameterAnnotations = behavior
					.getParameterAnnotations();
			ParameterInfo[] parameterNames = new ParameterInfo[parameterCount];
			int i;
			for (i = 0; i < parameterCount; i++) {
				Annotation[] annotations = parameterAnnotations[i];
				Parameter parameter = (Parameter) annotations[0];
				Class<?> type = behavior.getParameterTypes()[i];
				ParameterInfo parameterInfo = buildParameterInfo(
						parameter.value(), type.getName());
				parameterNames[i] = parameterInfo;
			}
			return parameterNames;
		} catch (Exception e) {
			logger.error(ExceptionLog.getStackTrace(e));
			return null;
		}
	}

	private ParameterInfo[] getParameters(Constructor<?> behavior) {
		try {
			int parameterCount = behavior.getParameterTypes().length;
			Annotation[][] parameterAnnotations = behavior
					.getParameterAnnotations();
			ParameterInfo[] parameterNames = new ParameterInfo[parameterCount];
			int i;
			for (i = 0; i < parameterCount; i++) {
				Annotation[] annotations = parameterAnnotations[i];
				Parameter parameter = (Parameter) annotations[0];
				Class<?> type = behavior.getParameterTypes()[i];
				ParameterInfo parameterInfo = buildParameterInfo(
						parameter.value(), type.getName());
				parameterNames[i] = parameterInfo;
			}
			return parameterNames;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private ParameterInfo buildParameterInfo(String name, String type) {
		ParameterInfo parameterInfo = new ParameterInfo();
		parameterInfo.setName(name);
		parameterInfo.setType(type);
		return parameterInfo;
	}

	private Method[] getBehaviors(Class<?> plugin) {
		try {
			Method[] methods = plugin.getMethods();
			List<Method> ret = new ArrayList<Method>();
			int i = 0;
			for (i = 0; i < methods.length; i++) {
				if (methods[i].isAnnotationPresent(Behavior.class)) {
					ret.add(methods[i]);
				}
			}
			return ret.toArray(new Method[0]);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Object initializePlugin(Class<?> plugin,
			Map<String, String> parameters, Map<String, String> extraParameters) {
		try {
			Constructor<?>[] ctConstructors = plugin.getConstructors();
			if (ctConstructors.length != 1) {
				return null;
			}
			if (needExtraParameters(plugin)) {
				parameters.putAll(extraParameters);
			}
			Constructor<?> constructor = getConstructor(ctConstructors);
			Object[] params = prepareParameters(constructor, parameters);
			return constructor.newInstance(params);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private boolean needExtraParameters(Class<?> plugin) {
		return plugin.getSuperclass().equals(ParameterBarn.class);
	}

	private Constructor<?> getConstructor(Constructor<?>[] ctConstructors) {
		for (Constructor<?> constructor2 : ctConstructors) {
			if (constructor2
					.isAnnotationPresent(org.bench4q.agent.plugin.Constructor.class)) {
				return constructor2;
			}
		}
		return null;
	}

	private Object[] prepareParameters(Constructor<?> behavior,
			Map<String, String> parameters) {
		try {
			ParameterInfo[] parameterInfo = this.getParameters(behavior);
			Object values[] = new Object[parameterInfo.length];
			int i = 0;
			for (i = 0; i < parameterInfo.length; i++) {
				Object value = parameters.get(parameterInfo[i].getName());
				if (value == null) {
					values[i] = null;
				} else {
					values[i] = this.getTypeConverter().convert(value,
							parameterInfo[i].getType());
				}
			}
			return values;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private Object[] prepareParameters(Method behavior,
			Map<String, String> parameters) {
		try {
			ParameterInfo[] parameterInfo = this.getParameters(behavior);
			Object values[] = new Object[parameterInfo.length];
			int i = 0;
			for (i = 0; i < parameterInfo.length; i++) {
				Object value = parameters.get(parameterInfo[i].getName());
				if (value == null) {
					values[i] = null;
				} else {
					values[i] = this.getTypeConverter().convert(value,
							parameterInfo[i].getType());
				}
			}
			return values;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public Object doBehavior(Object plugin, String behaviorName,
			Map<String, String> parameters) {
		try {
			Method method = findMethod(plugin, behaviorName);
			if (method == null) {
				return null;
			}
			Object[] params = prepareParameters(method, parameters);
			return method.invoke(plugin, params);
		} catch (Exception e) {
			return null;
		}
	}

	private Method findMethod(Object plugin, String behaviorName) {
		try {
			Method[] methods = plugin.getClass().getMethods();
			int i = 0;
			for (i = 0; i < methods.length; i++) {
				if (methods[i].isAnnotationPresent(Behavior.class)) {
					if (((Behavior) methods[i].getAnnotation(Behavior.class))
							.value().equals(behaviorName)) {
						return methods[i];
					}
				}
			}
			return null;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}
