package org.springframework.web.servlet.handler;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 覆盖RequestMappingHandlerMapping的实现，优化性能
 *
 * @author yezq
 */
public class KmssRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    /**
     * 构造实例
     */
    public static RequestMappingHandlerMapping build() {
        return new KmssRequestMappingHandlerMapping();
    }

    private KmssMappingRegistry mappingRegistry;

    private List<RequestMappingInfo> patternsMappings = new CopyOnWriteArrayList<>();

    private KmssRequestMappingHandlerMapping() {
        // 重写mappingRegistry属性
        mappingRegistry = new KmssMappingRegistry();
        try {
            Field field = AbstractHandlerMethodMapping.class.getDeclaredField("mappingRegistry");
            field.setAccessible(true);
            field.set(this, mappingRegistry);
        } catch (Exception e) {
            ExceptionUtils.wrapAndThrow(e);
        }
    }

    /**
     * 覆盖MappingRegistry，记录带通配符的路径
     */
    private class KmssMappingRegistry extends AbstractHandlerMethodMapping<RequestMappingInfo>.MappingRegistry {
        @Override
        public void register(RequestMappingInfo mapping, Object handler, Method method) {
            super.register(mapping, handler, method);
            Set<String> paths = getMappingPathPatterns(mapping);
            if (paths.isEmpty()) {
                patternsMappings.add(mapping);
            }else {
                for (String path : paths) {
                    if (getPathMatcher().isPattern(path)) {
                        patternsMappings.add(mapping);
                        return;
                    }
                }
            }
        }

        @Override
        public void unregister(RequestMappingInfo mapping) {
            super.unregister(mapping);
            patternsMappings.remove(mapping);
        }
    }

    // ========== 以下代码主框架抄袭父类，修改地方见注释 ==========
    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<>();
        List<RequestMappingInfo> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            //原文：addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
            //原文的遍历太多，实际上只需要遍历带通配符的mapping就好
            addMatchingMappings(this.patternsMappings, matches, request);
        }

        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            matches.sort(comparator);
            Match bestMatch = matches.get(0);
            if (matches.size() > 1) {
                if (logger.isTraceEnabled()) {
                    logger.trace(matches.size() + " matching mappings: " + matches);
                }
                if (CorsUtils.isPreFlightRequest(request)) {
                    return PREFLIGHT_AMBIGUOUS_MATCH;
                }
                Match secondBestMatch = matches.get(1);
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.handlerMethod.getMethod();
                    Method m2 = secondBestMatch.handlerMethod.getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(
                            "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
                }
            }
            request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        } else {
            //原文：return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
            //原文很奇怪，明明找不到，handleNoMatch又遍历了一次，还是找不到
            return null;
        }
    }

    // ========== 以下代码纯搬运 ==========
    private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH = new HandlerMethod(new EmptyHandler(),
            ClassUtils.getMethod(EmptyHandler.class, "handle"));

    private void addMatchingMappings(Collection<RequestMappingInfo> mappings, List<Match> matches,
                                     HttpServletRequest request) {
        for (RequestMappingInfo mapping : mappings) {
            RequestMappingInfo match = getMatchingMapping(mapping, request);
            if (match != null) {
                matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
            }
        }
    }

    private class MatchComparator implements Comparator<Match> {
        private final Comparator<RequestMappingInfo> comparator;

        public MatchComparator(Comparator<RequestMappingInfo> comparator) {
            this.comparator = comparator;
        }

        @Override
        public int compare(Match match1, Match match2) {
            return this.comparator.compare(match1.mapping, match2.mapping);
        }
    }

    private class Match {
        private final RequestMappingInfo mapping;

        private final HandlerMethod handlerMethod;

        public Match(RequestMappingInfo mapping, HandlerMethod handlerMethod) {
            this.mapping = mapping;
            this.handlerMethod = handlerMethod;
        }

        @Override
        public String toString() {
            return this.mapping.toString();
        }
    }

    private static class EmptyHandler {
        @SuppressWarnings("unused")
        public void handle() {
            throw new UnsupportedOperationException("Not implemented");
        }
    }
}
