package com.idamobile.dagger.helper;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.inject.Inject;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import javax.tools.JavaFileManager;
import javax.tools.StandardLocation;

import dagger.Module;
import dagger.Provides;

@SupportedAnnotationTypes("dagger.Module")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class DaggerModulesProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            System.out.println("dagger-helper: searching for modules");
            Set<String> keepNames = new HashSet<String>();
            for (Element elem : roundEnv.getElementsAnnotatedWith(Inject.class)) {
                if (elem.getKind() == ElementKind.FIELD) {
                    TypeMirror typeMirror = elem.asType();
                    addIfNeeded(typeMirror, keepNames);
                    addEnclosingClassName(elem, keepNames);
                } else if (elem.getKind() == ElementKind.CONSTRUCTOR
                    || elem.getKind() == ElementKind.METHOD
                    || elem.getKind() == ElementKind.PARAMETER) {
                    addEnclosingClassName(elem, keepNames);
                }
            }
            for (Element elem : roundEnv.getElementsAnnotatedWith(Provides.class)) {
                if (elem.getKind() == ElementKind.METHOD) {
                    ExecutableElement executable = (ExecutableElement) elem;
                    TypeMirror returnType = executable.getReturnType();
                    addIfNeeded(returnType, keepNames);
                }
            }
            for (Element elem : roundEnv.getElementsAnnotatedWith(Module.class)) {
                Module module = elem.getAnnotation(Module.class);
                try {
                    module.injects();
                } catch (MirroredTypesException e) {
                    List<? extends TypeMirror> typeMirrors = e.getTypeMirrors();
                    if (typeMirrors != null && !typeMirrors.isEmpty()) {
                        for (TypeMirror mirror : typeMirrors) {
                            addIfNeeded(mirror, keepNames);
                        }
                    }
                }
            }
            try {
                createProGuardFile(keepNames);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    private void addIfNeeded(TypeMirror type, Set<String> keepNames) {
        TypeParams params = new TypeParams(type);
        addIfNeeded(params, keepNames);

        Types typeUtils = processingEnv.getTypeUtils();
        TypeElement element = (TypeElement) typeUtils.asElement(type);
        while (element != null && !element.toString().equals(Object.class.getName())) {
            addIfNeeded(new TypeParams(element.asType()), keepNames);
            TypeMirror superclass = element.getSuperclass();
            if (superclass != null) {
                element = (TypeElement) typeUtils.asElement(superclass);
            } else {
                element = null;
            }
        }
    }

    private void addIfNeeded(TypeParams type, Set<String> keepNames) {
        if (type.isKeepRequaried()) {
            if (keepNames.add(type.getName())) {
                System.out.println("dagger-helper: found new dependent type " + type.getName());
            }
        }
        for (TypeParams params : type.getGenerics()) {
            addIfNeeded(params, keepNames);
        }
    }

    private void addEnclosingClassName(Element elem, Set<String> keepNames) {
        Element enclosingClass = elem.getEnclosingElement();
        for (; enclosingClass != null && enclosingClass.getKind() != ElementKind.CLASS; enclosingClass = elem.getEnclosingElement()) ;
        if (enclosingClass != null) {
            addIfNeeded(enclosingClass.asType(), keepNames);
        }
    }

    private void createProGuardFile(Set<String> keepNames) throws IOException {
        JavaFileManager.Location location = StandardLocation.SOURCE_OUTPUT;
        final String relativeName = UUID.randomUUID().toString();
        FileObject resource = processingEnv.getFiler().createResource(location, "", relativeName);

        final String name = resource.getName();
        final int targetIndexOf = name.indexOf("target");
        final int buildIndexOf = name.indexOf("build");
        final String basePath;
        if(targetIndexOf > 0) {
            basePath = name.substring(0, targetIndexOf);
        } else if(buildIndexOf > 0) {
            basePath = name.substring(0, buildIndexOf);
        } else {
            System.out.println("dagger-helper: COULD NOT GET BASE PATH FOR PATH: ");
            System.out.println(name);
            basePath = "";
        }
        File file = new File(basePath, "dagger-proguard-keepnames.cfg");
        if (file.exists() && keepNames.isEmpty()) {
            return;
        }

        System.out.println("dagger-helper: generating output file " + file.getAbsolutePath());
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
            writer.write("# do not modify that file, it's rewriting each build by dagger-proguard-helper and your changes will be removed");
            writer.newLine();
            for (String className : keepNames) {
                writer.write("-keepnames class ");
                writer.write(className);
                writer.newLine();
            }
            writer.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                }
            }
        }
    }
}
@@ -42,241 +42,45 @@
 ort 
-java.io.BufferedWriter;%0Aimport java.io.File;%0Aimport java.io.FileOutputStream;%0Aimport java.io.IOException;%0Aimport java.io.OutputStreamWriter;%0Aimport java.util.HashSet;%0Aimport java.util.List;%0Aimport java.util.Set;%0Aimport java.util.UUID
+dagger.Module;%0Aimport dagger.Provides
 ;%0A%0Ai
@@ -792,53 +792,44 @@
 on;%0A
-%0A
 import 
-dagger.Module;%0Aimport dagger.Provides
+java.io.*;%0Aimport java.util.*
 ;%0A%0A@
@@ -1665,32 +1665,36 @@
                 
+    
 %7C%7C elem.getKind(
@@ -1717,16 +1717,20 @@
 .METHOD%0A
+    
         
@@ -4453,17 +4453,16 @@
 ement())
- 
 ;%0A      
@@ -5096,16 +5096,17 @@
       if
+ 
 (targetI
@@ -5193,16 +5193,17 @@
  else if
+ 
 (buildIn
@@ -5304,79 +5304,21 @@
     
-System.out.println(%22dagger-helper: COULD NOT GET BASE PATH FOR PATH: %22)
+basePath = %22%22
 ;%0A  
@@ -5350,40 +5350,70 @@
 tln(
-name);%0A           
+%22dagger-helper: could not get
  base
-P
+ p
 ath 
-= %22%22
+of: %5C%22%22 + name + %22%5C%22%22)
 ;%0A  
@@ -5948,16 +5948,19 @@
 writing 
+on 
 each bui
@@ -6501,33 +6501,39 @@
 ch (IOException 
-e
+ignored
 ) %7B%0A            

format code