/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import se.unlogic.standardutils.populators.BeanStringPopulator;
import se.unlogic.standardutils.populators.BeanStringPopulatorRegistery;
import se.unlogic.standardutils.populators.UnableToFindSuitablePopulatorException;
import se.unlogic.standardutils.string.StringUtils;

public class ReflectionUtils {

	public static Object getInstance(String className) throws NoClassDefFoundError, ClassNotFoundException, InstantiationException, IllegalAccessException {
		return Class.forName(className).newInstance();
	}

	public static boolean isGenericlyTyped(Field field) {

		if (field.getGenericType() instanceof ParameterizedType) {

			return true;
		}

		return false;
	}

	public static int getGenericlyTypeCount(Field field) {

		if (field.getGenericType() instanceof ParameterizedType) {

			ParameterizedType type = (ParameterizedType) field.getGenericType();

			return type.getActualTypeArguments().length;
		}

		return 0;
	}

	public static int getGenericlyTypeCount(Method method) {

		return method.getGenericParameterTypes().length;
	}	
	
	public static boolean checkGenericTypes(Field field, Class<?>... classes) {

		if (field.getGenericType() instanceof ParameterizedType) {

			ParameterizedType type = (ParameterizedType) field.getGenericType();

			if (type.getActualTypeArguments().length != classes.length) {
				return false;
			}

			for (int i = 0; i < classes.length; i++) {

				if (!type.getActualTypeArguments()[i].equals(classes[i])) {
					return false;
				}
			}

			return true;
		}

		return false;
	}

	public static Type getGenericType(Field field) {
		
		Type type = ((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
		
		if(type instanceof WildcardType){
			
			//TODO do a little bit more research on this part...
			return ((WildcardType)type).getUpperBounds()[0];
			
		}else{
			
			return type;
		}
	}

	public static Object getGenericType(Method method) {

		return method.getGenericParameterTypes()[0];
	}
	
	public static void fixFieldAccess(Field field) {

		if(!field.isAccessible()){
			field.setAccessible(true);
		}
	}

	public static void fixMethodAccess(Method method) {

		if(!method.isAccessible()){
			method.setAccessible(true);
		}
	}

	public static Field getField(Class<?> bean, String fieldName) {


		List<Field> fields = getFields(bean);

		for(Field field : fields){

			if(field.getName().equals(fieldName)){

				return field;
			}
		}

		throw new RuntimeException(new NoSuchFieldError(fieldName));
	}

	public static boolean isAvailable(String classname) {
		try {
			Class.forName(classname);
			return true;
		} catch (ClassNotFoundException cnfe) {
			return false;
		}
	}

	public static List<Field> getFields(Class<?> clazz){
		
		ArrayList<Field> fields = new ArrayList<Field>();
		
		fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
		
		clazz = clazz.getSuperclass();
		
		while(clazz != Object.class){
			
			fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
			
			clazz = clazz.getSuperclass();
		}
		
		return fields;
	}

	public static List<Method> getMethods(Class<?> clazz){
		
		ArrayList<Method> methods = new ArrayList<Method>();
		
		methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
		
		clazz = clazz.getSuperclass();
		
		while(clazz != Object.class){
			
			methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
			
			clazz = clazz.getSuperclass();
		}
		
		return methods;
	}	
	
	public static Method getMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... inputParams) {

		if(inputParams == null){
		
			inputParams = new Class<?>[0];
		}
		
		Method[] methods = clazz.getDeclaredMethods();
		
		for(Method method : methods){
			
			if(method.getName().equals(methodName) && returnType.isAssignableFrom(method.getReturnType()) && Arrays.equals(inputParams, method.getParameterTypes())){
				
				return method;
			}
		}
		
		return null;
	}
	
	
	public static void setFieldValue(Field field, Object value, Object target) throws IllegalArgumentException, IllegalAccessException {

		boolean declaredAccessible = field.isAccessible();

		if (!declaredAccessible) {
			field.setAccessible(true);
		}

		field.set(target, value);

		if (!declaredAccessible) {
			field.setAccessible(false);
		}
	}

	public static void setMethodValue(Method method, Object value, Object target) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {

		boolean declaredAccessible = method.isAccessible();

		if (!declaredAccessible) {
			method.setAccessible(true);
		}

		method.invoke(target, value);

		if (!declaredAccessible) {
			method.setAccessible(false);
		}
	}	
	
	public static boolean setSetterMethod(Object target, String methodName, String value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, MethodNotFoundException, UnableToFindSuitablePopulatorException {

		if(!methodName.startsWith("set")){
			
			methodName = "set" + StringUtils.toFirstLetterUppercase(methodName);
		}
		
		Method method = getMethod(target.getClass(),methodName,1);
		
		if(method == null){
			
			throw new MethodNotFoundException("Unable to find setter method " + methodName + " with correct signature");
		}
		
		if(value == null || method.getParameterTypes()[0].equals(String.class)){
			
			setMethodValue(method,value,target);
		}
		
		BeanStringPopulator<?> populator = BeanStringPopulatorRegistery.getBeanStringPopulator(method.getParameterTypes()[0]);
		
		if(populator == null){
			
			throw new UnableToFindSuitablePopulatorException("Unable to find BeanStringPopulator for " + method.getParameterTypes()[0]);
		}
		
		setMethodValue(method,populator.getValue(value),target);
		
		return true;
	}

	public static Method getMethod(Class<?> clazz, String methodName, int argumentCount) {

		List<Method> methods = getMethods(clazz);
		
		for(Method method : methods){
			
			if(!method.getName().equalsIgnoreCase(methodName)){
				
				continue;
			}
			
			if(method.getParameterTypes().length != argumentCount){
				
				continue;
			}
			
			return method;
		}
		
		return null;
	}
}
