package com.wecloud.dispatch;

import com.wecloud.dispatch.common.ActionMethod;
import com.wecloud.dispatch.common.ApplyInfo;
import com.wecloud.dispatch.common.BaseRequest;
import com.wecloud.dispatch.config.ActionConfigurer;
import com.wecloud.dispatch.exception.ActionNotFoundException;
import com.wecloud.dispatch.extend.ActionBox;
import com.wecloud.dispatch.extend.ActionInterceptor;
import com.wecloud.dispatch.extend.ActionMessage;
import com.wecloud.dispatch.extend.ActionMethodInterceptor;
import com.wecloud.dispatch.extend.ActionRequest;
import com.wecloud.dispatch.extend.ArgumentBox;
import com.wecloud.dispatch.extend.impl.DefaultActionMessage;
import com.wecloud.dispatch.extend.impl.DefaultArgumentBox;
import com.wecloud.dispatch.extend.impl.DefaultMethodArgumentResolver;
import com.wecloud.dispatch.registry.ActionBoxRegistry;
import com.wecloud.dispatch.registry.ActionInterceptorRegistry;
import com.wecloud.dispatch.registry.ActionRegistry;
import com.wecloud.dispatch.registry.MethodArgumentResolverRegistry;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

/**
 * @author lixiaozhong
 */
@Slf4j
public class ActionDispatcher {

	protected ActionContext actionContext = new ActionContext(this);
	private final DefaultMethodArgumentResolver defaultMethodArgumentResolver = new DefaultMethodArgumentResolver();

	public Object action(String action, BaseRequest message) {
		ActionMessage am = resolver(action, message);
		return action(am, getArgumentBox());
	}

	public Object action(String action, BaseRequest message, ArgumentBox argumentBox) {
		Object returnValue = null;
		ActionMessage am = resolver(action, message);
		returnValue = action(am, argumentBox == null ? getArgumentBox() : argumentBox);
		return returnValue;
	}

	public Object action(ActionMessage am) {
		ActionRequest request = getActionRequest(am);

		return action(request, getArgumentBox());
	}

	public Object action(ActionMessage am, ArgumentBox argumentBox) {
		Object returnValue = null;
		ActionRequest request = getActionRequest(am);
		returnValue = action(request, argumentBox);
		return returnValue;
	}

	public Object action(ActionRequest request, ArgumentBox argumentBox) {
		Object returnValue = null;
		ApplyInfo applyInfo = applyInterceptor(request, argumentBox);
		if (applyInfo.isApprove()) {
			returnValue = invokeForRequest(request, argumentBox);
		} else {
			returnValue = applyInfo.getValue();
		}
		return returnValue;
	}

	public ActionRequest getActionRequest(ActionMessage am) {
		String actionCode = am.getAction();
		ActionMethod actionMethod = actionContext.getActionRegistry().getActionMethod(actionCode);
		if (null == actionMethod) {
			throw new ActionNotFoundException(actionCode + " not found");
		}
		Object action = null;
		Class<?> type = actionMethod.getActionClass();
		action = getAction(type);
		return createActionRequest(action, actionMethod, am);
	}

	public Object getAction(Class<?> type) {
		Object a = actionContext.getActionBoxRegistry().getAction(type);
		if (a == null) {
			a = actionContext.getActionBox().getAction(type);
		}
		return a;
	}

	public ActionRequest createActionRequest(Object action, ActionMethod actionMethod, ActionMessage am) {
		ActionRequest actionRequest = new ActionRequestImpl(action, actionMethod, am);
		return actionRequest;
	}

	public ApplyInfo applyInterceptor(ActionRequest request, ArgumentBox argumentBox) {
		ApplyInfo applyInfo = null;
		List<ActionInterceptor> list = actionContext.getActionInterceptorRegistry().getActionInterceptorList();
		if (null != list) {
			for (ActionInterceptor ai : list) {
				applyInfo = ai.previous(actionContext, request, argumentBox);
				if (null == applyInfo) {
					applyInfo = new ApplyInfo();
					break;
				} else if (!applyInfo.isApprove()) {
					break;
				}
			}
		}
		if (null == applyInfo) {
			applyInfo = new ApplyInfo();
			applyInfo.setApprove(true);
		}
		return applyInfo;
	}

	public Object invokeForRequest(ActionRequest request, ArgumentBox argumentBox) {
		Object[] args = getMethodArgumentValues(request, argumentBox);
		if (log.isTraceEnabled()) {
//			StringBuilder sb = new StringBuilder("Invoking [");
//			logger.debug(sb.toString());
		}
		Object bean = request.getAction();
		ActionMethod actionMethod = request.getActionMethod();
		Object returnValue = null;
		ApplyInfo applyInfo = applyActionMethodInterceptor(bean, actionMethod.getMethod(), args, actionMethod.getMethodParameters(), request, argumentBox);
		if (applyInfo.isApprove()) {
			returnValue = doInvoke(bean, actionMethod.getMethod(), args);
			if (log.isTraceEnabled()) {
				log.trace("Method [" + actionMethod.getMethod().getName() + "] returned [" + returnValue + "]");
			}
		} else {
			returnValue = applyInfo.getValue();
		}
		return returnValue;
	}

