
package cz.habarta.typescript.generator;

import cz.habarta.typescript.generator.parser.*;
import cz.habarta.typescript.generator.util.Predicate;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class Input {

    private final List<SourceType<Type>> sourceTypes;

    private Input(List<SourceType<Type>> sourceTypes) {
        this.sourceTypes = sourceTypes;
    }

    public List<SourceType<Type>> getSourceTypes() {
        return sourceTypes;
    }

    public static Input from(Type... types) {
        final List<SourceType<Type>> sourceTypes = new ArrayList<>();
        for (Type type : types) {
            sourceTypes.add(new SourceType<>(type));
        }
        return new Input(sourceTypes);
    }

    public static Input fromClassNamesAndJaxrsApplication(List<String> classNames, List<String> classNamePatterns, String jaxrsApplicationClassName, boolean automaticJaxrsApplication, Predicate<String> isClassNameExcluded, ClassLoader classLoader) {
        final ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(classLoader);
            final List<SourceType<Type>> types = new ArrayList<>();
            if (classNames != null) {
                types.addAll(fromClassNames(classNames).getSourceTypes());
            }
            if (classNamePatterns != null) {
                types.addAll(fromClassNamePatterns(scanClasspath(), classNamePatterns).getSourceTypes());
            }
            if (jaxrsApplicationClassName != null) {
                types.addAll(JaxrsApplicationScanner.scanJaxrsApplication(jaxrsApplicationClassName, isClassNameExcluded));
            }
            if (automaticJaxrsApplication) {
                types.addAll(JaxrsApplicationScanner.scanJaxrsApplication(scanClasspath(), isClassNameExcluded));
            }
            if (types.isEmpty()) {
                final String errorMessage = "No input classes found.";
                System.out.println(errorMessage);
                throw new RuntimeException(errorMessage);
            }
            return new Input(types);
        } finally {
            Thread.currentThread().setContextClassLoader(originalContextClassLoader);
        }
    }

    private static FastClasspathScanner scanClasspath() {
        if (fastClasspathScanner == null) {
            System.out.println("Scanning classpath");
            final Date scanStart = new Date();
            final FastClasspathScanner scanner = new FastClasspathScanner().scan();
            final int count = scanner.getNamesOfAllClasses().size();
            final Date scanEnd = new Date();
            final double timeInSeconds = (scanEnd.getTime() - scanStart.getTime()) / 1000.0;
            System.out.println(String.format("Scanning finished in %.2f seconds. Total number of classes: %d.", timeInSeconds, count));
            fastClasspathScanner = scanner;
        }
        return fastClasspathScanner;
    }

    private static FastClasspathScanner fastClasspathScanner = null;

    private static Input fromClassNamePatterns(FastClasspathScanner scanner, List<String> classNamePatterns) {
        final List<String> allClassNames = new ArrayList<>();
        allClassNames.addAll(scanner.getNamesOfAllStandardClasses());
        allClassNames.addAll(scanner.getNamesOfAllInterfaceClasses());
        Collections.sort(allClassNames);
        final List<String> classNames = filterClassNames(allClassNames, classNamePatterns);
        System.out.println(String.format("Found %d classes matching pattern.", classNames.size()));
        return fromClassNames(classNames);
    }

    private static Input fromClassNames(List<String> classNames) {
        try {
            final List<SourceType<Type>> types = new ArrayList<>();
            for (String className : classNames) {
                final Class<?> cls = Thread.currentThread().getContextClassLoader().loadClass(className);
                // skip synthetic classes (as those generated by java compiler for switch with enum)
                // and anonymous classes (should not be processed and they do not have SimpleName)
                if (!cls.isSynthetic() && !cls.isAnonymousClass()) {
                    types.add(new SourceType<Type>(cls, null, null));
                }
            }
            return new Input(types);
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    static List<String> filterClassNames(List<String> classNames, List<String> globs) {
        final List<Pattern> regexps = globsToRegexps(globs);
        final List<String> result = new ArrayList<>();
        for (String className : classNames) {
            if (classNameMatches(className, regexps)) {
                result.add(className);
            }
        }
        return result;
    }

    static boolean classNameMatches(String className, List<Pattern> regexps) {
        for (Pattern regexp : regexps) {
            if (regexp.matcher(className).matches()) {
                return true;
            }
        }
        return false;
    }

    static List<Pattern> globsToRegexps(List<String> globs) {
        final List<Pattern> regexps = new ArrayList<>();
        for (String glob : globs) {
            regexps.add(globToRegexp(glob));
        }
        return regexps;
    }

    /**
     * Creates regexp for glob pattern.
     * Replaces "*" with "[^.\$]*" and "**" with ".*".
     */
    static Pattern globToRegexp(String glob) {
        final Pattern globToRegexpPattern = Pattern.compile("(\\*\\*)|(\\*)");
        final Matcher matcher = globToRegexpPattern.matcher(glob);
        final StringBuffer sb = new StringBuffer();
        int lastEnd = 0;
        while (matcher.find()) {
            sb.append(Pattern.quote(glob.substring(lastEnd, matcher.start())));
            if (matcher.group(1) != null) {
                sb.append(Matcher.quoteReplacement(".*"));
            }
            if (matcher.group(2) != null) {
                sb.append(Matcher.quoteReplacement("[^.$]*"));
            }
            lastEnd = matcher.end();
        }
        sb.append(Pattern.quote(glob.substring(lastEnd, glob.length())));
        return Pattern.compile(sb.toString());
    }

}

@@ -1281,32 +1281,110 @@
 r(classLoader);%0A
+            final ClasspathScanner classpathScanner = new ClasspathScanner();%0A
             fina
@@ -1658,16 +1658,33 @@
 atterns(
+classpathScanner.
 scanClas
@@ -2054,16 +2054,33 @@
 ication(
+classpathScanner.
 scanClas
@@ -2524,32 +2524,140 @@
  private static 
+class ClasspathScanner %7B%0A%0A        private FastClasspathScanner fastClasspathScanner = null;%0A%0A        public 
 FastClasspathSca
@@ -2683,24 +2683,28 @@
 ) %7B%0A        
+  
+  
 if (fastClas
@@ -2719,32 +2719,36 @@
 nner == null) %7B%0A
+    
             Syst
@@ -2789,32 +2789,36 @@
 %22);%0A            
+ 
+   
 final Date scanS
@@ -2828,32 +2828,36 @@
 t = new Date();%0A
+    
             fina
@@ -2932,24 +2932,28 @@
             
+    
 final int co
@@ -3001,32 +3001,36 @@
 ();%0A            
+   
+ 
 final Date scanE
@@ -3038,32 +3038,36 @@
 d = new Date();%0A
+    
             fina
@@ -3147,32 +3147,36 @@
 .0;%0A            
+  
+  
 System.out.print
@@ -3295,16 +3295,20 @@
         
+    
 fastClas
@@ -3331,34 +3331,42 @@
 canner;%0A        
+   
+ 
 %7D%0A
+    
         return f
@@ -3394,79 +3394,20 @@
     
-%7D%0A%0A
     
-private static FastClasspathScanner fastClasspathScanner = null;
+%7D%0A%0A    %7D
 %0A%0A  

fix in classpath scanning