/*
 * Copyright (c) 2009 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 *
 */

package com.jscape.util.functor;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

/**
 * @author Alex Usun
 */
@SuppressWarnings("unchecked")
public abstract class BinaryPredicate<F, S>
        implements Serializable {

    /**
     * @param base base
     * @param <F>  <F>
     * @param <S>  <S>
     *
     * @return
     */
    public static <F, S> BinaryPredicate<F, S> not(BinaryPredicate<? super F, ? super S> base) {
        return new Not<F, S>(base);
    }

    /**
     * @param first  first
     * @param second second
     *
     * @return boolean value
     */
    public abstract boolean evaluate(F first, S second);

    /**
     * @param predicates predicates
     *
     * @return BinaryPredicate instance
     */
    public BinaryPredicate<F, S> and(BinaryPredicate<? super F, ? super S>... predicates) {
        BinaryPredicate<F, S> predicate = this;

        for (BinaryPredicate<? super F, ? super S> next : predicates) {
            predicate = new And<F, S>(predicate, next);
        }

        return predicate;
    }

    /**
     * @param predicates predicates
     *
     * @return BinaryPredicate
     */
    public BinaryPredicate<F, S> or(BinaryPredicate<? super F, ? super S>... predicates) {
        BinaryPredicate<F, S> predicate = this;

        for (BinaryPredicate<? super F, ? super S> next : predicates) {
            predicate = new Or<F, S>(predicate, next);
        }

        return predicate;
    }

    /**
     * @param <T>
     * @param <U>
     */
    public static class True<T, U>
            extends BinaryPredicate<T, U> {

        /**
         * @param first  first
         * @param second second
         *
         * @return boolean value
         */
        public boolean evaluate(T first, U second) {
            return true;
        }
    }

    /**
     * @param <T>
     * @param <U>
     */
    public static class False<T, U>
            extends BinaryPredicate<T, U> {

        /**
         * @param first  first
         * @param second second
         *
         * @return boolean value
         */
        public boolean evaluate(T first, U second) {
            return false;
        }
    }

    /**
     * @param <T>
     * @param <U>
     */
    public static class And<T, U>
            extends BinaryPredicate<T, U> {

        private List<BinaryPredicate<? super T, ? super U>> predicates;

        /**
         * @param first  first
         * @param second second
         */
        public And(BinaryPredicate<? super T, ? super U> first, BinaryPredicate<? super T, ? super U> second) {
            this.predicates = new LinkedList<>();

            add(first);
            add(second);
        }

        /**
         * @param first  first
         * @param second second
         *
         * @return boolean value
         */
        public boolean evaluate(T first, U second) {
            for (BinaryPredicate<? super T, ? super U> predicate : this.predicates) {
                if (!predicate.evaluate(first, second)) {
                    return false;
                }
            }

            return true;
        }

        /**
         * @param predicate predicate
         */
        private void add(BinaryPredicate<? super T, ? super U> predicate) {
            if (predicate instanceof And) {
                this.predicates.addAll(((And<T, U>) predicate).predicates);
            } else {
                this.predicates.add(predicate);
            }
        }

    }

    /**
     * @param <T>
     * @param <U>
     */
    public static class Or<T, U>
            extends BinaryPredicate<T, U> {

        private List<BinaryPredicate<? super T, ? super U>> predicates;

        /**
         * @param first  first
         * @param second second
         */
        public Or(BinaryPredicate<? super T, ? super U> first, BinaryPredicate<? super T, ? super U> second) {
            this.predicates = new LinkedList<>();

            add(first);
            add(second);
        }

        /**
         * @param first  first
         * @param second second
         *
         * @return boolean value
         */
        public boolean evaluate(T first, U second) {
            for (BinaryPredicate<? super T, ? super U> predicate : this.predicates) {
                if (predicate.evaluate(first, second)) {
                    return true;
                }
            }

            return false;
        }

        /**
         * @param predicate predicate
         */
        private void add(BinaryPredicate<? super T, ? super U> predicate) {
            if (predicate instanceof Or) {
                this.predicates.addAll(((Or<T, U>) predicate).predicates);
            } else {
                this.predicates.add(predicate);
            }
        }

    }

    /**
     * @param <T>
     * @param <U>
     */
    public static class Not<T, U>
            extends BinaryPredicate<T, U> {

        private BinaryPredicate<? super T, ? super U> base;

        /**
         * @param base base
         */
        public Not(BinaryPredicate<? super T, ? super U> base) {
            this.base = base;
        }

        /**
         * @param first  first
         * @param second second
         *
         * @return boolean value
         */
        public boolean evaluate(T first, U second) {
            return !this.base.evaluate(first, second);
        }
    }

}