package shortbread;

import com.google.auto.service.AutoService;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
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.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;

@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"shortbread.Shortcut"})
public class ShortcutProcessor extends AbstractProcessor {

    private boolean processed;

    @Override
    public boolean process(final Set<? extends TypeElement> set, final RoundEnvironment roundEnvironment) {
        if (processed) {
            return true;
        } else {
            processed = true;
        }

        List<ShortcutAnnotatedElement> annotatedElements = new ArrayList<>();

        for (final Element element : roundEnvironment.getElementsAnnotatedWith(Shortcut.class)) {
            if (element.getKind() == ElementKind.CLASS) {
                final TypeElement typeElement = (TypeElement) element;
                if (!isSubtypeOfActivity(typeElement.asType())) {
                    error(element, "Only activities can be annotated with @%s", Shortcut.class.getSimpleName());
                    return true;
                }

                annotatedElements.add(new ShortcutAnnotatedClass(typeElement));
            } else if (element.getKind() == ElementKind.METHOD) {
                final ExecutableElement executableElement = (ExecutableElement) element;

                final Element enclosingElement = executableElement.getEnclosingElement();
                if (!isSubtypeOfActivity(enclosingElement.asType())) {
                    error(element, "Methods annotated with @%s must be part of activities", Shortcut.class.getSimpleName());
                    return true;
                }

                if (!executableElement.getModifiers().contains(Modifier.PUBLIC)) {
                    error(element, "Methods annotated with @%s must be public", Shortcut.class.getSimpleName());
                    return true;
                }

                if (executableElement.getParameters().size() > 0) {
                    error(element, "Methods annotated with @%s can't have parameters", Shortcut.class.getSimpleName());
                    return true;
                }

                annotatedElements.add(new ShortcutAnnotatedMethod(executableElement));
            } else {
                error(element, "Only classes and methods can be annotated with @", Shortcut.class.getSimpleName());
                return true;
            }
        }

        new CodeGenerator(processingEnv.getFiler(), annotatedElements).generate();
        return false;
    }

    private void error(Element element, String message, Object... args) {
        if (args.length > 0) {
            message = String.format(message, args);
        }

        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element);
    }

    private boolean isSubtypeOfActivity(TypeMirror typeMirror) {
        if ("android.app.Activity".equals(typeMirror.toString())) {
            return true;
        }

        if (typeMirror.getKind() != TypeKind.DECLARED) {
            return false;
        }

        DeclaredType declaredType = (DeclaredType) typeMirror;
        Element element = declaredType.asElement();
        if (!(element instanceof TypeElement)) {
            return false;
        }

        TypeElement typeElement = (TypeElement) element;
        TypeMirror superType = typeElement.getSuperclass();
        return isSubtypeOfActivity(superType);
    }
}