package com.wecloud.dispatch.registry;

import com.wecloud.dispatch.annotation.ActionMapping;
import com.wecloud.dispatch.common.ActionMethod;
import com.wecloud.dispatch.exception.MappingException;
import org.springframework.http.server.PathContainer;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author lixiaozhong
 */
public class ActionRegistry {

	private final Map<PathPattern, ActionMethod> actionMethodMap = new ConcurrentHashMap<>(16);
	private String separator = AntPathMatcher.DEFAULT_PATH_SEPARATOR;

	private AntPathMatcher pathMatcher = new AntPathMatcher();
	private final PathPatternParser patternParser = new PathPatternParser();;

	/**
	 * 为了更快获取要执行的action，将要执行的action加入Map进行缓存
	 *
	 * @param classType
	 * @throws MappingException
	 */
	public void add(Class<?> classType) {
		Annotation[] as = classType.getAnnotations();
		ActionMapping am = null;
		for (Annotation annotation : as) {
			if (annotation instanceof ActionMapping) {
				am = ((ActionMapping) annotation);
				break;
			}
		}
		String[] codes = null == am ? new String[] { "" } : am.value();
		add(codes, classType);
	}

	/**
	 * 获取要执行的action的方法，并且缓存
	 *
	 * @param classType
	 * @return
	 * @throws MappingException
	 */
	public void add(String[] codes, Class<?> classType) {
		Method[] methods = classType.getMethods();
		if (null != methods && methods.length > 0) {
			for (Method method : methods) {
				add(codes, method);
			}
		}
	}

	public void add(Method method) {
		if (null != method) {
			Annotation[] as = method.getDeclaringClass().getAnnotations();
			ActionMapping am = null;
			for (Annotation annotation : as) {
				if (annotation instanceof ActionMapping) {
					am = ((ActionMapping) annotation);
					break;
				}
			}
			String[] codes = null == am ? new String[] { "" } : am.value();
			add(codes, method);
		}
	}

	public void add(String[] codes, Method method) {
		if (null != method) {
			codes = (null == codes) ? new String[] { "" } : codes;
			Annotation[] as = method.getAnnotations();
			for (Annotation a : as) {
				if (a instanceof ActionMapping) {
					ActionMapping actionMapping = (ActionMapping) a;
					String[] values = actionMapping.value();
					int order = actionMapping.order();
					List<String> paths = getPaths(codes, values);

					if (null != paths) {

						for (String path : paths) {
							ActionMethod am = getActionMethod(path);
							boolean equals = false;
							if (null != am) {
								equals = null != am.getMethodPath() && am.getMethodPath().equals(path);
							}
							if (null != am && equals) {

								Method lastMethod = am.getMethod();
								if (method.equals(lastMethod)) {
									continue;
								}
								ActionMapping lastMapping = am.getMethod().getAnnotation(ActionMapping.class);
								int lastOrder = lastMapping.order();
								if (order == lastOrder) {
									StringBuilder sb = new StringBuilder();
									sb.append("\n");
									sb.append("ActionMapping：");
									sb.append("\n");
									sb.append("[");
									sb.append(am.getActionClass().getName());
									sb.append("]");
									sb.append(".");
									sb.append("[");
									sb.append(am.getMethod().getName());
									sb.append("]");
									sb.append("\n");
									sb.append("与");
									sb.append("\n");
									sb.append("[");
									sb.append(method.getDeclaringClass().getName());
									sb.append("]");
									sb.append(".");
									sb.append("[");
									sb.append(method.getName());
									sb.append("]");
									sb.append("\n");
									sb.append("存在重复");
									sb.append("[");
									sb.append(path);
									sb.append("]！");
									throw new MappingException(sb.toString());
								} else if (order < lastOrder) {
									put(path, new ActionMethod(method, path));
								}
							} else {
								put(path, new ActionMethod(method, path));
							}
						}
					}
					break;
				}
			}
		}
	}

	public void cover(Class<?> classType) {
		Annotation[] as = classType.getAnnotations();
		ActionMapping am = null;
		for (Annotation annotation : as) {
			if (annotation instanceof ActionMapping) {
				am = ((ActionMapping) annotation);
				break;
			}
		}
		String[] value = null == am ? new String[] { "" } : am.value();
		cover(value, classType);
	}

