package cz.ladicek.annDocuGen.annotationProcessor;

import cz.ladicek.annDocuGen.api.Property;

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.tools.Diagnostic;
import java.util.Arrays;
import java.util.Set;

import static cz.ladicek.annDocuGen.annotationProcessor.Elements.declaringClassOf;

@SupportedSourceVersion(SourceVersion.RELEASE_6)
@SupportedAnnotationTypes({"javax.inject.Inject", "cz.ladicek.annDocuGen.api.Property"})
public class DocumentationGenerator extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        try {
            return doProcess(annotations, roundEnv);
        } catch (Exception e) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unexpected error: " + e.getMessage()
                    + " " + Arrays.toString(e.getStackTrace()));
            return false;
        }
    }

    private boolean doProcess(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (annotations.isEmpty()) {
            return false;
        }

        Documentation doc = new Documentation(processingEnv);

        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Collecting documentation for "
                + roundEnv.getRootElements());
        collectDocumentation(roundEnv, doc);

        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Collecting documentation for additional "
                + "encountered classes");
        doc.processEncounteredDependencies();

        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Documentation collected, generating files");
        doc.generateDocumentationFiles();

        return false;
    }

    private void collectDocumentation(RoundEnvironment roundEnv, Documentation doc) {
        collectDocumentationForDependencies(roundEnv, doc);
        collectDocumentationForProperties(roundEnv, doc);
    }

    private void collectDocumentationForDependencies(RoundEnvironment roundEnv, Documentation doc) {
        for (Element annotated : roundEnv.getElementsAnnotatedWith(Inject.class)) {
            if (annotated.getAnnotation(Property.class) != null) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                        "@Inject @Property is invalid. Use @Inject for dependencies and @Property for properties.",
                        annotated);
                continue;
            }

            Element clazz = declaringClassOf(annotated);
            DocumentedClass type = doc.documentClass(clazz);

            if (annotated.getKind() != ElementKind.CONSTRUCTOR && annotated.getKind() != ElementKind.FIELD) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                        "@Inject is only supported for fields and constructors", annotated);
                continue;
            }

            switch (annotated.getKind()) {
                case FIELD:
                    type.addDependency(doc.documentDependency(annotated));
                    break;
                case CONSTRUCTOR:
                    for (Element param : ((ExecutableElement) annotated).getParameters()) {
                        type.addDependency(doc.documentDependency(param));
                    }
                    break;
            }
        }
    }

    private void collectDocumentationForProperties(RoundEnvironment roundEnv, Documentation doc) {
        for (Element annotated : roundEnv.getElementsAnnotatedWith(Property.class)) {
            if (annotated.getAnnotation(Inject.class) != null) {
                // warning is printed out in collectDocumentationForDependencies, no need to do it here too
                continue;
            }

            Element clazz = declaringClassOf(annotated);
            DocumentedClass type = doc.documentClass(clazz);

            if (annotated.getKind() != ElementKind.FIELD) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                        "@Property is only supported for fields", annotated);
                continue;
            }

            type.addProperty(doc.documentProperty(annotated));
        }
    }
}

@@ -143,16 +143,74 @@
 cessor;%0A
+import javax.annotation.processing.ProcessingEnvironment;%0A
 import j
@@ -988,16 +988,230 @@
 essor %7B%0A
+    private Documentation doc;%0A%0A    @Override%0A    public synchronized void init(ProcessingEnvironment processingEnv) %7B%0A        super.init(processingEnv);%0A        this.doc = new Documentation(processingEnv);%0A    %7D%0A%0A
     @Ove
@@ -1740,24 +1740,25 @@
         if (
+!
 annotations.
@@ -1778,104 +1778,8 @@
     
-        return false;%0A        %7D%0A%0A        Documentation doc = new Documentation(processingEnv);%0A%0A
     
@@ -1893,16 +1893,20 @@
         
+    
 + roundE
@@ -1924,24 +1924,28 @@
 lements());%0A
+    
         coll
@@ -1977,17 +1977,72 @@
 , doc);%0A
-%0A
+        %7D%0A%0A        if (roundEnv.processingOver()) %7B%0A    
         
@@ -2163,16 +2163,20 @@
         
+   
+ 
 + %22encou
@@ -2189,24 +2189,28 @@
  classes%22);%0A
+    
         doc.
@@ -2240,24 +2240,28 @@
 dencies();%0A%0A
+    
         proc
@@ -2369,24 +2369,28 @@
 %22);%0A        
+   
+ 
 doc.generate
@@ -2406,24 +2406,34 @@
 tionFiles();
+%0A        %7D
 %0A%0A        re

rewrite DocumentationGenerator to better conform to the lifecycle