package lib.morkim.mfw.usecase;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;

import lib.morkim.mfw.util.GenericsUtils;

public class UseCaseCreator<T extends UseCase> {

    private T task;
    private Class<T> taskClass;

    public UseCaseCreator() {

    }

    public UseCaseCreator<T> create(Class<T> taskClass) {

        this.taskClass = taskClass;

        try {
            task = taskClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return this;
    }

    public UseCaseCreator<T> add(Class<T> taskClass) {

        this.taskClass = taskClass;

        try {
            task = taskClass.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return this;
    }

    public <d extends UseCaseDependencies> T with(d dependenciesImpl) {

        Map<Class, Field> fieldTypes = new HashMap<>();

        Class cls = taskClass;

        do {
            for (Field field : cls.getDeclaredFields()) {
                addAnnotatedField(fieldTypes, field, GenericsUtils.resolveActualTypeArgs(taskClass, cls));
            }
            cls = cls.getSuperclass();
        } while (!cls.isInstance(Object.class));

        for (Method method : dependenciesImpl.getClass().getDeclaredMethods()) {

            Field field = fieldTypes.get(method.getReturnType());
            if (field != null)
                try {
                    field.setAccessible(true);
                    field.set(task, method.invoke(dependenciesImpl));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
        }

        return task;
    }

    private boolean isFieldGeneric(Field field) {
        return !field.getGenericType().equals(field.getType());
    }

    private void addAnnotatedField(Map<Class, Field> fieldTypes, Field field, Type[] types) {

        if (field.isAnnotationPresent(TaskDependency.class)) {
            Type fieldType = field.getType();

            TypeVariable<? extends Class<?>>[] typeParams;
            if (isFieldGeneric(field)) {
                typeParams = field.getDeclaringClass().getTypeParameters();
                for (int i = 0; i < typeParams.length; i++)
                    if (typeParams[i].equals(field.getGenericType())) {
                        fieldType = types[i];
                        break;
                    }
            }

            Class<?> cls = fieldType instanceof ParameterizedType ?
                    (Class<?>) ((ParameterizedType) fieldType).getRawType() :
                    (Class<?>) fieldType;

            fieldTypes.put(cls, field);
        }
    }
}

@@ -1624,24 +1624,165 @@
 t.class));%0A%0A
+        Class%3C?%3E dependenciesImplClass = dependenciesImpl.getClass();%0A%0A        while (!dependenciesImplClass.isInstance(Object.class)) %7B%0A    
         for 
@@ -1814,27 +1814,21 @@
 ciesImpl
-.get
 Class
-()
 .getDecl
@@ -1857,16 +1857,20 @@
         
+ 
+   
 Field fi
@@ -1919,32 +1919,36 @@
 ));%0A            
+    
 if (field != nul
@@ -1958,38 +1958,46 @@
                 
+  
+  
 try %7B%0A
+    
                 
@@ -2039,32 +2039,36 @@
                 
+   
+ 
 field.set(task, 
@@ -2109,32 +2109,36 @@
                 
+    
 %7D catch (Illegal
@@ -2170,32 +2170,36 @@
                 
+   
+ 
 e.printStackTrac
@@ -2195,32 +2195,36 @@
 ntStackTrace();%0A
+    
                 
@@ -2275,32 +2275,36 @@
                 
+    
 e.printStackTrac
@@ -2316,33 +2316,127 @@
                 
-%7D
+    %7D%0A            %7D%0A%0A            dependenciesImplClass = dependenciesImplClass.getSuperclass();
 %0A        %7D%0A%0A    

handle dependency implementation hierarchy