package com.airlock.iam.base.infrastructure.reflection;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ClasspathScanner {
   private final ClassGraph classGraph = new ClassGraph().disableModuleScanning().disableNestedJarScanning();

   public static ClasspathScanner classpathScanner() {
      return new ClasspathScanner();
   }

   private ClasspathScanner() {
   }

   public ClasspathScanner include(Collection<String> includePackage) {
      return this.include(includePackage.toArray(String[]::new));
   }

   public ClasspathScanner include(String... includePackage) {
      this.classGraph.acceptPackages(includePackage);
      return this;
   }

   public ClasspathScanner includeJars(String... jarFileNames) {
      this.classGraph.acceptJars(jarFileNames);
      return this;
   }

   public Set<Method> getMethodsWithAnnotation(Class<? extends Annotation> annotation) {
      ScanResult result = this.classGraph.enableMethodInfo().enableAnnotationInfo().ignoreClassVisibility().ignoreMethodVisibility().scan();

      Set var3;
      try {
         var3 = result.getClassesWithMethodAnnotation(annotation.getName())
            .stream()
            .<Class>map(ClassInfo::loadClass)
            .flatMap(clazz -> Stream.of(clazz.getDeclaredMethods()))
            .filter(method -> method.isAnnotationPresent(annotation))
            .collect(Collectors.toSet());
      } catch (Throwable var6) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (result != null) {
         result.close();
      }

      return var3;
   }

   public Set<Constructor<?>> getConstructorsWithAnnotation(Class<? extends Annotation> annotation) {
      ScanResult result = this.classGraph.enableMethodInfo().enableAnnotationInfo().ignoreClassVisibility().ignoreMethodVisibility().scan();

      Set var3;
      try {
         var3 = result.getClassesWithMethodAnnotation(annotation.getName())
            .stream()
            .<Class>map(ClassInfo::loadClass)
            .flatMap(clazz -> Stream.of(clazz.getDeclaredConstructors()))
            .filter(constructor -> constructor.isAnnotationPresent(annotation))
            .collect(Collectors.toSet());
      } catch (Throwable var6) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (result != null) {
         result.close();
      }

      return var3;
   }

   public List<Class<?>> getClasses() {
      ScanResult result = this.classGraph.enableClassInfo().ignoreClassVisibility().scan();

      List var3;
      try {
         ClassInfoList classes = result.getAllClasses();
         var3 = classes.loadClasses();
      } catch (Throwable var5) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var4) {
               var5.addSuppressed(var4);
            }
         }

         throw var5;
      }

      if (result != null) {
         result.close();
      }

      return var3;
   }

   public <T> List<Class<? extends T>> getSubclasses(Class<T> type) {
      ScanResult result = this.classGraph.enableClassInfo().ignoreClassVisibility().scan();

      List var4;
      try {
         ClassInfoList classes = getSubclasses(result, type).filter(classInfo -> !classInfo.isExternalClass());
         var4 = uncheckedCast(classes.loadClasses(type));
      } catch (Throwable var6) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (result != null) {
         result.close();
      }

      return var4;
   }

   private static <E> List<E> uncheckedCast(List<? extends E> l) {
      return (List<E>)l;
   }

   private static <T> ClassInfoList getSubclasses(ScanResult result, Class<T> type) {
      return type.isInterface() ? result.getClassesImplementing(type.getName()) : result.getSubclasses(type.getName());
   }

   public List<Class<?>> getClassesWithAnnotation(Class<? extends Annotation> annotation) {
      return this.getClassesWithAnnotation(annotation, false);
   }

   public List<Class<?>> getClassesWithAnnotation(Class<? extends Annotation> annotation, boolean ignoreClassLoadingErrors) {
      ScanResult result = this.classGraph.enableAnnotationInfo().ignoreClassVisibility().scan();

      List var5;
      try {
         ClassInfoList classes = result.getClassesWithAnnotation(annotation.getName()).filter(classInfo -> !classInfo.isExternalClass());
         var5 = classes.loadClasses(ignoreClassLoadingErrors);
      } catch (Throwable var7) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var6) {
               var7.addSuppressed(var6);
            }
         }

         throw var7;
      }

      if (result != null) {
         result.close();
      }

      return var5;
   }

   public <T extends Annotation> Set<T> getAnnotationsOnClasses(Class<T> annotation) {
      ScanResult result = this.classGraph.enableAnnotationInfo().ignoreClassVisibility().scan();

      Set var3;
      try {
         var3 = result.getClassesWithAnnotation(annotation.getName())
            .stream()
            .map(classInfo -> classInfo.getAnnotationInfo(annotation.getName()))
            .map(annotationInfo -> annotationInfo.loadClassAndInstantiate())
            .collect(Collectors.toSet());
      } catch (Throwable var6) {
         if (result != null) {
            try {
               result.close();
            } catch (Throwable var5) {
               var6.addSuppressed(var5);
            }
         }

         throw var6;
      }

      if (result != null) {
         result.close();
      }

      return var3;
   }

   public ClasspathScanner exclude(String... excludePackage) {
      this.classGraph.rejectPackages(excludePackage);
      return this;
   }
}
