/*******************************************************************************
 * 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 se.unlogic.standardutils.populators.BeanStringPopulator;
import se.unlogic.standardutils.populators.BeanStringPopulatorRegistery;
import se.unlogic.standardutils.populators.UnableToFindSuitablePopulatorException;
import se.unlogic.standardutils.string.StringUtils;

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;

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;
   }
}
