/*
 * @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.Iterator;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
import net.ranides.assira.collection.IntCollection;
import net.ranides.assira.collection.IntComparator;
import net.ranides.assira.functional.IntBiPredicate;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class IntIteratorUtils {
    
    private IntIteratorUtils() {
        /* utility class */
    }
    
    public static int first(IntIterator iterator) {
		return iterator.next();
    }
    
    public static int first(IntIterator iterator, IntPredicate predicate) {
		while(iterator.hasNext()) {
            int v = iterator.nextInt();
            if( predicate.test(v)) {
                return v;
            }
        }
        throw new NoSuchElementException();
    }
    
    public static int last(IntIterator iterator) {
        int result;
		do {
			result = iterator.nextInt();
		} while(iterator.hasNext());
        return result;
		
    }
    
    public static int last(IntIterator iterator, IntPredicate predicate) {
        Integer result = null;
        while(iterator.hasNext()) { 
            int v = iterator.nextInt(); 
            if(predicate.test(v)) {
                result = v;
            }
        }
		if(null == result) {
			throw new NoSuchElementException();
		}
        return result;
    }
    
    public static int size(IntIterator iterator) {
		if(iterator==null) {
			return 0;
		}
        int c = 0;
        while(iterator.hasNext()) {
            c++;
            iterator.nextInt();
        }
        return c;
    }

    public static IntIterator filter(IntIterator iterator, IntPredicate predicate) {
        return new FilterIterator(iterator, predicate, false);
    }
	
	public static IntListIterator filter(IntListIterator iterator, IntPredicate predicate) {
        return new FilterListIterator(iterator, predicate, false);
    }
    
    public static IntIterator limit(IntIterator iterator, IntPredicate predicate) {
        return new FilterIterator(iterator, predicate, true);
    }
	
	public static IntListIterator limit(IntListIterator iterator, IntPredicate predicate) {
        return new FilterListIterator(iterator, predicate, true);
    }
	
	public static <T> Iterator<T> map(IntIterator iterator, IntFunction<T> function) {
        return new Adapter<>(iterator, function);
    }
    
    public static IntIterator map(IntIterator iterator, IntUnaryOperator function) {
        return new IntAdapter(iterator, function);
    }
    
    public static <T> ListIterator<T> map(IntListIterator iterator, IntFunction<T> function) {
        return new ListAdapter<>(iterator, function);
    }
    
    public static IntListIterator map(IntListIterator iterator, IntUnaryOperator function) {
        return new IntListAdapter(iterator, function);
    }
    
    public static IntIterator flat(Iterator<IntIterator> iterator) {
        return new IntFlatIterator(iterator);
    }
    
    public static IntStream stream(IntIterator iterator) {
		return StreamSupport.intStream(new IntSpliterator(iterator), false);
	}
    
    public static <C extends IntCollection> C collect(IntIterator iterator, C target) {
        while(iterator.hasNext()) {
            target.add(iterator.next());
        }
        return target;
	}
    
    public static IntIterator reverse(IntListIterator iterator) {
		return new IntIteratorUtils.ReverseIterator(iterator);
	}
    
	public static int next(IntIterator iterator, int count) {
		int i=0;
		for(; i<count && iterator.hasNext(); i++) {
			iterator.nextInt();
		}
		return i;
	}
    
    public static boolean equals(IntIterator a, IntIterator b) {
        return equals(a, b, (p,q) -> p==q);
    }
    
    public static boolean equals(IntIterator a, IntIterator b, IntBiPredicate predicate) {
        while(a.hasNext() && b.hasNext()) {
            if( !predicate.test(a.nextInt(), b.nextInt())) {
                return false;
            }
        }
        return !a.hasNext() && !b.hasNext();
    }
    
    public static <T> int compare(IntIterator a, IntIterator b) {
        return compare(a, b, Integer::compare);
    }
    
    public static <T> int compare(IntIterator a, IntIterator b, IntComparator cmp) {
        while(a.hasNext() && b.hasNext()) {
            int icmp = cmp.compare(a.nextInt(), b.nextInt());
            if(icmp !=0) {
                return icmp;
            }
        }
        return a.hasNext() ? +1 : b.hasNext() ? -1 : 0;
    }
    
    public static void remove(IntIterator iterator, IntPredicate predicate) {
        while(iterator.hasNext()) {
            int v = iterator.next();
            if(predicate.test(v)) {
                iterator.remove();
            }
        }
    }
    
    private static final class IntSpliterator implements Spliterator.OfInt {
        
        private final IntIterator iterator;

        public IntSpliterator(IntIterator iterator) {
            this.iterator = iterator;
        }

        @Override
        public OfInt trySplit() {
            return null;
        }

        @Override
        public boolean tryAdvance(IntConsumer action) {
            if (iterator.hasNext()) {
                action.accept(iterator.nextInt());
                return true;
            }
            return false;
        }

        @Override
        public boolean tryAdvance(Consumer<? super Integer> action) {
            return tryAdvance((int n) -> action.accept(n));
        }

        @Override
        public long estimateSize() {
            return Long.MAX_VALUE;
        }

        @Override
        public int characteristics() {
            return 0;
        }

    }

    
    private static final class Adapter<T> implements Iterator<T> {
    
        protected final IntIterator delegate;
        
        protected final IntFunction<T> function;

        protected <E> Adapter(IntIterator delegate, IntFunction<T> function) {
            this.delegate = delegate;
            this.function = function;
        }

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

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

        @Override
        public void remove() {
            delegate.remove();
        }
        
    }    
    
    private static final class IntAdapter implements IntIterator {
    
        protected final IntIterator delegate;
        
        protected final IntUnaryOperator function;

        protected IntAdapter(IntIterator delegate, IntUnaryOperator function) {
            this.delegate = delegate;
            this.function = function;
        }

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

        @Override
        public int nextInt() {
            return function.applyAsInt(delegate.nextInt());
        }
        
        @Override
        public void remove() {
            delegate.remove();
        }
    } 
    
    private static final class ListAdapter<T> implements ListIterator<T> {
    
        protected final IntListIterator delegate;
        
        protected final IntFunction<T> function;

        protected <E> ListAdapter(IntListIterator delegate, IntFunction<T> function) {
            this.delegate = delegate;
            this.function = function;
        }
        
        @Override
        public T next() {
            return function.apply(delegate.nextInt());
        }
        
        @Override
        public T previous() {
            return function.apply(delegate.previousInt());
        }

        @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 IntListAdapter implements IntListIterator {

        protected final IntListIterator delegate;
        
        protected final IntUnaryOperator function;

        protected <E> IntListAdapter(IntListIterator delegate, IntUnaryOperator function) {
            this.delegate = delegate;
            this.function = function;
        }
        
        @Override
        public int nextInt() {
            return function.applyAsInt(delegate.nextInt());
        }


        @Override
        public int previousInt() {
            return function.applyAsInt(delegate.previousInt());
        }

        @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 add(int value) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void set(int value) {
            throw new UnsupportedOperationException();
        }
        
    }
    
    private static class FilterIterator implements IntIterator {

        private final IntIterator iterator;
        private final IntPredicate predicate;
        private final boolean limit;
        private int next;
        private boolean hasNext;

        public FilterIterator(IntIterator iterator, IntPredicate predicate, boolean limit) {
            this.iterator = iterator;
			this.predicate = predicate;
            this.limit = limit;
            nextMatch();
        }

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

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

        private int nextMatch() {
            int last = next;

            while (iterator.hasNext()) {
                int value = iterator.nextInt();
                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 implements IntListIterator {

        private final IntListIterator iterator;
        private final IntPredicate predicate;
        private final boolean limit;
		int index;
		private int next;
        private boolean hasNext;

        public FilterListIterator(IntListIterator iterator, IntPredicate 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 int nextInt() {
            if (!hasNext) {
                throw new NoSuchElementException();
            }
			index++;
            return nextMatch();
        }
		
		@Override
        public int previousInt() {
            if (index<=0) {
                throw new NoSuchElementException();
            }
			index--;
            return prevMatch();
        }

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

            hasNext = false;
            return ilast;
        }
		
		private int prevMatch() {
            while (iterator.hasPrevious()) {
                int 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(int e) {
			// impossible to implement both "hasNext" & "mutable methods" reliably
            throw new UnsupportedOperationException();
		}

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

    }
    
    private static class ReverseIterator implements IntIterator {
		
		private final IntListIterator iterator;

		public ReverseIterator(IntListIterator iterator) {
			this.iterator = iterator;
		}

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

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

		@Override
		public int nextInt() {
			return iterator.previous();
		}

	}
    
    private static final class IntFlatIterator implements IntIterator {

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

        public IntFlatIterator(Iterator<IntIterator> data) {
            li = data;
            ci = IntCollection.EMPTY.iterator();
        }

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

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

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

    }
    
}
