package io.yawp.repository.models;

import io.yawp.commons.utils.ReflectionUtils;
import io.yawp.commons.utils.kind.KindResolver;
import io.yawp.repository.IdRef;
import io.yawp.repository.annotations.Id;
import io.yawp.repository.annotations.ParentId;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

public class ObjectModel {

    private Class<?> clazz;

    public ObjectModel(Class<?> clazz) {
        this.clazz = clazz;
    }

    public String getKind() {
        return KindResolver.getKindFromClass(clazz);
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public Field getIdField() {
        return ReflectionUtils.getFieldWithAnnotation(clazz, Id.class);
    }

    public Field getParentField() {
        return ReflectionUtils.getFieldWithAnnotation(clazz, ParentId.class);
    }

    public String getIdFieldName() {
        return getIdField().getName();
    }

    public boolean isIdRef(Field field) {
        return IdRef.class.isAssignableFrom(field.getType());
    }

    public Class<?> getParentClazz() {
        Field parentField = getParentField();
        if (parentField == null) {
            return null;
        }
        return ReflectionUtils.getIdRefEndpointClazz(parentField);
    }

    public boolean hasParent() {
        return getParentField() != null;
    }

    public ObjectModel getParentModel() {
        return new ObjectModel(getParentClazz());
    }

    public Class<?> getAncestorClazz(int ancestor) {
        Class<?> parentClazz = clazz;
        for (int i = 0; i <= ancestor; i++) {
            ObjectModel model = new ObjectModel(parentClazz);
            parentClazz = model.getParentClazz();
        }
        return parentClazz;
    }

    public int getAncestorNumber(Class<?> ancestorClazz) {
        if (clazz.equals(ancestorClazz)) {
            return -1;
        }

        Class<?> parentClazz = getParentClazz();
        int ancestorNumber = 0;

        while (parentClazz != null && !parentClazz.equals(ancestorClazz)) {
            ObjectModel model = new ObjectModel(parentClazz);
            parentClazz = model.getParentClazz();
            ancestorNumber++;
        }

        if (parentClazz == null) {
            return -1;
        }

        return ancestorNumber;
    }

    public boolean isAncestor(Class<?> ancestorClazz) {
        return getAncestorNumber(ancestorClazz) != -1;
    }

    public List<FieldModel> getFieldModels() {
        List<Field> fields = ReflectionUtils.getFieldsRecursively(clazz);

        List<FieldModel> fieldModels = new ArrayList<>();

        for (Field field : fields) {
            fieldModels.add(new FieldModel(field));
        }

        return fieldModels;
    }

    public FieldModel getFieldModel(String fieldName) {
        return new FieldModel(ReflectionUtils.getFieldRecursively(clazz, fieldName));
    }

    public boolean isIdShuffled() {
        return getIdField().getAnnotation(Id.class).shuffle();
    }

    @SuppressWarnings("unchecked")
    public <T> T createInstance() {
        try {
            Constructor<T> defaultConstructor = (Constructor<T>) clazz.getDeclaredConstructor(new Class<?>[]{});
            defaultConstructor.setAccessible(true);
            return defaultConstructor.newInstance();

        } catch (InvocationTargetException e) {
            throw new RuntimeException("An exception was thrown when calling the default constructor of the class " + clazz.getSimpleName()
                    + ": ", e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("The class " + clazz.getSimpleName()
                    + " must have a default constructor and cannot be an non-static inner class.", e);
        } catch (InstantiationException e) {
            throw new RuntimeException("The class " + clazz.getSimpleName() + " must cannot be abstract.", e);
        } catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
            throw new RuntimeException("Unexpected error: ", e);
        }
    }
}

@@ -245,16 +245,54 @@
 arentId;
+%0Aimport sun.reflect.ReflectionFactory;
 %0A%0Aimport
@@ -3112,24 +3112,272 @@
 e();%0A    %7D%0A%0A
+    public %3CT%3E T createInstance() %7B%0A        try %7B%0A            return createInstanceWithDefaultConstructor();%0A        %7D catch (NoSuchMethodException e) %7B%0A            return createInstanceWithoutDefaultConstructor((Class%3CT%3E) clazz);%0A        %7D%0A    %7D%0A%0A
     @Suppres
@@ -3400,29 +3400,30 @@
 cked%22)%0A    p
-ublic
+rivate
  %3CT%3E T creat
@@ -3423,34 +3423,85 @@
 T createInstance
-()
+WithDefaultConstructor() throws NoSuchMethodException
  %7B%0A        try %7B
@@ -3944,231 +3944,8 @@
 e);%0A
-        %7D catch (NoSuchMethodException e) %7B%0A            throw new RuntimeException(%22The class %22 + clazz.getSimpleName()%0A                    + %22 must have a default constructor and cannot be an non-static inner class.%22, e);%0A
     
@@ -4180,32 +4180,32 @@
 yException e) %7B%0A
-
             thro
@@ -4268,11 +4268,843 @@
  %7D%0A    %7D
+%0A%0A    private %3CT%3E T createInstanceWithoutDefaultConstructor(Class%3CT%3E clazz) %7B%0A        return createInstanceWithoutDefaultConstructor(clazz, Object.class);%0A    %7D%0A%0A    private %3CT%3E T createInstanceWithoutDefaultConstructor(Class%3CT%3E clazz,%0A                                                          Class%3C? super T%3E parent) %7B%0A        try %7B%0A            ReflectionFactory rf = ReflectionFactory.getReflectionFactory();%0A            Constructor objDef = parent.getDeclaredConstructor();%0A            Constructor intConstr = rf.newConstructorForSerialization(clazz, objDef);%0A            return clazz.cast(intConstr.newInstance());%0A        %7D catch (RuntimeException e) %7B%0A            throw e;%0A        %7D catch (Exception e) %7B%0A            throw new IllegalStateException(%22Cannot createInstanceWithoutDefaultConstructor object%22, e);%0A        %7D%0A    %7D
 %0A%7D%0A

create instances without default constructor