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

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.functional.special.AnyFunction;

import java.lang.invoke.MethodHandle;
import java.util.List;

/**
 * This interface represents any "callable" object: method, constructor, lambda expression etc
 *
 * It contains all methods shared across all callable objects.
 *
 * Derived interfaces can give more details about exact reflective object.
 * For example IConstructor is parameterized by return type and IMethod gives access to
 * reflective "Method" object.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public interface IExecutable extends IElement {

    @Override
    IExecutables<? extends IExecutable> collect();

    /**
     * Returns list of generic parameters from generic signature.
     *
     * Please note, that if method is declared by generic class,
     * those parameters are not returned even if they are used in signature.
     *
     * t is impossible to "resolve" generic parameters from signature by definition:
     * they are "resolved" at call time. Returned list contains wildcard types with
     * type bounds defined as in generic class declaration.
     *
     * @return List
     */
    List<IClass<?>> params();

    /**
     * Returns type of result produced by method
     *
     * @return IClass
     */
    IClass<?> returns();

    /**
     * Returns arguments accepted by method
     *
     * @return IArguments
     */
    IArguments arguments();

    /**
     * Returns list of exceptions declared by method
     *
     * @return List
     */
    List<IClass<?>> exceptions();

    /**
     * Returns declaring class
     *
     * @return IClass
     */
    @Override
    IClass<?> parent();

    /**
     * Returns true if method accepts exactly provided parameters
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean declares(IArguments arguments);

    /**
     * Returns true if method accepts exactly provided parameters
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean declares(IClasses arguments);

    /**
     * Returns true if method accepts exactly provided parameters
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean declares(IClass<?>... arguments);

    /**
     * Returns true if method accepts exactly provided parameters
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean declares(Class<?>... arguments);

    /**
     * Returns true if method does not take any parameters
     *
     * @return boolean
     */
    boolean declares();

    /**
     * Returns true if method accepts provided parameters (using upcasting)
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean matches(IArguments arguments);

    /**
     * Returns true if method accepts provided parameters (using upcasting)
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean matches(IClasses arguments);

    /**
     * Returns true if method accepts provided parameters (using upcasting)
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean matches(IClass<?>... arguments);

    /**
     * Returns true if method accepts provided parameters (using upcasting)
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean matches(Class<?>... arguments);

    /**
     * Returns true if method does not take any parameters
     *
     * @return boolean
     */
    boolean matches();

    /**
     * Returns true if method accepts provided parameter values
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean accepts(Object... arguments);

    /**
     * Returns true if method accepts provided parameter values
     *
     * This is special method:
     * First value is skipped, so size of "values" must be smaller by one.
     *
     * Generally it is written for edge cases, when we process arguments,
     * but "this" argument must be processed separately.
     *
     * @param arguments arguments
     * @return boolean
     */
    boolean $accepts1(Object... arguments);

    /**
     * Executes method passing null as "this" argument.
     *
     * @return Object
     */
    Object invoke();

    /**
     * Executes method passing "that" as "this" argument.
     *
     * @param that that
     * @return Object
     */
    Object invoke(Object that);

    /**
     * Executes method passing "that" as "this" argument.
     *
     * @param that that
     * @param arguments arguments
     * @return Object
     */
    Object invoke(Object that, Object... arguments);

    /**
     * Executes method.
     *
     * If method is static, passes null as "this" argument.
     * Otherwise, it passes first argument as "this".
     *
     * @param arguments arguments
     * @return Object
     */
    Object call(Object... arguments);

    /**
     * Executes method passing null as "this" argument.
     *
     * Please note difference:
     * "invoke" and "call" uses varags but "apply" uses array explicitly.
     *
     * @param arguments arguments
     * @return Object
     */
    Object apply(Object[] arguments);

    /**
     * Executes method passing "that" as "this" argument.
     *
     * Please note difference:
     * "invoke" and "call" uses varags but "apply" uses array explicitly.
     *
     * @param that that
     * @param arguments arguments
     * @return Object
     */
    Object apply(Object that, Object[] arguments);

    /**
     * Executes method passing null as "this" argument.
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $invoke();

    /**
     * Executes method passing "that" as "this" argument.
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param that that
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $invoke(Object that);

    /**
     * Executes method passing "that" as "this" argument.
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param that that
     * @param arguments arguments
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $invoke(Object that, Object... arguments);

    /**
     * Executes method.
     *
     * If method is static, passes null as "this" argument.
     * Otherwise, it passes first argument as "this".
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param arguments arguments
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $call(Object... arguments);

    /**
     * Executes method passing null as "this" argument.
     *
     * Please note difference:
     * "invoke" and "call" uses varags but "apply" uses array explicitly.
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param arguments arguments
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $apply(Object[] arguments);

    /**
     * Executes method passing "that" as "this" argument.
     *
     * Please note difference:
     * "invoke" and "call" uses varags but "apply" uses array explicitly.
     *
     * This special method makes unchecked cast of returned value int inferred type.
     *
     * @param that that
     * @param arguments arguments
     * @param <T> T
     * @return T
     */
    @Meta.Unsafe
    <T> T $apply(Object that, Object[] arguments);

    /**
     * Assumess that method is non-static and converts it into AnyFunction
     * by binding "this" argument (1st argument) into provided value
     *
     * Binded function will be always invoked on specified "that" object,
     * expecting only explictly declared parameters.
     *
     * @param that that
     * @return AnyFunction
     */
    AnyFunction<Object> bind(Object that);

    /**
     * Returns reflective MethodHandle
     *
     * Every method, including constructors and lambda expressions support MethodHandle
     *
     * @return MethodHandle
     */
    MethodHandle handle();

    /**
     * Converts IMethod into provided functional type
     *
     * @param functor functor
     * @param <F> F
     * @return F
     */
    <F> F handle(Class<F> functor);

    /**
     * Converts IMethod into provided functional type
     *
     * @param functor functor
     * @param <F> F
     * @return F
     */
    <F> F handle(IClass<F> functor);

    /**
     * Forces reflective access if method ins non-public.
     * It can throw exception, if it is not allowed because of SecurityException.
     *
     * @return this
     */
    IExecutable accessible();

    boolean isAccessible();

    /**
     * Alias for {@code attribute(IAttribute.ABSTRACT)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(ABSTRACT)")
    boolean isAbstract();

    /**
     * Alias for {@code attribute(IAttribute.SYNCHRONIZED)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(SYNCHRONIZED)")
    boolean isSynchronized();

    /**
     * Alias for {@code attribute(IAttribute.BRIDGE)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(BRIDGE)")
    boolean isBridge();

    /**
     * Alias for {@code attribute(IAttribute.NATIVE)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(NATIVE)")
    boolean isNative();

    /**
     * Alias for {@code attribute(IAttribute.LAMBDA)}.
     *
     * @return boolean
     */
    @Meta.Alias("attribute(LAMBDA)")
    boolean isLambda();

}
