package propertymapper;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.Objects;


public class Utils {

  public static BeanInfo getBeanInfo(Class<?> type) {
    try {
      return Introspector.getBeanInfo(type);
    } catch (IntrospectionException e) {
      throw new IllegalStateException(e);
    }
  }

  public static <T> Constructor<T> getConstructor(Class<T> type, Class<?>... parameterTypes) {
    try {
      return type.getConstructor(parameterTypes);
    } catch (NoSuchMethodException e) {
      throw (NoSuchMethodError) new NoSuchMethodError().initCause(e);
    }
  }

  public static <T> T newInstance(Constructor<T> constructor) {
    Objects.requireNonNull(constructor);
    try {
      return constructor.newInstance();
    } catch (InstantiationException e) {
      throw (InstantiationError) new InstantiationError().initCause(e);
    } catch(IllegalAccessException e) {
      throw (IllegalAccessError) new IllegalAccessError().initCause(e);
    } catch (InvocationTargetException e) {
      var cause = e.getCause();
      switch (cause) {
        case RuntimeException runtimeException -> throw runtimeException;
        case Error error -> throw error;
        case Throwable throwable -> throw new UndeclaredThrowableException(throwable);
      }
    }
  }

  public static Object invoke(Method method, Object instance, Object... arguments) {
    Objects.requireNonNull(method);
    Objects.requireNonNull(instance);
    Objects.requireNonNull(arguments);
    try {
      return method.invoke(instance, arguments);
    } catch (IllegalAccessException e) {
      throw (IllegalAccessError) new IllegalAccessError().initCause(e);
    } catch (InvocationTargetException e) {
      var cause = e.getCause();
      switch (cause) {
        case RuntimeException runtimeException -> throw runtimeException;
        case Error error -> throw error;
        case Throwable throwable -> throw new UndeclaredThrowableException(throwable);
      }
    }
  }
}