	public void cover(String[] codes, Class<?> classType) {
		Method[] methods = classType.getMethods();
		if (null != methods && methods.length > 0) {
			for (Method method : methods) {
				Annotation[] as = method.getAnnotations();
				for (Annotation a : as) {
					if (a instanceof ActionMapping) {
						String[] values = ((ActionMapping) a).value();
						List<String> paths = getPaths(codes, values);
						cover(paths, method);
						break;
					}
				}
			}
		}
	}

	public void cover(List<String> paths, Method method) {
		if (null != paths && null != method) {
			for (String path : paths) {
				cover(path, method);
			}
		}
	}

	public void cover(String path, Method method) {
		if (null != path && null != method) {
			put(path, new ActionMethod(method, path));
		}
	}

	public void put(String path, ActionMethod am) {
		PathPattern pattern = patternParser.parse(path);
		actionMethodMap.put(pattern, am);
	}

	public boolean has(String path) {
		ActionMethod method = path == null || path.isEmpty() ? null : lookupHandler(PathContainer.parsePath(path));
		return null != method;
	}

	public ActionMethod lookupHandler(PathContainer lookupPath) {
		ActionMethod am = null;
		List<PathPattern> matches = this.actionMethodMap.keySet().stream()
				.filter(key -> key.matches(lookupPath))
				.collect(Collectors.toList());

		if (matches.isEmpty()) {
			return null;
		}

		if (matches.size() > 1) {
			matches.sort(PathPattern.SPECIFICITY_COMPARATOR);
		}

		PathPattern pattern = matches.get(0);
		am = this.actionMethodMap.get(pattern);
		return am;
	}

	/**
	 * 截取不包含包名，和小写首字母的类名
	 *
	 * @param classType
	 * @return
	 */
	public String getSimpleNameAsProperty(Class<?> classType) {
		String valueName = classType.getSimpleName();
		return valueName = valueName.substring(0, 1).toLowerCase() + valueName.substring(1);
	}

	public Class<?> getClass(String path) {
		ActionMethod am = getActionMethod(path);
		return null == am ? null : am.getActionClass();
	}

	public ActionMethod getActionMethod(String path) {
		// ActionMethod method = actionMethodMap.get(path == null ? "" : path);
		ActionMethod method = path == null || path.isEmpty() ? null : lookupHandler(PathContainer.parsePath(path));
		return method;
	}

	public ActionMethod getActionMethod(String classCode, String methodCode) {
		String path = getPath(classCode, methodCode);
		return getActionMethod(path);
	}

	public List<String> getPaths(String[] classCodes, String[] methodCodes) {
		classCodes = (null == classCodes || classCodes.length == 0) ? new String[] { "" } : classCodes;
		methodCodes = (null == methodCodes || methodCodes.length == 0) ? new String[] { "" } : methodCodes;

		List<String> list = new ArrayList<>();
		for (String classCode : classCodes) {
			for (String methodCode : methodCodes) {
				list.add(getPath(classCode, methodCode));
			}
		}
		return list;
	}

	public String getPath(String classCode, String methodCode) {
//		StringBuilder sb = new StringBuilder();
		String path = pathMatcher.combine(classCode, methodCode);
//		if (isSeparateAlways()) {
//			sb.append(getSeparator());
//		} else if (null != classCode && !classCode.isEmpty() && !classCode.startsWith(getSeparator())) {
//			sb.append(getSeparator());
//		}
//		sb.append(classCode);
//		if (isSeparateAlways()) {
//			sb.append(getSeparator());
//		} else if (!methodCode.startsWith(getSeparator())) {
//			sb.append(getSeparator());
//		}
//		sb.append(methodCode);
		path = prependLeadingSlash(path);
		return path;
	}

	private static String prependLeadingSlash(String pattern) {
		String pre = "/";
		if (StringUtils.hasLength(pattern) && !pattern.startsWith(pre)) {
			return pre + pattern;
		} else {
			return pattern;
		}
	}

	public Map<PathPattern, ActionMethod> getActionMethodMap() {
		return actionMethodMap;
	}

	public String getSeparator() {
		return separator;
	}

	public void setSeparator(String separator) {
		this.separator = separator;
		pathMatcher.setPathSeparator(separator);
	}

//	public boolean isSeparateAlways() {
//		return separateAlways;
//	}
//
//	public void setSeparateAlways(boolean separateAlways) {
//		this.separateAlways = separateAlways;
//	}
//
//	public void setSeparator(String separator, boolean separateAlways) {
//		this.separator = separator;
//		this.separateAlways = separateAlways;
//		pathMatcher.setPathSeparator(separator);
//	}
}
