package sharin.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

public class PropertyUtils {

    private static final Map<Class<?>, Object> defaultValueMap = new HashMap<Class<?>, Object>();

    static {
        defaultValueMap.put(boolean.class, Boolean.FALSE);
        defaultValueMap.put(byte.class, new Byte((byte) 0));
        defaultValueMap.put(char.class, new Character((char) 0));
        defaultValueMap.put(double.class, new Double(0));
        defaultValueMap.put(float.class, new Float(0));
        defaultValueMap.put(int.class, new Integer(0));
        defaultValueMap.put(long.class, new Long(0));
        defaultValueMap.put(short.class, new Short((short) 0));
    }

    public static Class<?> getSimplePropertyType(Class<?> clazz,
            String simpleName) {

        Class<?> result = null;
        Field field = findField(clazz, simpleName);

        if (field != null) {
            result = field.getType();

        } else {
            Method getter = findGetter(clazz, simpleName);

            if (getter != null) {
                result = getter.getReturnType();
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    public static boolean hasSimpleProperty(Object object, String simpleName) {

        if (object instanceof Map) {
            return ((Map) object).containsKey(simpleName);
        }

        Field field = findField(object.getClass(), simpleName);

        if (field != null) {
            return true;
        }

        return findGetter(object.getClass(), simpleName) != null;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getSimplePropertyValue(Object object, String simpleName) {

        if (object instanceof Map) {
            return (T) ((Map) object).get(simpleName);
        }

        Field field = findField(object.getClass(), simpleName);

        if (field != null) {
            return (T) ReflectUtils.get(field, object);
        }

        Method getter = findGetter(object.getClass(), simpleName);

        if (getter != null) {
            return (T) ReflectUtils.invoke(getter, object);
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    public static void setSimplePropertyValue(Object object, String simpleName,
            Object value) {

        if (object instanceof Map) {
            ((Map) object).put(simpleName, value);
        }

        Field field = findField(object.getClass(), simpleName);

        if (field != null) {

            if (value == null) {
                value = defaultValueMap.get(field.getType());
            }

            ReflectUtils.set(field, object, value);
        }

        Method setter = findSetter(object.getClass(), simpleName, value);

        if (setter != null) {

            if (value == null) {
                value = defaultValueMap.get(setter.getParameterTypes()[0]);
            }

            ReflectUtils.invoke(setter, object, value);
        }
    }

    public static Class<?> getNestedPropertyType(Class<?> clazz,
            String nestedName) {

        Class<?> result = null;
        int p = nestedName.indexOf('.');

        if (p == -1) {
            result = getSimplePropertyType(clazz, nestedName);

        } else {
            Class<?> child = getSimplePropertyType(clazz, nestedName.substring(
                    0, p));

            if (child != null) {
                result = getNestedPropertyType(child, nestedName
                        .substring(p + 1));
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    public static boolean hasNestedProperty(Object object, String nestedName) {
        int p = nestedName.indexOf('.');

        if (p == -1) {
            return hasSimpleProperty(object, nestedName);
        }

        if (object instanceof Map) {

            if (((Map) object).containsKey(nestedName)) {
                return true;
            }
        }

        Object child = getSimplePropertyValue(object, nestedName
                .substring(0, p));

        if (child != null) {
            return hasNestedProperty(child, nestedName.substring(p + 1));
        }

        return false;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getNestedPropertyValue(Object object, String nestedName) {
        int p = nestedName.indexOf('.');

        if (p == -1) {
            return (T) getSimplePropertyValue(object, nestedName);
        }

        if (object instanceof Map) {
            Map map = (Map) object;
            Object value = map.get(nestedName);

            if (value != null) {
                return (T) value;
            }

            if (map.containsKey(nestedName)) {
                return null;
            }
        }

        Object child = getSimplePropertyValue(object, nestedName
                .substring(0, p));

        if (child != null) {
            return (T) getNestedPropertyValue(child, nestedName
                    .substring(p + 1));
        }

        return null;
    }

    @SuppressWarnings("unchecked")
    public static void setNestedPropertyValue(Object object, String nestedName,
            Object value) {

        int p = nestedName.indexOf('.');

        if (p == -1) {
            setSimplePropertyValue(object, nestedName, value);
            return;
        }

        String childName = nestedName.substring(0, p);

        if (object instanceof Map) {
            Map map = ((Map) object);
            Object child = map.get(childName);

            if (child == null) {

                if (map.containsKey(nestedName)) {
                    map.put(nestedName, value);
                    return;
                }

                child = new HashMap();
                map.put(childName, child);
            }

            if (child != null) {
                setNestedPropertyValue(child, nestedName.substring(p + 1),
                        value);
            }

            return;
        }

        Object child = getSimplePropertyValue(object, childName);

        if (child == null) {
            Class<?> childType = getSimplePropertyType(object.getClass(),
                    childName);

            if (childType != null) {

                if (!Modifier.isAbstract(childType.getModifiers())) {
                    child = ReflectUtils.newInstance(childType);

                } else if (childType.equals(Map.class)) {
                    child = new HashMap();
                }
            }
        }

        if (child != null) {
            setSimplePropertyValue(object, childName, child);
            setNestedPropertyValue(child, nestedName.substring(p + 1), value);
        }
    }

    private static Field findField(Class<?> clazz, String name) {
        Field result = null;

        try {
            Field field = clazz.getField(name);

            if (field != null) {
                if (isInstancePublic(field.getModifiers())) {
                    result = field;
                }
            }

        } catch (SecurityException e) {
            /* do nothing */

        } catch (NoSuchFieldException e) {
            /* do nothing */
        }

        return result;
    }

    private static Method findGetter(Class<?> clazz, String name) {
        String getterName = getAccessorName("get", name);
        return getAccessor(clazz, getterName);
    }

    private static Method findSetter(Class<?> clazz, String name, Object value) {
        Method setter = null;
        String setterName = getAccessorName("set", name);

        if (value != null) {
            setter = getAccessor(clazz, setterName, value.getClass());
        }

        if (setter == null) {

            for (Method method : clazz.getMethods()) {

                if (!isInstancePublic(method.getModifiers())) {
                    continue;
                }

                if (method.getParameterTypes().length != 1) {
                    continue;
                }

                if (method.getName().equals(setterName)) {
                    setter = method;
                    break;
                }
            }
        }

        return setter;
    }

    private static String getAccessorName(String prefix, String name) {
        return prefix + Character.toUpperCase(name.charAt(0))
                + name.substring(1);
    }

    private static Method getAccessor(Class<?> clazz, String name,
            Class<?>... paramTypes) {

        try {
            Method method = clazz.getMethod(name, paramTypes);

            if (method != null) {

                if (isInstancePublic(method.getModifiers())) {
                    return method;
                }
            }

        } catch (SecurityException e) {
            /* do nothing */

        } catch (NoSuchMethodException e) {
            /* do nothing */
        }

        return null;
    }

    private static boolean isInstancePublic(int modifiers) {

        if (Modifier.isStatic(modifiers)) {
            return false;
        }

        return Modifier.isPublic(modifiers);
    }
}
