package net.ranides.assira.functional.special;

import net.ranides.assira.functional.ConsumerUtils;
import net.ranides.assira.functional.Consumers;

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

/**
 * 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 AnyConsumer<R> extends Consumer<Object[]>, Serializable {

    @Override
    void accept(Object[] arguments);

    /**
     * Helper method, invokes #accept
     *
     * @param arguments arguments
     */
    default void call(Object... arguments) {
        accept(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)) {
                AnyConsumer.this.accept(args);
                return null;
            } else {
                return method.invoke(AnyConsumer.this, args);
            }
        }));
    }

    /**
     * Converts vararg into Consumer0
     * @param any function
     * @param <R> result
     * @return strict 0-arg function
     */
    static <R> Consumers.Consumer0 toConsumer0(AnyConsumer<R> any) {
        return any == null ? null : any::call;
    }

    /**
     * Converts vararg into Consumer1
     * @param any function
     * @param <R> result
     * @return strict 1-arg function
     */
    static <R> Consumers.Consumer1<Object> toConsumer1(AnyConsumer<R> any) {
        return any == null ? null : any::call;
    }

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

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

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

    /**
     * Converts Consumer0 into vararg
     * @param any strict 0-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyConsumer<R> from(Consumers.Consumer0 any) {
        return args -> any.accept();
    }

    /**
     * Converts Function1 into vararg
     * @param any strict 1-arg function
     * @param <R> result
     * @return vararg function
     */
    static <R> AnyConsumer<R> from(Consumers.Consumer1<?> any) {
        Consumers.Consumer1<Object> func = ConsumerUtils.raw(any);
        return args -> func.accept(args[0]);
    }

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

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

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