	public ApplyInfo applyActionMethodInterceptor(Object bean, Method method, Object[] args, MethodParameter[] parameter, ActionRequest request, ArgumentBox argumentBox) {
		ApplyInfo applyInfo = null;
		List<ActionMethodInterceptor> list = actionContext.getActionMethodInterceptorRegistry().getList();
		if (null != list) {
			for (ActionMethodInterceptor ai : list) {
				applyInfo = ai.intercept(actionContext, bean, method, args, parameter, request, argumentBox);
				if (null == applyInfo) {
					applyInfo = new ApplyInfo();
					break;
				} else if (!applyInfo.isApprove()) {
					break;
				}
			}
		}
		if (null == applyInfo) {
			applyInfo = new ApplyInfo();
			applyInfo.setApprove(true);
		}
		return applyInfo;
	}

	public Object[] getMethodArgumentValues(ActionRequest request, ArgumentBox argumentBox) {

		ActionMethod actionMethod = request.getActionMethod();
		MethodParameter[] parameters = actionMethod.getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			// parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			// GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
			if (this.actionContext.getMethodArgumentResolverRegistry().supportsParameter(parameter)) {
				args[i] = this.actionContext.getMethodArgumentResolverRegistry().resolveArgument(actionContext, parameter, request, argumentBox);
				continue;
			}
			if (args[i] == null) {
				args[i] = defaultMethodArgumentResolver.resolveArgument(actionContext, parameter, request, argumentBox);
			}
			if (args[i] == null) {
				String msg = ("No suitable resolver for argument" + i);
				throw new IllegalStateException(msg);
			}
		}
		return args;
	}

	public Object doInvoke(Object bean, Method method, Object... args) {
		try {
			makeAccessible(method);
			return method.invoke(bean, args);
		} catch (IllegalArgumentException e) {
			throw e;
		} catch (RuntimeException e) {
			throw e;
		} catch (InvocationTargetException e) {
			Throwable t = e.getTargetException();
			if (t instanceof RuntimeException) {
				throw (RuntimeException) t;
			}
			String message = (e.getMessage() != null ? e.getMessage() : "方法调用异常");
			throw new IllegalStateException(message, e);
		} catch (IllegalAccessException e) {
			Throwable t = e.getCause();
			if (t instanceof RuntimeException) {
				throw (RuntimeException) t;
			}
			String message = (e.getMessage() != null ? e.getMessage() : "非法访问");
			throw new IllegalStateException(message, e);
		}
	}

	public void makeAccessible(Method method) {
		boolean isNotAccessible = (!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible();
		if (isNotAccessible) {
			method.setAccessible(true);
		}
	}

	public ArgumentBox getArgumentBox() {
		ArgumentBox ab = new DefaultArgumentBox();
		return ab;
	}

	public ActionMessage resolver(String action, BaseRequest message) {
		ActionMessage am = new DefaultActionMessage(action, message);
		return am;
	}

	public void scan(String... path) {
		actionContext.scan(path);
	}

	public void cover(String... path) {
		actionContext.cover(path);
	}

	public void cover(Class<?> classType) {
		actionContext.cover(classType);
	}

	public void addConfig(ActionConfigurer actionConfigurer) {
		actionContext.addConfig(actionConfigurer);
	}

	public ActionRegistry getActionRegistry() {
		return actionContext.getActionRegistry();
	}

	public ActionBoxRegistry getActionBoxRegistry() {
		return actionContext.getActionBoxRegistry();
	}

	public MethodArgumentResolverRegistry getMethodArgumentResolverRegistry() {
		return actionContext.getMethodArgumentResolverRegistry();
	}

	public ActionInterceptorRegistry getActionInterceptorRegistry() {
		return actionContext.getActionInterceptorRegistry();
	}

	public ActionBox getActionBox() {
		return actionContext.getActionBox();
	}

	class ActionRequestImpl implements ActionRequest {

		private ActionMethod actionMethod;
		private Object action;
		private BaseRequest data;
		private String path;
		private Long senderClientId;
		private Channel senderChannel;

		ActionRequestImpl(String path, Object action, ActionMethod actionMethod, BaseRequest data, Long senderClientId, Channel senderChannel) {
			this.path = path;
			this.action = action;
			this.actionMethod = actionMethod;
			this.data = data;
			this.senderClientId = senderClientId;
			this.senderChannel = senderChannel;
		}

		ActionRequestImpl(Object action, ActionMethod actionMethod, ActionMessage am) {
			this.path = am.getAction();
			this.action = action;
			this.actionMethod = actionMethod;
			this.data = am.getMessage();
			this.senderClientId = am.getSenderClientId();
			this.senderChannel = am.getSenderChannel();
		}

		@Override
		public Object getAction() {
			return this.action;
		}

		@Override
		public BaseRequest getData() {
			return this.data;
		}

		@Override
		public ActionMethod getActionMethod() {
			return this.actionMethod;
		}

		@Override
		public Long getSenderClientId() {
			return this.senderClientId;
		}

		@Override
		public Channel getSenderChannel() {
			return this.senderChannel;
		}

		@Override
		public String getPath() {
			return path;
		}

	}
}
