package org.jusecase.builders.generator;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class PropertiesResolver {
    private final Class<?> entityClass;

    public PropertiesResolver(Class<?> entityClass) {
        this.entityClass = entityClass;
    }

    public Collection<Property> resolveProperties() {
        SortedSet<Property> properties = new TreeSet<>();
        addProperties(entityClass, properties);
        return properties;
    }

    private void addProperties(Class<?> clazz, Collection<Property> properties) {
        if (clazz != null && clazz != Object.class) {
            addPublicFields(clazz, properties);
            addSetterMethods(clazz, properties);

            addProperties(clazz.getSuperclass(), properties);
        }
    }

    private void addPublicFields(Class<?> clazz, Collection<Property> properties) {
        for (Field field : clazz.getDeclaredFields()) {
            if (isSuitableField(field)) {
                Property property = new Property();
                property.name = field.getName();
                property.type = field.getType();
                property.isSetter = false;
                properties.add(property);
            }
        }
    }

    private void addSetterMethods(Class<?> clazz, Collection<Property> properties) {
        for (Method method : clazz.getDeclaredMethods()) {
            if (isSuitableMethod(method)) {
                Property property = new Property();
                property.name = method.getName().substring(3);
                property.type = method.getParameterTypes()[0];
                property.isSetter = true;
                properties.add(property);
            }
        }
    }

    private boolean isSuitableField(Field field) {
        return Modifier.isPublic(field.getModifiers()) &&
              !Modifier.isStatic(field.getModifiers()) &&
              !Modifier.isFinal(field.getModifiers());
    }

    private boolean isSuitableMethod(Method method) {
        return Modifier.isPublic(method.getModifiers()) &&
                method.getReturnType().equals(void.class) &&
                method.getParameterTypes().length == 1 &&
                method.getName().matches("^set[A-Z].*");
    }

    public static class Property implements Comparable<Property> {
        public String name;
        public Class<?> type;
        public boolean isSetter;

        public String getTypeString() {
            String typeName;

            if (type.getName().startsWith("[L")) {
                typeName = type.getName().substring(2, type.getName().length() - 1) + "[]";
            } else if (type.getPackage() == null || "java.lang".equals(type.getPackage().getName())) {
                typeName = type.getSimpleName();
            } else {
                typeName = type.getName();
            }

            return correctTypeName(typeName);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Property property = (Property) o;

            return name.equals(property.name) && type.equals(property.type);
        }

        @Override
        public int hashCode() {
            int result = name.hashCode();
            result = 31 * result + type.hashCode();
            return result;
        }

        @Override
        public int compareTo(Property o) {
            int result = name.compareTo(o.name);
            if (result == 0) {
                result = type.getName().compareTo(o.type.getName());
            }
            return result;
        }

        private String correctTypeName(String typeName) {
            typeName = correctNestedTypeName(typeName);
            typeName = correctArrayTypeName(typeName);
            return typeName;
        }

        private String correctArrayTypeName(String typeName) {
            if (typeName.endsWith("[]")) {
                typeName = typeName.substring(0, typeName.length() - 2) + " ...";
            }
            return typeName;
        }

        private String correctNestedTypeName(String typeName) {
            return typeName.replace('$', '.');
        }

        public String nameStartingWithUppercase() {
            return ("" + name.charAt(0)).toUpperCase() +  name.substring(1);
        }
    }
}

@@ -135,16 +135,47 @@
 difier;%0A
+import java.lang.reflect.Type;%0A
 import j
@@ -2409,24 +2409,25 @@
 blic String 
+ 
 name;%0A      
@@ -2435,24 +2435,23 @@
  public 
-Class%3C?%3E
+Type   
  type;%0A 
@@ -2569,24 +2569,66 @@
     if (
+isArray(type)) %7B%0A              typeName = 
 type.get
 Name().s
@@ -2623,31 +2623,65 @@
 .get
+Type
 Name()
-.startsWith(%22%5BL%22)
+;%0A            %7D else if ( isJavaLangType(type) 
 ) %7B%0A
@@ -2710,36 +2710,91 @@
 me =
+ ((Class%3C?%3E)
  type
+)
 .get
-Name().substring(2,
+SimpleName();%0A            %7D else %7B%0A                typeName =
  typ
@@ -2802,49 +2802,100 @@
 .get
+Type
 Name()
-.length() - 1) + %22%5B%5D%22;%0A
+;%0A            %7D%0A%0A            return correctTypeName(typeName);%0A   
     
+ %7D%0A%0A
         
 %7D el
@@ -2894,23 +2894,181 @@
     
-%7D else if (type
+private static boolean isJavaLangType(Type type) %7B%0A            if (type instanceof Class%3C?%3E) %7B%0A                Class%3C?%3E clazz = (Class%3C?%3E)type;%0A                 return clazz
 .get
@@ -3107,20 +3107,21 @@
 .equals(
-type
+clazz
 .getPack
@@ -3136,19 +3136,57 @@
 tName())
-) %7B
+;%0A            %7D%0A            return false;
 %0A       
@@ -3182,32 +3182,35 @@
 se;%0A        
+%7D%0A%0A
         
 typeName = t
@@ -3201,101 +3201,183 @@
     
-typeName = type.getSimpleName();%0A            %7D else %7B%0A                typeName = type.getName
+private static boolean isArray( Type type ) %7B%0A            if (type instanceof Class%3C?%3E) %7B%0A                Class%3C?%3E clazz = (Class%3C?%3E)type;%0A                return clazz.isArray
 ();%0A
@@ -3382,33 +3382,32 @@
 ;%0A            %7D%0A
-%0A
             retu
@@ -3413,33 +3413,13 @@
 urn 
-correctTypeName(typeName)
+false
 ;%0A  
@@ -4086,24 +4086,28 @@
 t = type.get
+Type
 Name().compa
@@ -4117,24 +4117,28 @@
 o(o.type.get
+Type
 Name());%0A   

migrate Property.type from Class to Type