package com.gabrielittner.auto.value.util;

import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.common.base.Optional;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import java.lang.annotation.Annotation;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
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.element.VariableElement;
import javax.lang.model.util.Elements;

public final class ElementUtil {

    public static boolean hasMethod(TypeElement cls, boolean isAbstract, boolean isStatic,
            TypeName takes, TypeName returns) {
        return getMethod(cls, isAbstract, isStatic, takes, returns) != null;
    }

    public static ExecutableElement getMethod(TypeElement cls, boolean isAbstract, boolean isStatic,
            TypeName takes, TypeName returns) {
        List<? extends Element> elements = cls.getEnclosedElements();
        for (Element element : elements) {
            if (element.getKind() != ElementKind.METHOD) {
                continue;
            }
            if (isAbstract != element.getModifiers().contains(Modifier.ABSTRACT)) {
                continue;
            }
            if (isStatic != element.getModifiers().contains(Modifier.STATIC)) {
                continue;
            }

            ExecutableElement method = (ExecutableElement) element;
            if (methodTakes(method, takes) && methodReturns(method, returns)) {
                return method;
            }
        }
        return null;
    }

    private static boolean methodTakes(ExecutableElement method, TypeName takes) {
        List<? extends VariableElement> parameters = method.getParameters();
        if (takes != null) {
            if (parameters.size() != 1) {
                return false;
            }
            if (!takes.equals(ClassName.get(parameters.get(0).asType()))) {
                return false;
            }
        } else {
            if (parameters.size() > 0) {
                return false;
            }
        }
        return true;
    }

    private static boolean methodReturns(ExecutableElement method, TypeName returns) {
        return returns.equals(ClassName.get(method.getReturnType()));
    }

    public static boolean typeExists(Elements elements, ClassName className) {
        String name = className.toString();
        return elements.getTypeElement(name) != null;
    }

    public static boolean hasAnnotationWithName(Element element, String simpleName) {
        for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
            String name = mirror.getAnnotationType().asElement().getSimpleName().toString();
            if (simpleName.equals(name)) {
                return true;
            }
        }
        return false;
    }

    public static Object getAnnotationValue(Element element, Class<? extends Annotation> clazz,
            String key) {
        Optional<AnnotationMirror> annotation = MoreElements.getAnnotationMirror(element, clazz);
        if (annotation.isPresent()) {
            return AnnotationMirrors.getAnnotationValue(annotation.get(), key).getValue();
        }
        return null;
    }

    private ElementUtil() {
        throw new AssertionError("No instances.");
    }
}

@@ -675,16 +675,96 @@
 ments;%0A%0A
+import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;%0A%0A
 public f
@@ -818,16 +818,22 @@
 lean has
+Static
 Method(T
@@ -851,58 +851,8 @@
 cls,
- boolean isAbstract, boolean isStatic,%0A           
  Typ
@@ -898,24 +898,30 @@
   return get
+Static
 Method(cls, 
@@ -923,30 +923,8 @@
 cls,
- isAbstract, isStatic,
  tak
@@ -991,16 +991,22 @@
 ment get
+Static
 Method(T
@@ -1025,44 +1025,22 @@
 ls, 
-boolean isAbstract, boolean isStatic
+TypeName takes
 ,%0A  
@@ -1048,32 +1048,16 @@
         
- TypeName takes,
  TypeNam
@@ -1081,39 +1081,28 @@
     
-List%3C? extends 
+for (
 Element
-%3E
  element
 s = 
@@ -1097,19 +1097,18 @@
  element
-s =
+ :
  cls.get
@@ -1129,49 +1129,8 @@
 ts()
-;%0A        for (Element element : elements
 ) %7B%0A
@@ -1244,75 +1244,133 @@
     
-if (isAbstract != element.getModifiers().contains(Modifier.ABSTRACT
+ExecutableElement method = (ExecutableElement) element;%0A            if (methodMatches(method, Modifier.STATIC, takes, returns
 )) %7B
@@ -1382,32 +1382,37 @@
             
-continue
+return method
 ;%0A          
@@ -1427,75 +1427,557 @@
     
+%7D%0A
     
-if (isStatic != e
+    return null;%0A    %7D%0A%0A    public static boolean hasAbstractMethod(Elements elementUtils, TypeElement cls, TypeName takes,%0A            TypeName returns) %7B%0A        return getAbstractMethod(elementUtils, cls, takes, returns) != null;%0A    %7D%0A%0A    public static ExecutableE
 lement
-.getModifiers().contains(Modifier.STATIC
+ getAbstractMethod(Elements elementUtils,%0A            TypeElement cls, TypeName takes, TypeName returns) %7B%0A        for (ExecutableElement method : getLocalAndInheritedMethods(cls, elementUtils)) %7B%0A            if (methodMatches(method, Modifier.ABSTRACT, takes, returns
 )) %7B
@@ -1989,32 +1989,37 @@
             
-continue
+return method
 ;%0A          
@@ -2026,93 +2026,228 @@
   %7D%0A
-%0A
+   
      
+%7D%0A
        
-ExecutableElement method = (ExecutableElement) element;%0A            if (
+ return null;%0A    %7D%0A%0A    private static boolean methodMatches(ExecutableElement method, Modifier modifier,%0A            TypeName takes, TypeName returns) %7B%0A        return hasModifier(method, modifier) && 
 meth
@@ -2268,16 +2268,32 @@
 , takes)
+%0A               
  && meth
@@ -2322,86 +2322,150 @@
 rns)
-) %7B%0A                return method;%0A            %7D%0A        %7D%0A        return null
+;%0A    %7D%0A%0A    static boolean hasModifier(ExecutableElement method, Modifier modifier) %7B%0A        return method.getModifiers().contains(modifier)
 ;%0A  
@@ -2469,32 +2469,24 @@
 %0A    %7D%0A%0A    
-private 
 static boole
@@ -2996,32 +2996,24 @@
 %0A    %7D%0A%0A    
-private 
 static boole

split static and abstract method finders