package org.gaertner.annotationprocessor;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import org.gaertner.annotationprocessor.puml.model.classdiagram.ClassDiagram;
import org.gaertner.annotationprocessor.puml.model.classdiagram.elements.Class;
import org.gaertner.annotationprocessor.puml.model.classdiagram.elements.Field;
import org.gaertner.annotationprocessor.util.TeeWriter;
import org.gaertner.annotations.UmlClassDiagram;

import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;

@SupportedAnnotationTypes({"org.gaertner.annotations.UmlClassDiagram"})
public class ClassDiagramProcessor extends AbstractProcessor {

	private Messager messager = null;
	private Map<String, ClassDiagram> classDiagrams = new HashMap<>();
	private Filer filer = null;

	@Override
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		if (roundEnv.processingOver()) return false;
		buildDiagramModels(roundEnv);
		writeDiagrams();
		return true;
	}

	private void writeDiagrams() {
		for (Entry<String, ClassDiagram> entry : classDiagrams.entrySet()) {
			String filename = entry.getKey();
			Writer pumlFileWriter = null;
			OutputStream svgOutputStreams = null;
			try {
				messager.printMessage(Kind.NOTE, "writing Diagram File " + filename);
				FileObject pumlResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "puml", filename + ".puml");
				FileObject svgResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "puml", filename + ".svg");

				StringWriter stringWriter = new StringWriter();
				pumlFileWriter = new TeeWriter(stringWriter, pumlResource.openWriter());

				entry.getValue().write(pumlFileWriter);
				pumlFileWriter.flush();

				SourceStringReader reader = new SourceStringReader(stringWriter.toString());
				svgOutputStreams = svgResource.openOutputStream();
				reader.outputImage(svgOutputStreams, new FileFormatOption(FileFormat.SVG));

			} catch (IOException e) {
				messager.printMessage(Kind.ERROR, "Error creating " + filename + ":\n" + e.getMessage());
			} finally {
				if (pumlFileWriter != null) {
					try {
						pumlFileWriter.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					pumlFileWriter = null;
				}
				if (svgOutputStreams != null) {
					try {
						svgOutputStreams.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					svgOutputStreams = null;
				}
			}
		}
	}

	private void buildDiagramModels(RoundEnvironment roundEnv) {
		for (Element element : roundEnv.getElementsAnnotatedWith(UmlClassDiagram.class)) {
			if (element instanceof TypeElement) {
				TypeElement typeElement = (TypeElement) element;
				ClassDiagram diagram = getOrCreateClassDiagram(typeElement);
				diagram.addClass(createClassElement(typeElement));
			}
		}
	}

	private Class createClassElement(TypeElement typeElement) {
		Name qualifiedName = typeElement.getQualifiedName();
		Class clazz = new Class(qualifiedName.toString());
		List<? extends Element> elements = typeElement.getEnclosedElements();
		for (Element element : elements) {
			if (element instanceof VariableElement) {
				VariableElement variable = (VariableElement) element;
				Field field = new Field(variable.asType().toString(), variable.getSimpleName().toString());
				clazz.addField(field);
			}
		}
		
		TypeMirror superclass = typeElement.getSuperclass();
		if (superclass instanceof DeclaredType) {
			DeclaredType declared = (DeclaredType) superclass;
			TypeElement element = (TypeElement) declared.asElement();
			Name qualifiedSupertypeName = element.getQualifiedName();
			clazz.setExtendsType(qualifiedSupertypeName.toString());
		}
		
		return clazz;
	}

	private ClassDiagram getOrCreateClassDiagram(TypeElement typeElement) {
		UmlClassDiagram annotation = typeElement.getAnnotation(UmlClassDiagram.class);
		String filename = annotation.filename();
		ClassDiagram classDiagram = classDiagrams.get(filename);
		if (classDiagram == null) {
			classDiagram = new ClassDiagram();
			classDiagrams.put(filename, classDiagram);
		}
		return classDiagram;
	}

	@Override
	public synchronized void init(ProcessingEnvironment processingEnv) {
		super.init(processingEnv);
		messager  = processingEnv.getMessager();
		filer  = processingEnv.getFiler();
	}

	@Override
	public Set<String> getSupportedAnnotationTypes() {
		Set<String> annotations = new HashSet<>();
		annotations.add(UmlClassDiagram.class.getCanonicalName());
		return annotations ;
	}

	@Override
	public SourceVersion getSupportedSourceVersion() {
		return SourceVersion.latestSupported();
	}
}

@@ -2345,37 +2345,38 @@
 tandardLocation.
-CLASS
+SOURCE
 _OUTPUT, %22puml%22,
@@ -2361,36 +2361,32 @@
 SOURCE_OUTPUT, %22
-puml
 %22, filename + %22.
@@ -2464,13 +2464,14 @@
 ion.
-CLASS
+SOURCE
 _OUT
@@ -2476,20 +2476,16 @@
 UTPUT, %22
-puml
 %22, filen

output to SOURCE_OUTPUT