package cn.potato.core;

import cn.potato.helper.ConvertHelper;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.*;

/**
 * HandlerMethodArgsBuilder 通过反射实现控制器方法参数的注入,由于JDK 1.7 反射无法获取参数名称故采用 javassist。
 */
public class HandlerMethodArgsBuilder {
	private Map<String, Object> requestParamInfo;
	private Map<Class<?>, String> methodParamInfo;
	private List<Object> args;
	private static final Logger log = LoggerFactory.getLogger(HandlerMethodArgsBuilder.class);

	public HandlerMethodArgsBuilder(HttpServletRequest request, Method handler,
			Object controller) {
		initRequestParamInfo(request);
		initMethodParamInfo(controller, handler.getName());
	}

	public Object[] build() {
		inject();
		return args.toArray();
	}

	private void initRequestParamInfo(HttpServletRequest request) {
		requestParamInfo = new HashMap<String, Object>();
		Map<String, String[]> parameterMap = request.getParameterMap();
		Set<String> keySet = parameterMap.keySet();
		for (String key : keySet)
			if (key.contains(".")) {
				String[] fs = key.split("\\.");
				Map<String, Object> beanMap = new HashMap<>();
				for (String subKey : keySet) {
					if (subKey.startsWith(fs[0])) {
						String[] subFs = subKey.split("\\.");
						beanMap.put(subFs[1],parameterMap.get(subKey)[0]);
					}
				}
				requestParamInfo.put(fs[0], beanMap);
			} else {
				requestParamInfo.put(key, parameterMap.get(key)[0]);
			}
	}

	private void initMethodParamInfo(Object controller, String methodName) {
		methodParamInfo = new LinkedHashMap<Class<?>, String>();
		Class<?> clazz = controller.getClass();
		try {
			ClassPool classPool = ClassPool.getDefault();
			classPool.insertClassPath(new ClassClassPath(clazz));
			CtClass ctClass = classPool.get(clazz.getName());
			CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);

			MethodInfo methodInfo = ctMethod.getMethodInfo();
			CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
			LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
					.getAttribute("LocalVariableTable");

			CtClass[] parameterTypes = ctMethod.getParameterTypes();
			int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
			for (int i = 0; i < parameterTypes.length; i++)
				methodParamInfo.put(Class.forName(parameterTypes[i].getName()),
						attr.variableName(i + pos));
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage());
		}
	}

	private void inject() {
		args = new ArrayList<Object>();
		Set<Class<?>> paramTypeClasses = methodParamInfo.keySet();
		try {
			for (Class<?> clazz : paramTypeClasses) {
				String key = (String) methodParamInfo.get(clazz);
				Object object;
				if (requestParamInfo.containsKey(key)) {
					Object value = requestParamInfo.get(key);
					if ((value instanceof Map)) {//如果为Map型的转换为Bean
						@SuppressWarnings("unchecked")
						Map<String, Object> map = (Map<String, Object>) value;
						object  = ConvertHelper.mapConvertToBean(map, clazz);
					}else{
						object = ConvertHelper.convert(value, clazz);
					}
				}else{
					object = clazz.newInstance();
				}
				args.add(object);
			}
		} catch (Exception e) {
			log.error("Handler method args inject fail!", e);
		}
	}
}