/*
 *  Copyright 2016 Fabio Collini.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.cosenonjaviste.daggermock;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.inject.Provider;

class ReflectUtils {
    public static Object buildComponent(Object builder) {
        try {
            return builder.getClass().getMethod("build").invoke(builder);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String toCamelCase(String str) {
        return str.substring(0, 1).toLowerCase() + str.substring(1);
    }

    public static Method getComponentSetterMethod(Object builder, Object component) throws NoSuchMethodException {
        Class<?> daggerComponentClass = component.getClass();
        String daggerComponentName = daggerComponentClass.getSimpleName();
        String componentName = daggerComponentName.replace("Dagger", "");
        String setterName = toCamelCase(componentName);
        return builder.getClass().getMethod(setterName, daggerComponentClass.getInterfaces()[0]);
    }

    public static Method getSetterMethod(Object builder, Object module) throws NoSuchMethodException {
        Class<?> moduleClass = module.getClass();
        while (true) {
            try {
                String moduleName = moduleClass.getSimpleName();
                String setterName = toCamelCase(moduleName);
                return builder.getClass().getMethod(setterName, moduleClass);
            } catch (NoSuchMethodException e) {
                moduleClass = moduleClass.getSuperclass();
                if (moduleClass.equals(Object.class)) {
                    throw e;
                }
            }
        }
    }

    public static void extractFields(Object target, Map<ObjectId, Provider> map) {
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                final Object value = field.get(target);
                if (value != null) {
                    map.put(new ObjectId(field), new Provider() {
                        @Override public Object get() {
                            return value;
                        }
                    });
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Error accessing field " + field, e);
            }
        }
    }

    public static List<Field> extractAnnotatedFields(Object target, Class<? extends Annotation> annotationClass) {
        List<Field> ret = new ArrayList<>();
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(annotationClass)) {
                ret.add(field);
            }
        }
        return ret;
    }

    public static Method getMethodReturning(Class<?> declaringClass, Class<?> returnClass) {
        Method[] methods = declaringClass.getMethods();
        for (Method method : methods) {
            if (method.getReturnType().equals(returnClass)) {
                return method;
            }
        }
        return null;
    }

    public static Object invokeMethod(Object component, Method m) {
        try {
            return m.invoke(component);
        } catch (Exception e) {
            throw new RuntimeException("Error invoking method " + m, e);
        }
    }

    public static void setFieldValue(Object target, Field field, Object obj) {
        try {
            field.set(target, obj);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Error setting field " + field, e);
        }
    }

    public static Class<?> getDaggerComponentClass(Class componentClass) throws ClassNotFoundException {
        String packageName = componentClass.getPackage().getName();
        if (componentClass.isMemberClass()) {
            componentClass.getDeclaringClass();
            String declaringClass = componentClass.getDeclaringClass().getSimpleName();
            return Class.forName(packageName + ".Dagger" + declaringClass + "_" + componentClass.getSimpleName());
        } else {
            return Class.forName(packageName + ".Dagger" + componentClass.getSimpleName());
        }
    }
}

@@ -634,16 +634,40 @@
 rmock;%0A%0A
+import org.junit.Rule;%0A%0A
 import j
@@ -2556,32 +2556,95 @@
 eld : fields) %7B%0A
+            if (field.getAnnotation(Rule.class) == null) %7B%0A    
             fiel
@@ -2670,38 +2670,46 @@
 e);%0A            
+    
 try %7B%0A
+    
                 
@@ -2760,24 +2760,28 @@
             
+    
 if (value !=
@@ -2781,32 +2781,36 @@
 alue != null) %7B%0A
+    
                 
@@ -2883,16 +2883,20 @@
         
+    
 @Overrid
@@ -2915,24 +2915,28 @@
 ect get() %7B%0A
+    
             
@@ -2981,34 +2981,41 @@
                 
-%7D%0A
+    %7D%0A   
                 
@@ -3018,16 +3018,17 @@
         
+ 
 %7D);%0A    
@@ -3031,34 +3031,41 @@
                 
-%7D%0A
+    %7D%0A   
             %7D ca
@@ -3052,32 +3052,33 @@
 %0A               
+ 
 %7D catch (Illegal
@@ -3090,32 +3090,36 @@
 sException e) %7B%0A
+    
                 
@@ -3175,32 +3175,50 @@
  %22 + field, e);%0A
+                %7D%0A
             %7D%0A  

Check to ignore rule fields