package net.ranides.assira.functional.special;

import net.ranides.assira.functional.FunctionUtils;
import net.ranides.assira.functional.Functions;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.util.function.Function;

/**
 * This is functional interface for var-args functions.
 *
 * It declares helper method #call which can be used to invoke method easily.
 * Inherited #apply must be implemented in exactly the same way as in standard Function interface.
 *
 * You can use a bunch of static methods, to convert strict n-ary functions into varags.
 * @param <R>
 */
public interface AnyFunction<R> extends Function<Object[], R>, Serializable {

    @Override
    R apply(Object[] arguments);

    /**
     * Helper method, invokes #apply
     *
     * @param arguments arguments
     * @return result
     */
    default R call(Object... arguments) {
        return apply(arguments);
    }

    default <T> T cast(Class<T> target) {
        return target.cast(Proxy.newProxyInstance(target.getClassLoader(), new Class<?>[]{target}, (proxy, method, args) -> {
            if(method.getDeclaringClass().equals(target)) {
                return AnyFunction.this.apply(args);
            } else {
                return method.invoke(AnyFunction.this, args);
            }
        }));
    }

    /**
     * Converts vararg into Function0
     * @param any function
     * @param <R> result
     * @return strict 0-arg function
     */
    static <R> Functions.Function0<R> toFunction0(AnyFunction<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts vararg into Function1
     * @param any function
     * @param <R> result
     * @return strict 1-arg function
     */
    static <R> Functions.Function1<Object, R> toFunction1(AnyFunction<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts vararg into Function2
     * @param any function
     * @param <R> result
     * @return strict 2-arg function
     */
    static <R> Functions.Function2<Object, Object, R> toFunction2(AnyFunction<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts vararg into Function3
     * @param any function
     * @param <R> result
     * @return strict 3-arg function
     */
    static <R> Functions.Function3<Object, Object, Object, R> toFunction3(AnyFunction<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts vararg into Function4
     * @param any function
     * @param <R> result
     * @return strict 4-arg function
     */
    static <R> Functions.Function4<Object, Object, Object, Object, R> toFunction4(AnyFunction<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts Function0 into vararg
     * @param any strict 0-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyFunction<R> from(Functions.Function0<R> any) {
        return args -> any.apply();
    }

    /**
     * Converts Function1 into vararg
     * @param any strict 1-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyFunction<R> from(Functions.Function1<?, R> any) {
        Functions.Function1<Object, R> func = FunctionUtils.raw(any);
        return args -> func.apply(args[0]);
    }

    /**
     * Converts Function2 into vararg
     * @param any strict 2-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyFunction<R> from(Functions.Function2<?, ?, R> any) {
        Functions.Function2<Object, Object, R> func = FunctionUtils.raw(any);
        return args -> func.apply(args[0], args[1]);
    }

    /**
     * Converts Function3 into vararg
     * @param any strict 3-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyFunction<R> from(Functions.Function3<?, ?, ?, R> any) {
        Functions.Function3<Object, Object, Object, R> func = FunctionUtils.raw(any);
        return args -> func.apply(args[0], args[1], args[2]);
    }

    /**
     * Converts Function4 into vararg
     * @param any strict 4-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyFunction<R> from(Functions.Function4<?, ?, ?, ?, R> any) {
        Functions.Function4<Object, Object, Object, Object, R> func = FunctionUtils.raw(any);
        return args -> func.apply(args[0], args[1], args[2], args[3]);
    }
}
