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

import com.jscape.util.Copyable;
import com.jscape.util.functor.*;
import com.jscape.util.functor.adapters.BoundFirstUnaryPredicate;

import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static com.jscape.util.collections.ArrayIterator.iterator;
import static com.jscape.util.functor.UnaryPredicate.not;

/**
 * @author Alex Usun
 */
public class Algorithms {

    public static <T> List<T> copyIf(Iterator<T> iterator, UnaryPredicate<? super T> predicate) {
        List<T> ret = new LinkedList<>();

        while (iterator.hasNext()) {
            T candidate = iterator.next();

            if (predicate.evaluate(candidate)) {
                ret.add(candidate);
            }
        }

        return ret;
    }

    public static <T> List<T> copyIf(Collection<T> collection, UnaryPredicate<? super T> predicate) {
        return copyIf(collection.iterator(), predicate);
    }

    public static <T> List<T> copyIf(T[] collection, UnaryPredicate<? super T> predicate) {
        return copyIf(iterator(collection), predicate);
    }

    public static <T> int count(Iterator<T> iterator, UnaryPredicate<? super T> predicate) {
        int count = 0;

        while (iterator.hasNext()) {
            T candidate = iterator.next();

            if (predicate.evaluate(candidate)) {
                ++count;
            }
        }

        return count;
    }

    public static <T> int count(Collection<T> collection, UnaryPredicate<? super T> predicate) {
        return count(collection.iterator(), predicate);
    }

    public static <T> int count(T[] collection, UnaryPredicate<? super T> predicate) {
        return count(iterator(collection), predicate);
    }

    public static <T> T find(Iterator<T> iterator, UnaryPredicate<? super T> predicate, T defaultValue) {
        while (iterator.hasNext()) {
            T candidate = iterator.next();

            if (predicate.evaluate(candidate)) {
                return candidate;
            }
        }

        return defaultValue;
    }

    public static <T> T find(Collection<T> collection, UnaryPredicate<? super T> predicate, T defaultValue) {
        return find(collection.iterator(), predicate, defaultValue);
    }

    public static <T> T find(T[] collection, UnaryPredicate<? super T> predicate, T defaultValue) {
        return find(iterator(collection), predicate, defaultValue);
    }

    public static <T> T find(Iterator<T> iterator, UnaryPredicate<? super T> predicate) {
        return find(iterator, predicate, null);
    }

    public static <T> T find(Collection<T> collection, UnaryPredicate<? super T> predicate) {
        return find(collection.iterator(), predicate);
    }

    public static <T> T find(T[] collection, UnaryPredicate<? super T> predicate) {
        return find(iterator(collection), predicate);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Iterator<T> iterator, U procedure, Predicate predicate) {
        while (predicate.evaluate() && iterator.hasNext()) {
            procedure.evaluate(iterator.next());
        }

        return procedure;
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Collection<T> collection, U procedure, Predicate predicate) {
        return forEach(collection.iterator(), procedure, predicate);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(T[] collection, U procedure, Predicate predicate) {
        return forEach(iterator(collection), procedure, predicate);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Iterator<T> iterator, U procedure) {
        return forEach(iterator, procedure, Predicate.TRUE);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Collection<T> collection, U procedure) {
        return forEach(collection.iterator(), procedure);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(T[] collection, U procedure) {
        return forEach(iterator(collection), procedure);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Iterator<T> iterator, U procedure, UnaryPredicate<? super T> predicate) {
        while (iterator.hasNext()) {
            T value = iterator.next();

            if (predicate.evaluate(value)) {
                procedure.evaluate(value);
            }
        }

        return procedure;
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(Collection<T> collection, U procedure, UnaryPredicate<? super T> predicate) {
        return forEach(collection.iterator(), procedure, predicate);
    }

    public static <T, U extends UnaryProcedure<? super T>> U forEach(T[] collection, U procedure, UnaryPredicate<? super T> predicate) {
        return forEach(iterator(collection), procedure, predicate);
    }

    public static <T, U> List<U> copy(Iterator<T> iterator) {
        return copyIf(TransformIterator.adapt(iterator, new Identity<T, U>()), new UnaryPredicate.True<U>());
    }

    public static <T, U> List<U> copy(Collection<T> collection) {
        return copy(collection.iterator());
    }

    public static <T, U> List<U> copy(T[] collection) {
        return copy(ArrayIterator.iterator(collection));
    }

    @SuppressWarnings("unchecked")
    public static <T, R> R[] toArray(Iterator<T> iterator, Class<R> aClass, UnaryFunction<? super T, ? extends R> converter) {
        List<R> ret = copy(TransformIterator.adapt(iterator, converter));
        return ret.toArray((R[]) Array.newInstance(aClass, ret.size()));
    }

    public static <T, R> R[] toArray(Collection<T> collection, Class<R> aClass, UnaryFunction<? super T, ? extends R> converter) {
        return toArray(collection.iterator(), aClass, converter);
    }

    public static <T, R> R[] toArray(Collection<T> collection, Class<R> aClass) {
        return toArray(collection, aClass, new Identity<T, R>());
    }

    public static <T, R> R[] toArray(Iterator<T> iterator, Class<R> aClass) {
        return toArray(iterator, aClass, new Identity<T, R>());
    }

    public static <T, R> R[] toArray(T[] collection, Class<R> aClass) {
        return toArray(ArrayIterator.iterator(collection), aClass);
    }

    public static <T, U> Collection<T> difference(Iterator<T> iterator,
                                                  Collection<U> collection,
                                                  BinaryPredicate<? super T, ? super U> predicate) {
        return copyIf(iterator, not(new Contained<T, U>(collection, predicate)));
    }

    public static <T, U> Collection<T> difference(Collection<T> first,
                                                  Collection<U> second,
                                                  BinaryPredicate<? super T, ? super U> predicate) {
        return difference(first.iterator(), second, predicate);
    }

    public static <T, U> Collection<T> intersection(Iterator<T> iterator,
                                                    Collection<U> collection,
                                                    BinaryPredicate<? super T, ? super U> predicate) {
        return copyIf(iterator, new Contained<T, U>(collection, predicate));
    }

    public static <T, U> Collection<T> intersection(Collection<T> first,
                                                    Collection<U> second,
                                                    BinaryPredicate<? super T, ? super U> predicate) {
        return intersection(first.iterator(), second, predicate);
    }

    public static <T> T lastElement(Iterator<T> iterator) {
        T ret = null;

        while (iterator.hasNext()) {
            ret = iterator.next();
        }

        return ret;
    }

    public static <T> T lastElement(Collection<T> collection) {
        return lastElement(collection.iterator());
    }

    public static <T extends Copyable<T>> List<T> clone(Iterator<T> iterator) {
        List<T> ret = new LinkedList<>();

        while (iterator.hasNext()) {
            ret.add(iterator.next().copy());
        }

        return ret;
    }

    public static <T extends Copyable<T>> List<T> clone(Collection<T> collection) {
        return clone(collection.iterator());
    }

    public static <T extends Copyable<T>> List<T> clone(T[] collection) {
        return clone(ArrayIterator.iterator(collection));
    }

    private Algorithms() {
    }

    public static class Contained<T, U>
            extends UnaryPredicate<T> {

        private final Collection<U> collection;
        private final BinaryPredicate<? super T, ? super U> predicate;

        private Contained(Collection<U> collection, BinaryPredicate<? super T, ? super U> predicate) {
            this.collection = collection;
            this.predicate = predicate;
        }

        public boolean evaluate(T value) {
            return find(this.collection, new BoundFirstUnaryPredicate<T, U>(this.predicate, value)) != null;
        }
    }
}
