/*
 * Copyright (c) 2016, Mazen Kotb, mazenkotb@gmail.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
package xyz.mkotb.configapi.internal;

import sun.reflect.ReflectionFactory;
import xyz.mkotb.configapi.internal.naming.NamingStrategies;
import xyz.mkotb.configapi.internal.naming.NamingStrategy;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

public final class InternalsHelper {
    private InternalsHelper() {
    }

    public static Class typeOf(Field field, int index) {
        Type genericType = field.getGenericType();

        if (genericType instanceof ParameterizedType) {
            return (Class) ((ParameterizedType) genericType).getActualTypeArguments()[index];
        }

        return Object.class;
    }

    /*
     * Creates an instance of the class without calling it's constructor.
     * clazz must implement java.io.Serializable
     */
    public static <T> T newInstanceWithoutInit(Class<T> clazz) {
        try {
            ReflectionFactory rf =
                    ReflectionFactory.getReflectionFactory();
            Constructor objDef = Object.class.getDeclaredConstructor();
            Constructor intConstr = rf.newConstructorForSerialization(clazz, objDef);

            return clazz.cast(intConstr.newInstance());
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException("Cannot create object", e);
        }
    }

    public static <T> T newInstance(Class<T> classOf) {
        try {
            return classOf.newInstance();
        } catch (InstantiationException | IllegalAccessException ex) {
            return null;
        }
    }

    public static <T> Map<String, Object> mapFieldsOf(T obj) {
        return mapFieldsOf(obj, NamingStrategies.from("dummy"));
    }

    public static <T> Map<String, Object> mapFieldsOf(T obj, NamingStrategy strategy) {
        Map<String, Object> fieldMap = new HashMap<>();
        Field[] fields = obj.getClass().getFields();

        for (Field field : fields) {
            fieldMap.put(strategy.rename(field.getName()), getField(field, obj));
        }

        return fieldMap;
    }

    public static Field staticFieldFor(Class<?> clazz, String name) {
        try {
            return clazz.getDeclaredField(name);
        } catch (NoSuchFieldException ex) {
            return null;
        }
    }

    public static Field fieldFor(Object object, String name) {
        try {
            return object.getClass().getDeclaredField(name);
        } catch (NoSuchFieldException ignored) {
            return null;
        }
    }

    public static <T> T getField(Field field, Object instance) {
        try {
            field.setAccessible(true);
            return (T) field.get(instance);
        } catch (IllegalAccessException ex) {
            return null;
        }
    }

    public static boolean setField(String fieldName, Object instance, Object value) {
        Field field = fieldFor(instance, fieldName);
        return field != null && setField(field, instance, value);
    }

    public static boolean setField(Field field, Object instance, Object value) {
        try {
            field.setAccessible(true);
            field.set(instance, value);
            return true;
        } catch (IllegalAccessException ex) {
            return false;
        }
    }
}

@@ -1182,45 +1182,178 @@
 ap;%0A
-%0Apublic final class InternalsHelper %7B
+import java.util.concurrent.ConcurrentHashMap;%0A%0Apublic final class InternalsHelper %7B%0A    private static final Map%3CString, Field%3E FIELD_CACHE = new ConcurrentHashMap%3C%3E();%0A
 %0A   
@@ -3224,267 +3224,748 @@
     
-return clazz.getDeclaredField(name);%0A        %7D catch (NoSuchFieldException ex) %7B%0A            return null;%0A        %7D%0A    %7D%0A%0A    public static Field fieldFor(Object object, String name) %7B%0A        try %7B%0A            return object.getClass().getDeclaredField(name)
+String id = fieldIdentifier(clazz, name);%0A%0A            if (FIELD_CACHE.containsKey(id)) %7B%0A                return FIELD_CACHE.get(id);%0A            %7D%0A%0A            Field field = clazz.getDeclaredField(name);%0A%0A            FIELD_CACHE.put(id, field);%0A            return field;%0A        %7D catch (NoSuchFieldException ex) %7B%0A            return null;%0A        %7D%0A    %7D%0A%0A    public static Field fieldFor(Object object, String name) %7B%0A        try %7B%0A            String id = fieldIdentifier(object.getClass(), name);%0A%0A            if (FIELD_CACHE.containsKey(id)) %7B%0A                return FIELD_CACHE.get(id);%0A            %7D%0A            Field field = object.getClass().getDeclaredField(name);%0A%0A            FIELD_CACHE.put(id, field);%0A            return field
 ;%0A  
@@ -4801,11 +4801,284 @@
  %7D%0A    %7D
+%0A%0A    private static String fieldIdentifier(Class%3C?%3E cls, String fieldName) %7B%0A        return cls.getName() + %22%7C%7C%22 + fieldName;%0A    %7D%0A%0A    private static String fieldIdentifier(Field field) %7B%0A        return field.getDeclaringClass().getName() + %22%7C%7C%22 + field.getName();%0A    %7D
 %0A%7D%0A

Add field caching