package com.navercorp.pinpoint.testapp.controller;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * @author koo.taejin
 */
import com.navercorp.pinpoint.testapp.util.Description;

@Controller(value = "apisController")
public class ApisController {

    private final RequestMappingHandlerMapping handlerMapping;

    private final Map<String, SortedSet<RequestMappedUri>> apiMappings = new TreeMap<String, SortedSet<RequestMappedUri>>(String.CASE_INSENSITIVE_ORDER);

    @Autowired
    public ApisController(RequestMappingHandlerMapping handlerMapping) {
        this.handlerMapping = handlerMapping;
    }

    @PostConstruct
    private void initApiMappings() {
        Map<RequestMappingInfo, HandlerMethod> requestMappedHandlers = this.handlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> requestMappedHandlerEntry : requestMappedHandlers.entrySet()) {
            RequestMappingInfo requestMappingInfo = requestMappedHandlerEntry.getKey();
            HandlerMethod handlerMethod = requestMappedHandlerEntry.getValue();

            Class<?> handlerMethodBeanClazz = handlerMethod.getBeanType();
            if (handlerMethodBeanClazz == this.getClass()) {
                continue;
            }

            String controllerName = handlerMethodBeanClazz.getSimpleName();
            Set<String> mappedRequests = requestMappingInfo.getPatternsCondition().getPatterns();

            SortedSet<RequestMappedUri> alreadyMappedRequests = this.apiMappings.get(controllerName);
            if (alreadyMappedRequests == null) {
                alreadyMappedRequests = new TreeSet<RequestMappedUri>(RequestMappedUri.MAPPED_URI_ORDER);
                this.apiMappings.put(controllerName, alreadyMappedRequests);
            }
            alreadyMappedRequests.addAll(createRequestMappedApis(handlerMethod, mappedRequests));
        }
    }

    private Set<RequestMappedUri> createRequestMappedApis(HandlerMethod handlerMethod, Set<String> mappedUris) {
        if (CollectionUtils.isEmpty(mappedUris)) {
            return Collections.emptySet();
        }
        Set<RequestMappedUri> requestMappedUris = new HashSet<RequestMappedUri>(mappedUris.size());
        Description description = handlerMethod.getMethodAnnotation(Description.class);
        for (String mappedUri : mappedUris) {
            requestMappedUris.add(new RequestMappedUri(mappedUri, description));
        }
        return requestMappedUris;
    }

    @RequestMapping(value = {"/", "/index.html", "/apis"}, method = RequestMethod.GET)
    public String apis(Model model) {
        model.addAttribute("apiMappings", this.apiMappings);
        return "apis";
    }

    public static class RequestMappedUri {

        private final String mappedUri;
        private final String description;

        private RequestMappedUri(String mappedUri, Description description) {
            if (mappedUri == null) {
                throw new IllegalArgumentException("mappedUri");
            }
            this.mappedUri = mappedUri;
            this.description = description == null ? "" : description.value();
        }

        public String getMappedUri() {
            return this.mappedUri;
        }

        public String getDescription() {
            return this.description;
        }

        private static final Comparator<RequestMappedUri> MAPPED_URI_ORDER = new Comparator<RequestMappedUri>() {
            @Override
            public int compare(RequestMappedUri arg0, RequestMappedUri arg1) {
                if (arg0 == null && arg1 == null) return 0;
                if (arg1 == null) return -1;
                if (arg0 == null) return 1;
                return String.CASE_INSENSITIVE_ORDER.compare(arg0.mappedUri, arg1.mappedUri);
            }
        };

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((description == null) ? 0 : description.hashCode());
            result = prime * result + ((mappedUri == null) ? 0 : mappedUri.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            RequestMappedUri other = (RequestMappedUri) obj;
            if (description == null) {
                if (other.description != null)
                    return false;
            } else if (!description.equals(other.description))
                return false;
            if (mappedUri == null) {
                if (other.mappedUri != null)
                    return false;
            } else if (!mappedUri.equals(other.mappedUri))
                return false;
            return true;
        }

        @Override
        public String toString() {
            return "RequestMappedUri [mappedUri=" + mappedUri + ", description=" + description + "]";
        }
    }
}
