package org.simpleflatmapper.reflect;

import org.simpleflatmapper.reflect.asm.AsmFactory;
import org.simpleflatmapper.reflect.getter.FieldGetter;
import org.simpleflatmapper.reflect.getter.MethodGetter;
import org.simpleflatmapper.reflect.getter.GetterHelper;
import org.simpleflatmapper.reflect.primitive.BooleanGetter;
import org.simpleflatmapper.reflect.primitive.BoxedBooleanGetter;
import org.simpleflatmapper.reflect.primitive.BoxedByteGetter;
import org.simpleflatmapper.reflect.primitive.BoxedCharacterGetter;
import org.simpleflatmapper.reflect.primitive.BoxedDoubleGetter;
import org.simpleflatmapper.reflect.primitive.BoxedFloatGetter;
import org.simpleflatmapper.reflect.primitive.BoxedIntGetter;
import org.simpleflatmapper.reflect.primitive.BoxedLongGetter;
import org.simpleflatmapper.reflect.primitive.BoxedShortGetter;
import org.simpleflatmapper.reflect.primitive.ByteGetter;
import org.simpleflatmapper.reflect.primitive.CharacterGetter;
import org.simpleflatmapper.reflect.primitive.DoubleGetter;
import org.simpleflatmapper.reflect.primitive.FloatGetter;
import org.simpleflatmapper.reflect.primitive.IntGetter;
import org.simpleflatmapper.reflect.primitive.LongGetter;
import org.simpleflatmapper.reflect.primitive.ShortGetter;

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

/**
 *
 */
public final class ObjectGetterFactory {
    private final AsmFactory asmFactory;

	public ObjectGetterFactory(AsmFactory asmFactory) {
        this.asmFactory = asmFactory;
    }
	

	public <T, P> Getter<T, P> getGetter(final Class<? super T> target, final String property) {
		// first look for method
		final Method method = lookForMethod(target, property);
		final Getter<T, P> getter;
		if (method == null) {
            getter = getFieldGetter(target, property);
		} else {
            getter = getMethodGetter(method);
		}
		return getter;
	}

	public <T, P> Getter<T, P> getMethodGetter(final Method method) {
		boolean accessible = Modifier.isPublic(method.getModifiers()) && Modifier.isPublic(method.getDeclaringClass().getModifiers());
		if (asmFactory != null && accessible) {
            try {
                return asmFactory.createGetter(method);
            } catch(Throwable e) {
                // ignore
            }
        }
		if (!accessible) {
			method.setAccessible(true);
		}
        return new MethodGetter<T, P>(method);
	}

	public <T, P> Getter<T, P> getFieldGetter(final Class<?> target, final String property) {
		// look for field
		final Field field = lookForField(target, property);
		
		if (field != null) {
            return getFieldGetter(field);
		} else {
			return null;
		}
	}

    public <T, P> Getter<T, P> getFieldGetter(Field field) {

		boolean accessible = Modifier.isPublic(field.getModifiers()) && Modifier.isPublic(field.getDeclaringClass().getModifiers());
		if (asmFactory != null && accessible) {
            try {
                return asmFactory.createGetter(field);
            } catch(Throwable e) {}
        }

        if (!accessible) {
            field.setAccessible(true);
        }
        return new FieldGetter<T, P>(field);
    }

    private Method lookForMethod(final Class<?> target, final String property) {
        if (target == null)  return null;

		for(Method m : target.getDeclaredMethods()) {
			if(GetterHelper.isPublicMember(m.getModifiers())
					&& GetterHelper.methodNameMatchesProperty(m.getName(), property)) {
				return m;
			}
		}
		
		if (!Object.class.equals(target)) {
			return lookForMethod(target.getSuperclass(), property);
		}
		
		return null;
	}
	

	private Field lookForField(final Class<?> target, final String property) {

        if (target == null)  return null;

		for(Field field : target.getDeclaredFields()) {
			if(GetterHelper.fieldModifiersMatches(field.getModifiers())
					&& GetterHelper.fieldNameMatchesProperty(field.getName(), property)) {
				return field;
			}
		}
		
		return lookForField(target.getSuperclass(), property);
	}

	@SuppressWarnings("unchecked")
	public static <T> BooleanGetter<T> toBooleanGetter(final Getter<T, ? extends Boolean> getter) {
		if (getter instanceof BooleanGetter) {
			return (BooleanGetter<T>) getter;
		} else {
			return new BoxedBooleanGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> IntGetter<T> toIntGetter(Getter<T, ? extends Integer> getter) {
		if (getter instanceof IntGetter) {
			return (IntGetter<T>) getter;
		} else {
			return new BoxedIntGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> LongGetter<T> toLongGetter(Getter<T, ? extends Long> getter) {
		if (getter instanceof LongGetter) {
			return (LongGetter<T>) getter;
		} else {
			return new BoxedLongGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> FloatGetter<T> toFloatGetter(Getter<T, ? extends Float> getter) {
		if (getter instanceof FloatGetter) {
			return (FloatGetter<T>) getter;
		} else {
			return new BoxedFloatGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> DoubleGetter<T> toDoubleGetter(Getter<? super T, ? extends Double> getter) {
		if (getter instanceof DoubleGetter) {
			return (DoubleGetter<T>) getter;
		} else {
			return new BoxedDoubleGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> ByteGetter<T> toByteGetter(Getter<T, ? extends Byte> getter) {
		if (getter instanceof ByteGetter) {
			return (ByteGetter<T>) getter;
		} else {
			return new BoxedByteGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> ShortGetter<T> toShortGetter(Getter<T, ? extends Short> getter) {
		if (getter instanceof ShortGetter) {
			return (ShortGetter<T>) getter;
		} else {
			return new BoxedShortGetter<T>(getter);
		}
	}

	@SuppressWarnings("unchecked")
	public static <T> CharacterGetter<T> toCharGetter(Getter<T, ? extends Character> getter) {
		if (getter instanceof CharacterGetter) {
			return (CharacterGetter<T>) getter;
		} else {
			return new BoxedCharacterGetter<T>(getter);
		}
	}

}

@@ -5112,24 +5112,16 @@
 (Getter%3C
-? super 
 T, ? ext

fix java6