package nxm.sys.lib;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;

import nxm.sys.inc.ProvisionalUseOnly;

/**
 *  Static utility methods useful for Java Reflection
 *
 *  @author Jeanette McCall
 *  @since NeXtMidas 3.9.2
 *
 */
public class ReflectUtil {

  /** This constructor was automatically provided by Java,
   *  but it is unneeded as all the methods are static methods.
   *  @deprecated now that Javadocs are required, it is temporarily
   *              being explicitely provided but deprecated
   */
  @Deprecated
  public ReflectUtil () {}

  //***************************************************************************************
  // wrapper classes for Java reflection methods that throw exception
  // null is returned if unsuccessful
  //***************************************************************************************

  /** Wrapper for Class.forName that returns null rather than throwing exception
   *
   *  @param className name of Class
   *  @return Class if found
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  public static Class<?> getClassForName (String className) {
    try {
      return Class.forName(className);
    }
    catch (ClassNotFoundException e) {
      return null;
    }
  }

  /** Checks if a known class is an instanceof a class that might no longer exist
   *  (same as instanceof, but returns false (rather than throwing exceptions) if the
   *  class that is being checked against does not exist)
   *
   *  @param classChecking        known class
   *  @param classCheckingAgainst seeing if instance of this class, which may not even be present
   *  @return  false if class checking against does not exist, and instanceof otherwise
   *  @since NeXtMidas 3.9.2, specifically to allow instanceof checks during the JDK 11 modularity transition
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  static public boolean isInstanceOfClass (Class<?> classChecking, String classCheckingAgainst) {
    return classChecking.isInstance(getClassForName(classCheckingAgainst));
  }

  /** Gets the method matching the String description of the class and method (with
   *  the given parameter types), or null if no match is found<br>
   *  Specifically added to allow calling methods of classes that are being transitioned from
   *  one location to another for HDK 11 and having graphics be in a separate module from library code
   *
   *  @param className String containing class name
   *  @param methodName String containing method name
   *  @param parameterTypes method pramter types
   *  @return method if it exists and otherwise null
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  static public Method getMethodOfClass (String className, String methodName, Class<?>... parameterTypes) {
    try {
      return Class.forName(className).getMethod(methodName, parameterTypes);
    }
    catch (NoSuchMethodException|SecurityException|ClassNotFoundException e) {
      return null;
    }
  }

  /** Gets the method matching the the class, method name, and the given
   *  parameter types, or null if no match is found<br>
   *  Specifically added to allow calling methods of classes that are being transitioned from
   *  one location to another for HDK 11 and having graphics be in a separate module from library code
   *
   *  @param clazz          class from which to retrieve method
   *  @param methodName     String containing method name
   *  @param parameterTypes method pramter types
   *  @return method if it exists and otherwise null
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  static public Method getMethodOfClass (Class<?> clazz, String methodName, Class<?>... parameterTypes) {
    try {
      return clazz.getMethod(methodName, parameterTypes);
    }
    catch (NoSuchMethodException|SecurityException e) {
      return null;
    }
  }

  /** Gets the contructor matching the the class and the given
   *  parameter types, or null if no match is found<br>
   *
   *  @param className String containing class name
   *  @param parameterTypes constructor parameter types
   *  @return constructor if it exists and otherwise null
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  static public Constructor<?> getConstructorOfClass (String className, Class<?>... parameterTypes) {
    try {
      return Class.forName(className).getConstructor(parameterTypes);
    }
    catch (NoSuchMethodException|SecurityException|ClassNotFoundException e) {
      return null;
    }
  }

  /** Gets the contructor matching the the class and the given
   *  parameter types, or null if no match is found<br>
   *
   *  @param clazz          class from which to retrieve constructor
   *  @param parameterTypes constructor parameter types
   *  @return constructor if it exists and otherwise null
   */
  @ProvisionalUseOnly("Mainly intended for internal use")
  static public Constructor<?> getConstructorOfClass (Class<?> clazz, Class<?>... parameterTypes) {
    try {
      return clazz.getConstructor(parameterTypes);
    }
    catch (NoSuchMethodException|SecurityException e) {
      return null;
    }
  }

  /** Calls a method using reflection
  *
  *  @param obj        object on which to call the method or null if method is static
  *  @param className  the name of the Class containing the method
  *  @param methodName method name
  *  @param params     the parameters for the method
  *  @param parameterTypes the parameter types for the method
  *  @return Optional Object containing the return value of empty if unsuccessful
  */
  @ProvisionalUseOnly("mainly for internal use - not thoroughly tested")
  static public Optional<Object> callMethodOfClass (Object obj, String className, String methodName,
      Object[] params, Class<?>... parameterTypes) {
    try {
      return callMethodOfClass(obj, Class.forName(className), methodName, params, parameterTypes);
    }
    catch (ClassNotFoundException e) {
      return Optional.empty();
    }
  }

  /** Calls a method using reflection
   *
   *  @param obj        object on which to call the method or null if method is static
   *  @param clazz      the Class containing the method
   *  @param methodName method name
   *  @param params     the parameters for the method
   *  @param parameterTypes the parameter types for the method
   *  @return Optional Object containing the return value of empty if unsuccessful
   */
  @ProvisionalUseOnly("mainly for internal use - not thoroughly tested")
  static public Optional<Object> callMethodOfClass (Object obj, Class<?> clazz, String methodName,
      Object[] params, Class<?>... parameterTypes) {
    Method method = ReflectUtil.getMethodOfClass(clazz, methodName, String.class);
    return callMethod(obj, method, params);
  }

  /** Call the given Method using reflection
   *
   *  @param obj    object on which to call the methos (null if static)
   *  @param method the Method
   *  @param params Object array containing method parameters
   *  @return Optional Object with return value (empty if unsuccessful)
   */
  @ProvisionalUseOnly("mainly for internal use - not thoroughly tested")
  static public Optional<Object> callMethod (Object obj, Method method, Object[] params) {
    Optional<Object> returnValue = Optional.empty();
    if (method != null) {
      try {
        returnValue = Optional.of(method.invoke(obj, params));
      }
      catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
      }
    }
    return returnValue;
  }

}
