/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.util;

import lombok.experimental.UtilityClass;

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

import net.ranides.assira.reflection.*;

/**
 * Helper functions for reflective method invocation and inspection
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@UtilityClass
public final class MethodUtils {

    /**
     * Returns true if "candidate" method overrides "original" declared in base class.
     *
     * Supports edge case where package-scope method can be overriden only by subclass from the same package.
     *
     * @param candidate candidate
     * @param original original
     * @return boolean
     */
    public static boolean overrides(Method original, Method candidate) {
        if( !original.getName().equals(candidate.getName()) ) {
            return false;
        }
        if( MemberTraits.isPrivate(original) ) {
            return false;
        }
        Class<?> otype = original.getDeclaringClass();
        Class<?> ctype = candidate.getDeclaringClass();
        if(!otype.isAssignableFrom(ctype)) {
            return false;
        }

        // package scope check
        if( MemberTraits.isPackage(original)) {
            Package opackage = otype.getPackage();
            Package cpackage = ctype.getPackage();
            if( !opackage.equals(cpackage) ) {
                return false;
            }
        }
        return Arrays.equals(original.getParameterTypes(), candidate.getParameterTypes());
    }
    
    /**
     * Calls non-static method passing "that" as "this" argument.
     *
     * Throws IReflectiveException.CallException if method throws exception.
     * Throws IReflectiveException.AbnormalException if illegal access is thrown.
     * Throws IReflectiveException if other error occurs.
     *
     * @param that that
     * @param method method
     * @param arguments arguments
     * @return Object
     */
    public static Object invoke(Method method, Object that, Object... arguments) {
        if(that == null && !MemberTraits.isStatic(method)) {
            throw new IReflectiveException("non-static method require this!=null");
        }

        try {
            return method.invoke(that, arguments);
        } catch (InvocationTargetException ex) {
            throw new IReflectiveException.CallException(ex.getTargetException());
        } catch (IllegalAccessException ex) {
            throw new IReflectiveException.AbnormalException(ex);
        } catch (IllegalArgumentException  cause) {
            throw new IReflectiveException(cause);
        }
    }

    /**
     * Creates new object by invoking provided constructor.
     *
     * Throws IReflectiveException.CallException if method throws exception.
     * Throws IReflectiveException.AbnormalException if illegal access is thrown.
     * Throws IReflectiveException if other error occurs.
     *
     * @param ctor ctor
     * @param arguments arguments
     * @return Object
     */
    public static Object invoke(Constructor<?> ctor, Object... arguments) {
        try {
            return ctor.newInstance(arguments);
        } catch (InvocationTargetException  cause) {
            throw new IReflectiveException.CallException(cause);
        } catch (IllegalAccessException ex) {
            throw new IReflectiveException.AbnormalException(ex);
        } catch (InstantiationException | IllegalArgumentException cause) {
            throw new IReflectiveException(cause);
        }
    }

    /**
     * Calls non-static method passing "that" as "this" argument.
     * Uses {@link #invoke(Method, Object, Object...)} method.
     *
     * It casts returned value using unchecked type inference.
     *
     * @param method method
     * @param that that
     * @param arguments arguments
     * @param <T> T
     * @return T
     */
	@SuppressWarnings("unchecked")
	public static <T> T $invoke(Method method, Object that, Object... arguments) {
		return (T)invoke(method, that, arguments);
	}
    
}
