/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.iterators;

import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import net.ranides.assira.functional.EachFunction;
import net.ranides.assira.generic.CompareUtils;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class EnumerationUtils {

    private EnumerationUtils() {
        /* utility class */
    }

    public static <T> Enumeration<T> enumeration(Iterator<T> values) {
        return new Enumeration<T>() {
            @Override
            public boolean hasMoreElements() {
                return values.hasNext();
            }

            @Override
            public T nextElement() {
                return values.next();
            }
        };
    }

    public static <T> Iterator<T> iterator(Enumeration<T> values) {
        return new Iterator<T>() {
            @Override
            public boolean hasNext() {
                return values.hasMoreElements();
            }

            @Override
            public T next() {
                return values.nextElement();
            }
        };
    }
    
    public static <T> Optional<T> first(Enumeration<T> iterator) {
		if(iterator.hasMoreElements()) {
			return Optional.of(iterator.nextElement());
		}
        return Optional.empty();
    }
    
    public static <T> Optional<T> first(Enumeration<T> iterator, Predicate<? super T> predicate) {
        while(iterator.hasMoreElements()) {
            T v = iterator.nextElement();
            if( predicate.test(v)) {
                return Optional.of(v);
            }
        }
        return Optional.empty();
    }
    
    public static <T> Optional<T> last(Enumeration<T> iterator) {
		if(!iterator.hasMoreElements()) {
			return Optional.empty();
		}
        T result = null;
        while(iterator.hasMoreElements()) {
            result = iterator.nextElement();
        }
        return Optional.of(result);
    }
    
    public static <T> Optional<T> last(Enumeration<T> iterator, Predicate<? super T> predicate) {
        boolean found = false;
		T result = null;
        while(iterator.hasMoreElements()) {
            T v = iterator.nextElement();
            if(predicate.test(v)) {
                result = v;
				found = true;
            }
        }
		if(!found) {
			return Optional.empty();
		}
        return Optional.of(result);
    }
    
    public static int size(Enumeration<?> iterator) {
		if(iterator==null) {
			return 0;
		}
        int c = 0;
        while(iterator.hasMoreElements()) {
            c++;
            iterator.nextElement();
        }
        return c;
    }

    public static <T> Iterator<T> filter(Enumeration<T> iterator, Predicate<? super T> predicate) {
        return new FilterIterator<>(iterator, predicate, false);
    }

    public static <T> Iterator<T> limit(Enumeration<T> iterator, Predicate<? super T> predicate) {
        return new FilterIterator<>(iterator, predicate, true);
    }
	
    public static <S,T> Iterator<T> map(Enumeration<S> iterator, EachFunction<? super S,? extends T> function) {
        return new EachAdapter<>(iterator, function);
    }
	
	public static <S,T> Iterator<T> map(Enumeration<S> iterator, Function<? super S,? extends T> function) {
        return new Adapter<>(iterator, function);
    }

    public static <T> Iterator<T> flat(Enumeration<Enumeration<T>> iterator) {
        return new FlatIterator<>(iterator);
    }

	public static <T> Stream<T> stream(Enumeration<T> iterator) {
		return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(iterator), 0), false);
	}
    
    public static <T, C extends Collection<? super T>> C collect(Enumeration<T> iterator, C target) {
        while(iterator.hasMoreElements()) {
            target.add(iterator.nextElement());
        }
        return target;
	}

	public static <T> int next(Enumeration<T> iterator, int count) {
		int i=0;
		for(; i<count && iterator.hasMoreElements(); i++) {
			iterator.nextElement();
		}
		return i;
	}
    
    public static <T> boolean equals(Enumeration<T> a, Enumeration<T> b) {
        return equals(a, b, CompareUtils::equals);
    }
    
    public static <T> boolean equals(Enumeration<T> a, Enumeration<T> b, BiPredicate<T,T> predicate) {
        while(a.hasMoreElements() && b.hasMoreElements()) {
            if( !predicate.test(a.nextElement(), b.nextElement())) {
                return false;
            }
        }
        return !a.hasMoreElements() && !b.hasMoreElements();
    }
    
    public static <T> int compare(Enumeration<T> a, Enumeration<T> b) {
        return compare(a, b, CompareUtils.comparator());
    }
    
    public static <T> int compare(Enumeration<T> a, Enumeration<T> b, Comparator<? super T> cmp) {
        while(a.hasMoreElements() && b.hasMoreElements()) {
            int icmp = cmp.compare(a.nextElement(), b.nextElement());
            if(icmp !=0) {
                return icmp;
            }
        }
        return a.hasMoreElements() ? +1 : b.hasMoreElements() ? -1 : 0;
    }
    
    private static final class Adapter<S, T> implements Iterator<T> {
    
        protected final Enumeration<S> delegate;
        
        protected final Function<? super S,? extends T> function;

        protected <E> Adapter(Enumeration<S> delegate, Function<? super S,? extends T> function) {
            this.delegate = delegate;
            this.function = function;
        }

        @Override
        public boolean hasNext() {
            return delegate.hasMoreElements();
        }

        @Override
        public T next() {
            return function.apply(delegate.nextElement());
        }

    }    
    
    private static final class EachAdapter<S, T> implements Iterator<T> {
    
        protected final Enumeration<S> delegate;
        
        protected final EachFunction<? super S,? extends T> function;
        
        protected int index;

        protected <E> EachAdapter(Enumeration<S> delegate, EachFunction<? super S,? extends T> function) {
            this.delegate = delegate;
            this.function = function;
            this.index = 0;
        }

        @Override
        public boolean hasNext() {
            return delegate.hasMoreElements();
        }

        @Override
        public T next() {
            return function.apply(index++, delegate.nextElement());
        }

    } 

    private static class FilterIterator<T> implements Iterator<T> {

        private final Enumeration<T> iterator;
        private final Predicate<? super T> predicate;
        private final boolean limit;
        private T next;
        private boolean hasNext;
        
        public FilterIterator(Enumeration<T> iterator, Predicate<? super T> predicate, boolean limit) {
            this.iterator = iterator;
			this.predicate = predicate;
            this.limit = limit;
            nextMatch();
        }

        @Override
        public boolean hasNext() {
            return hasNext;
        }

        @Override
        public T next() {
            if (!hasNext) {
                throw new NoSuchElementException();
            }
            return nextMatch();
        }

        private T nextMatch() {
            T last = next;

            while (iterator.hasMoreElements()) {
                T value = iterator.nextElement();
                if (predicate.test(value)) {
                    hasNext = true;
                    next = value;
                    return last;
                } else if(limit) {
                    break;
                }
            }

            hasNext = false;
            return last;
        }

        @Override
        public void remove() {
            // impossible to implement both "hasNext" & "mutable methods" reliably
            throw new UnsupportedOperationException();
        }

    }

    private static final class FlatIterator<T> implements Iterator<T> {

        private final Enumeration<Enumeration<T>> li;
        private Enumeration<T> ci;

        public FlatIterator(Enumeration<Enumeration<T>> data) {
            li = data;
            ci = Collections.emptyEnumeration();
        }

        @Override
        public boolean hasNext() {
            if(ci.hasMoreElements()) {
                return true;
            }
            while(li.hasMoreElements()) {
                ci = li.nextElement();
                if(ci.hasMoreElements()) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public T next() {
            if(!hasNext()) {
                throw new NoSuchElementException();
            }
            return ci.nextElement();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

    }
    
}
