/*
 * @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 IteratorUtils {
    
    private IteratorUtils() {
        /* utility class */
    }
    
    public static <T> Optional<T> first(Iterator<T> iterator) {
		if(iterator.hasNext()) {
			return Optional.of(iterator.next());
		}
        return Optional.empty();
    }
    
    public static <T> Optional<T> first(Iterator<T> iterator, Predicate<? super T> predicate) {
        while(iterator.hasNext()) {
            T v = iterator.next();
            if( predicate.test(v)) {
                return Optional.of(v);
            }
        }
        return Optional.empty();
    }
    
    public static <T> Optional<T> last(Iterator<T> iterator) {
		if(!iterator.hasNext()) {
			return Optional.empty();
		}
        T result = null;
        while(iterator.hasNext()) { 
            result = iterator.next(); 
        }
        return Optional.of(result);
    }
    
    public static <T> Optional<T> last(Iterator<T> iterator, Predicate<? super T> predicate) {
        boolean found = false;
		T result = null;
        while(iterator.hasNext()) { 
            T v = iterator.next(); 
            if(predicate.test(v)) {
                result = v;
				found = true;
            }
        }
		if(!found) {
			return Optional.empty();
		}
        return Optional.of(result);
    }
    
    public static int size(Iterator<?> iterator) {
		if(iterator==null) {
			return 0;
		}
        int c = 0;
        while(iterator.hasNext()) {
            c++;
            iterator.next();
        }
        return c;
    }

    public static <T> Iterator<T> filter(Iterator<T> iterator, Predicate<? super T> predicate) {
        return new FilterIterator<>(iterator, predicate, false);
    }
	
	public static <T> ListIterator<T> filter(ListIterator<T> iterator, Predicate<? super T> predicate) {
        return new FilterListIterator<>(iterator, predicate, false);
    }
    
    public static <T> Iterator<T> limit(Iterator<T> iterator, Predicate<? super T> predicate) {
        return new FilterIterator<>(iterator, predicate, true);
    }
	
	public static <T> ListIterator<T> limit(ListIterator<T> iterator, Predicate<? super T> predicate) {
        return new FilterListIterator<>(iterator, predicate, true);
    }
    
    public static <S,T> Iterator<T> map(Iterator<S> iterator, EachFunction<? super S,? extends T> function) {
        return new EachAdapter<>(iterator, function);
    }
	
	public static <S,T> Iterator<T> map(Iterator<S> iterator, Function<? super S,? extends T> function) {
        return new Adapter<>(iterator, function);
    }
    
    public static <S,T> ListIterator<T> map(ListIterator<S> iterator, EachFunction<S,T> function) {
        return new EachListAdapter<>(iterator, function);
    }
    
    public static <S,T> ListIterator<T> map(ListIterator<S> iterator, Function<? super S,? extends T> function) {
        return new ListAdapter<>(iterator, function);
    }
    
    public static <T> Iterator<T> flat(Iterator<Iterator<T>> iterator) {
        return new FlatIterator<>(iterator);
    }
    
	public static <T> Stream<T> stream(Iterator<T> iterator) {
		return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
	}
    
    public static <T, C extends Collection<? super T>> C collect(Iterator<T> iterator, C target) {
        while(iterator.hasNext()) {
            target.add(iterator.next());
        }
        return target;
	}
	
    /**
     * Nie da się stworzyć odwróconego ListIterator ponieważ indeksowanie się położy, operacja
     * #add będzie wstawiać na nieprawidłowej pozycji. Operacja #set w zasadzie ma szanse działać. 
     * Ale to za mało, żeby zwracać interfejs, którego kontraktu w 90% nie dotrzymujemy.
     * @param <T>
     * @param iterator
     * @return 
     */
	public static <T> Iterator<T> reverse(ListIterator<T> iterator) {
		return new ReverseIterator<>(iterator);
	}
	
	public static <T> int next(Iterator<T> iterator, int count) {
		int i=0;
		for(; i<count && iterator.hasNext(); i++) {
			iterator.next();
		}
		return i;
	}
    
    public static <T> boolean equals(Iterator<T> a, Iterator<T> b) {
        return equals(a, b, CompareUtils::equals);
    }
    
    public static <T> boolean equals(Iterator<T> a, Iterator<T> b, BiPredicate<T,T> predicate) {
        while(a.hasNext() && b.hasNext()) {
            if( !predicate.test(a.next(), b.next())) {
                return false;
            }
        }
        return !a.hasNext() && !b.hasNext();
    }
    
    public static <T> int compare(Iterator<T> a, Iterator<T> b) {
        return compare(a, b, CompareUtils.comparator());
    }
    
    public static <T> int compare(Iterator<T> a, Iterator<T> b, Comparator<? super T> cmp) {
        while(a.hasNext() && b.hasNext()) {
            int icmp = cmp.compare(a.next(), b.next());
            if(icmp !=0) {
                return icmp;
            }
        }
        return a.hasNext() ? +1 : b.hasNext() ? -1 : 0;
    }
    
    public static <T> void remove(Iterator<T> iterator, Predicate<? super T> predicate) {
        while(iterator.hasNext()) {
            T v = iterator.next();
            if(predicate.test(v)) {
                iterator.remove();
            }
        }
    }

    private static final class Adapter<S, T> implements Iterator<T> {
    
        protected final Iterator<S> delegate;
        
        protected final Function<? super S,? extends T> function;

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

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

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

        @Override
        public void remove() {
            delegate.remove();
        }
        
    }    
    
    private static final class EachAdapter<S, T> implements Iterator<T> {
    
        protected final Iterator<S> delegate;
        
        protected final EachFunction<? super S,? extends T> function;
        
        protected int index;

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

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

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

        @Override
        public void remove() {
            delegate.remove();
        }
        
    } 
    
    private static final class ListAdapter<S, T> implements ListIterator<T> {
    
        protected final ListIterator<S> delegate;
        
        protected final Function<? super S,? extends T> function;

        protected <E> ListAdapter(ListIterator<S> delegate, Function<? super S,? extends T> function) {
            this.delegate = delegate;
            this.function = function;
        }
        
        @Override
        public T next() {
            return function.apply(delegate.next());
        }
        
        @Override
        public T previous() {
            return function.apply(delegate.previous());
        }

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

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

        @Override
        public int nextIndex() {
            return delegate.nextIndex();
        }

        @Override
        public int previousIndex() {
            return delegate.previousIndex();
        }

        @Override
        public void remove() {
            delegate.remove();
        }

        @Override
        public void set(T value) {
            throw new UnsupportedOperationException("");
        }

        @Override
        public void add(T value) {
            throw new UnsupportedOperationException("");
        }
        
    }
    
    private static final class EachListAdapter<S, T> implements ListIterator<T> {
    
        protected final ListIterator<S> delegate;
        
        protected final EachFunction<? super S,? extends T> function;

        protected <E> EachListAdapter(ListIterator<S> delegate, EachFunction<? super S,? extends T> function) {
            this.delegate = delegate;
            this.function = function;
        }
        
        @Override
        public T next() {
            
            return function.apply(delegate.nextIndex(), delegate.next());
        }
        
        @Override
        public T previous() {
            return function.apply(delegate.previousIndex(), delegate.previous());
        }

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

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

        @Override
        public int nextIndex() {
            return delegate.nextIndex();
        }

        @Override
        public int previousIndex() {
            return delegate.previousIndex();
        }

        @Override
        public void remove() {
            delegate.remove();
        }

        @Override
        public void set(T value) {
            throw new UnsupportedOperationException("");
        }

        @Override
        public void add(T value) {
            throw new UnsupportedOperationException("");
        }
        
    }
    
    private static class FilterIterator<T> implements Iterator<T> {

        private final Iterator<T> iterator;
        private final Predicate<? super T> predicate;
        private final boolean limit;
        private T next;
        private boolean hasNext;
        
        public FilterIterator(Iterator<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.hasNext()) {
                T value = iterator.next();
                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 class FilterListIterator<T> implements ListIterator<T> {

        private final ListIterator<T> iterator;
        private final Predicate<? super T> predicate;
        private final boolean limit;
		int index;
		private T next;
        private boolean hasNext;

        public FilterListIterator(ListIterator<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 boolean hasPrevious() {
            return index>0;
        }

        @Override
        public T next() {
            if (!hasNext) {
                throw new NoSuchElementException();
            }
			index++;
            return nextMatch();
        }
		
		@Override
        public T previous() {
            if (index<=0) {
                throw new NoSuchElementException();
            }
			index--;
            return prevMatch();
        }

        private T nextMatch() {
            T ilast = next;
			
            while (iterator.hasNext()) {
                T value = iterator.next();
                if (predicate.test(value)) {
                    hasNext = true;
                    next = value;
                    return ilast;
                } else if(limit) {
                    break;
                }
            }

            hasNext = false;
            return ilast;
        }
		
		private T prevMatch() {
            while (iterator.hasPrevious()) {
                T value = iterator.previous();
                if (predicate.test(value)) {
                    hasNext = true;
                    next = value;
                    return next;
                }
            }
            throw new AssertionError("unreachable code");
        }

		
		@Override
		public int nextIndex() {
			return index;
		}
		
		@Override
		public int previousIndex() {
			return index-1;
		}

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

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

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

    }
    
	private static class ReverseIterator<T> implements Iterator<T> {
		
		private final ListIterator<T> iterator;

		public ReverseIterator(ListIterator<T> iterator) {
			this.iterator = iterator;
		}

		@Override
		public void remove() {
			iterator.remove();
		}

		@Override
		public boolean hasNext() {
			return iterator.hasPrevious();
		}

		@Override
		public T next() {
			return iterator.previous();
		}

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

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

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

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

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

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

    }
    
}
