/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.functional.special;

import java.util.Iterator;
import java.util.function.BinaryOperator;

/**
 * Specialization of BinaryOperator.
 * Fold operation is supposed to be called by reduce operations.
 *
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
@FunctionalInterface
public interface Fold<T> extends BinaryOperator<T> {

    @Override
    T apply(T a, T b);

    /**
     * It folds all values from iterator
     *
     * @param iterator iterator
     * @return value
     */
    default T apply(Iterator<T> iterator) {
        return apply(iterator, this);
    }

    /**
     * It folds all values from array
     *
     * @param array array
     * @return value
     */
    default T apply(T[] array) {
        return apply(array, this);
    }

    /**
     * It folds all values from iterator.
     * It allows to provide default argument, if iterator is empty.
     *
     * @param iterator iterator
     * @param identity identity
     * @param operator operator
     * @param <T> T
     * @return value
     */
    static <T> T apply(Iterator<T> iterator, T identity, BinaryOperator<T> operator) {
        T ret = identity;
        while(iterator.hasNext()) {
            ret = operator.apply(ret, iterator.next());
        }
        return ret;
    }

    /**
     * It folds all values from iterator.
     *
     * @param iterator iterator
     * @param operator operator
     * @param <T> T
     * @return value
     */
    static <T> T apply(Iterator<T> iterator, BinaryOperator<T> operator) {
        T ret = iterator.next();
        while(iterator.hasNext()) {
            ret = operator.apply(ret, iterator.next());
        }
        return ret;
    }

    /**
     * It folds all values from array.
     *
     * @param array array
     * @param operator operator
     * @param <T> T
     * @return value
     */
    static <T> T apply(T[] array, BinaryOperator<T> operator) {
        T ret = array[0];
        for(int i=1; i<array.length; i++) {
            ret = operator.apply(ret, array[i]);
        }
        return ret;
    }
    
}
