package org.hildan.livedoc.springmvc.scanner.builder;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import org.hildan.livedoc.springmvc.scanner.utils.ClasspathUtils;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.web.bind.annotation.RequestMapping;

public class SpringPathBuilder {

    public static Set<String> buildPath(Method method, Class<?> controller) {
        return getMappings(method, controller, getPathExtractors());
    }

    private static List<PathExtractor<?>> getPathExtractors() {
        List<PathExtractor<?>> extractors = new ArrayList<>(3);
        if (ClasspathUtils.isMessageMappingOnClasspath()) {
            extractors.add(new PathExtractor<>(MessageMapping.class, MessageMapping::value));
        }
        if (ClasspathUtils.isSubscribeMappingOnClasspath()) {
            extractors.add(new PathExtractor<>(SubscribeMapping.class, SubscribeMapping::value));
        }
        if (ClasspathUtils.isRequestMappingOnClasspath()) {
            extractors.add(new PathExtractor<>(RequestMapping.class, RequestMapping::value, RequestMapping::path));
        }
        return extractors;
    }

    private static Set<String> getMappings(Method method, Class<?> controller, List<PathExtractor<?>> extractors) {
        Set<String> paths = new HashSet<>();
        for (PathExtractor<?> extractor : extractors) {
            if (method.isAnnotationPresent(extractor.getAnnotationClass())) {
                paths.addAll(getMappings(method, controller, extractor));
            }
        }
        return paths;
    }

    private static Set<String> getMappings(Method method, Class<?> controller, PathExtractor<?> extractor) {
        Set<String> controllerMappings = extractor.extractPaths(controller);
        Set<String> methodMappings = extractor.extractPaths(method);
        return joinAll(controllerMappings, methodMappings);
    }

    private static Set<String> joinAll(Set<String> pathPrefixes, Set<String> pathsSuffixes) {
        Set<String> mappings = new HashSet<>();
        for (String controllerPath : pathPrefixes) {
            for (String methodPath : pathsSuffixes) {
                mappings.add(join(controllerPath, methodPath));
            }
        }
        return mappings;
    }

    private static String join(String path1, String path2) {
        boolean path1HasSep = path1.endsWith("/");
        boolean path2HasSep = path2.startsWith("/");
        if (path1HasSep && path2HasSep) {
            return path1 + path2.substring(1);
        }
        if (!path1HasSep && !path2HasSep && (path1.isEmpty() || !path2.isEmpty())) {
            return path1 + '/' + path2;
        }
        return path1 + path2;
    }

    private static class PathExtractor<A extends Annotation> {

        private final Class<A> annotationClass;

        private final Function<A, String[]>[] pathsExtractors;

        @SafeVarargs
        private PathExtractor(Class<A> annotationClass, Function<A, String[]>... pathsExtractors) {
            this.annotationClass = annotationClass;
            this.pathsExtractors = pathsExtractors;
        }

        Class<A> getAnnotationClass() {
            return annotationClass;
        }

        Set<String> extractPaths(AnnotatedElement element) {
            A annotation = element.getAnnotation(annotationClass);
            if (annotation == null) {
                return Collections.singleton("");
            }
            Set<String> paths = new HashSet<>();
            for (Function<A, String[]> pathExtractor : pathsExtractors) {
                Collections.addAll(paths, pathExtractor.apply(annotation));
            }
            if (paths.isEmpty()) {
                paths.add("");
            }
            return paths;
        }
    }
}

@@ -188,24 +188,49 @@
 .ArrayList;%0A
+import java.util.Arrays;%0A
 import java.
@@ -1023,36 +1023,32 @@
  extractors.add(
-new 
 PathExtractor%3C%3E(
@@ -1044,18 +1044,19 @@
 xtractor
-%3C%3E
+.of
 (Message
@@ -1186,36 +1186,32 @@
  extractors.add(
-new 
 PathExtractor%3C%3E(
@@ -1207,18 +1207,19 @@
 xtractor
-%3C%3E
+.of
 (Subscri
@@ -1359,20 +1359,16 @@
 ors.add(
-new 
 PathExtr
@@ -1372,18 +1372,19 @@
 xtractor
-%3C%3E
+.of
 (Request
@@ -3173,16 +3173,21 @@
 e final 
+List%3C
 Function
@@ -3199,18 +3199,17 @@
 tring%5B%5D%3E
-%5B%5D
+%3E
  pathsEx
@@ -3223,29 +3223,8 @@
 s;%0A%0A
-        @SafeVarargs%0A
     
@@ -3275,16 +3275,21 @@
 nClass, 
+List%3C
 Function
@@ -3305,11 +3305,9 @@
 g%5B%5D%3E
-...
+%3E
  pat
@@ -3429,32 +3429,304 @@
 ors;%0A        %7D%0A%0A
+        @SafeVarargs%0A        public static %3CA extends Annotation%3E PathExtractor%3CA%3E of(Class%3CA%3E annotationClass,%0A                Function%3CA, String%5B%5D%3E... pathsExtractors) %7B%0A            return new PathExtractor%3C%3E(annotationClass, Arrays.asList(pathsExtractors));%0A        %7D%0A%0A
         Class%3CA%3E

Fix generic array creation warning in SpringPathBuilder