package net.ranides.assira.functional;

import lombok.experimental.UtilityClass;

import java.io.Serializable;
import java.util.function.BiPredicate;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;

/**
 * Predicate interfaces with arity other than 1
 *
 * Please note, that those interfaces extend Serializable.
 * You can use them if you want to store your lambda expressions in Serializable objects.
 */
@UtilityClass
public class Predicates {

    /**
     * Predicate interface with arity = 0.
     * It is equivalent to BooleanSupplier, but uses Predicate naming convention.
     */
    public interface Predicate0 extends BooleanSupplier, Serializable {
        /**
         * Operation
         *
         * @return bool
         */
        boolean test();

        default boolean getAsBoolean() {
            return test();
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate0 and(Predicate0 other) {
            return () -> test() && other.test();
        }

        /**
         * Creates negated predicate
         *
         * @return predicate
         */
        default Predicate0 negate() {
            return () -> !test();
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate0 or(Predicate0 other) {
            return () -> test() || other.test();
        }
    }

    /**
     * Predicate interface with arity = 1
     * It is equivalent to Predicate
     *
     * @param <A> A
     */
    public interface Predicate1<A> extends Predicate<A>, Serializable {
        @Override
        boolean test(A a);
    }

    /**
     * Predicate interface with arity = 2
     * It is equivalent to BiPredicate
     *
     * @param <A> A
     * @param <B> B
     */
    public interface Predicate2<A,B> extends BiPredicate<A,B>, Serializable {
        @Override
        boolean test(A a, B b);
    }

    /**
     * Predicate interface with arity = 3
     *
     * @param <A> A
     * @param <B> B
     * @param <C> C
     */
    public interface Predicate3<A,B,C> extends Serializable {
        /**
         * Operation
         *
         * @param a a
         * @param b b
         * @param c c
         * @return bool
         */
        boolean test(A a, B b, C c);

        /**
         * Creates negated predicate
         *
         * @return predicate
         */
        default Predicate3<A, B, C> negate() {
            return (a, b, c) -> !test(a, b, c);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate3<A, B, C> and(Predicate3<? super A, ? super B, ? super C> other) {
            return (a, b, c) -> test(a, b, c) && other.test(a, b, c);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate3<A, B, C> or(Predicate3<? super A, ? super B, ? super C> other) {
            return (a, b, c) -> test(a, b, c) || other.test(a, b, c);
        }
    }

    /**
     * Predicate interface with arity = 4
     *
     * @param <A> A
     * @param <B> B
     * @param <C> C
     * @param <D> D
     */
    public interface Predicate4<A,B,C,D> extends Serializable {
        /**
         * Operation
         *
         * @param a a
         * @param b b
         * @param c c
         * @param d d
         * @return bool
         */
        boolean test(A a, B b, C c, D d);

        /**
         * Creates negated predicate
         *
         * @return predicate
         */
        default Predicate4<A, B, C, D> negate() {
            return (a, b, c, d) -> !test(a, b, c, d);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate4<A, B, C, D> and(Predicate4<? super A, ? super B, ? super C, ? super D> other) {
            return (a, b, c, d) -> test(a, b, c, d) && other.test(a, b, c, d);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default Predicate4<A, B, C, D> or(Predicate4<? super A, ? super B, ? super C, ? super D> other) {
            return (a, b, c, d) -> test(a, b, c, d) || other.test(a, b, c, d);
        }
    }

    /**
     * It is Predicate which takes value of element and index of element inside collection.
     * It is used by functional looping methods.
     *
     * @author Ranides Atterwim {@literal <ranides@gmail.com>}
     */
    public interface EachPredicate<T> extends Serializable {

        /**
         * Operation: takes value and it's position.
         *
         * @param index index
         * @param value value
         * @return bool
         */
        boolean test(int index, T value);

        /**
         * Creates negated predicate
         *
         * @return predicate
         */
        default EachPredicate<T> negate() {
            return (i, v) -> !test(i, v);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default EachPredicate<T> and(EachPredicate<? super T> other) {
            return (i, v) -> test(i, v) && other.test(i, v);
        }

        /**
         * Creates composite predicate
         *
         * @param other other
         * @return predicate
         */
        default EachPredicate<T> or(EachPredicate<? super T> other) {
            return (i, v) -> test(i, v) || other.test(i, v);
        }

    }

}
