/*
 * 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;

/**
 * @author Alex Usun
 */
public abstract class UnaryFunction<V, R>
        implements Serializable {

    /**
     * @param value value
     * @param <T>   <T>
     * @param <U>   <U>
     *
     * @return Constant instance
     */
    public static <T, U> Constant<T, U> constant(U value) {
        return new Constant<T, U>(value);
    }

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

    /**
     * @param next next
     * @param <U>  <U>
     *
     * @return UnaryFunction instance
     */
    public <U> UnaryFunction<V, U> then(UnaryFunction<? super R, ? extends U> next) {
        return new Chain<V, R, U>(this, next);
    }

    public static class Chain<T, R, U>
            extends UnaryFunction<T, U> {

        private UnaryFunction<? super T, ? extends R> first;
        private UnaryFunction<? super R, ? extends U> second;

        /**
         * @param first  first
         * @param second second
         */
        private Chain(UnaryFunction<? super T, ? extends R> first, UnaryFunction<? super R, ? extends U> second) {
            this.first = first;
            this.second = second;
        }

        /**
         * @param value value
         *
         * @return U instance
         */
        public U evaluate(T value) {
            return this.second.evaluate(this.first.evaluate(value));
        }

    }

    public static class Constant<T, U>
            extends UnaryFunction<T, U> {

        private U value;

        /**
         * @param value value
         */
        public Constant(U value) {
            this.value = value;
        }

        /**
         * @param value value
         *
         * @return U instance
         */
        public U evaluate(T value) {
            return this.value;
        }

    }

    public static class Evaluator<V, R>
            extends UnaryFunction<UnaryFunction<V, R>, R> {

        private V argument;

        /**
         * @param argument argument
         */
        public Evaluator(V argument) {
            this.argument = argument;
        }

        /**
         * @param value value
         *
         * @return R instance
         */
        public R evaluate(UnaryFunction<V, R> value) {
            return value.evaluate(this.argument);
        }
    }
}