/*
 * 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 UnaryPredicate<V>
        implements Serializable {

    /**
     * @param base base
     * @param <V>  <V>
     *
     * @return UnaryPredicate instance
     */
    public static <V> UnaryPredicate<V> not(UnaryPredicate<? super V> base) {
        return new Not<V>(base);
    }

    /**
     * @param value value
     *
     * @return boolean value
     */
    public abstract boolean evaluate(V value);

    /**
     * @param predicates predicates
     *
     * @return UnaryPredicate instance
     */
    public UnaryPredicate<V> and(UnaryPredicate<? super V>... predicates) {
        UnaryPredicate<V> predicate = this;

        for (UnaryPredicate<? super V> next : predicates) {
            predicate = new And<V>(predicate, next);
        }

        return predicate;
    }

    /**
     * @param predicates predicates
     *
     * @return UnaryPredicate instance
     */
    public UnaryPredicate<V> or(UnaryPredicate<? super V>... predicates) {
        UnaryPredicate<V> predicate = this;

        for (UnaryPredicate<? super V> next : predicates) {
            predicate = new Or<V>(predicate, next);
        }

        return predicate;
    }

    public static class True<T>
            extends UnaryPredicate<T> {

        /**
         * @param value value
         *
         * @return boolean value
         */
        public boolean evaluate(Object value) {
            return true;
        }
    }

    public static class False<T>
            extends UnaryPredicate<T> {

        /**
         * @param value value
         *
         * @return boolean value
         */
        public boolean evaluate(Object value) {
            return false;
        }
    }

    @SuppressWarnings("unchecked")
    public static class And<T>
            extends UnaryPredicate<T> {

        private List<UnaryPredicate<? super T>> predicates;

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

            add(first);
            add(second);
        }

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

            return true;
        }

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

    @SuppressWarnings("unchecked")
    public static class Or<T>
            extends UnaryPredicate<T> {

        private List<UnaryPredicate<? super T>> predicates;

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

            add(first);
            add(second);
        }

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

            return false;
        }

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

    }

    public static class Not<T>
            extends UnaryPredicate<T> {

        private UnaryPredicate<? super T> base;

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

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


}