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

package net.ranides.assira.collection.lists;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.RandomAccess;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.IntUnaryOperator;
import net.ranides.assira.collection.IntCollectionUtils;
import net.ranides.assira.collection.Swapper;
import net.ranides.assira.collection.arrays.NativeArrayAllocator;
import net.ranides.assira.collection.iterators.IntListIterator;
import net.ranides.assira.collection.iterators.RandomAccessIntIterator;
import net.ranides.assira.functional.IntProjectionFunction;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class IntListUtils {
	
	private IntListUtils() {
		/* utility class */
	}
	
	public static int first(IntList values) {
		if(values.isEmpty()) {
			throw new NoSuchElementException();
		}
		return values.getInt(0);
	}
	
	public static int first(IntList values, IntPredicate predicate) {
		return IntCollectionUtils.first(values, predicate);
	}
	
	public static int first(IntList values, int begin, int end, IntPredicate predicate) {
		return first(values.subList(begin, end), predicate);
	}
	
	public static int last(IntList values) {
		if(values.isEmpty()) {
			throw new NoSuchElementException();
		}
		return values.getInt(values.size()-1);
	}
	
	public static int last(IntList values, IntPredicate predicate) {
		IntListIterator iterator = values.listIterator(values.size());

        while(iterator.hasPrevious()) {
            int v = iterator.previousInt();
            if( predicate.test(v)) {
                return v;
            }
        }
        throw new NoSuchElementException();
	}
	
	public static int last(IntList values, int begin, int end, IntPredicate predicate) {
		return last(values.subList(begin, end), predicate);
	}
	
	public static IntList filter(IntList values, IntPredicate predicate) {
		IntList ret = new IntArrayList();
		for(int i=0,n=values.size(); i<n; i++) {
			int v = values.getInt(i);
			if(predicate.test(v)) {
				ret.add(v);
			}
		}
		return ret;
	}
	
	public static <T> List<T> map(IntList values, IntFunction<T> function) {
		return new VirtualList<T>() {
			
			private static final long serialVersionUID = 1L;
			
			@Override
			public T get(int index) {
				return function.apply(values.get(index));
			}

			@Override
			public int size() {
				return values.size();
			}
			
			@Override
            public T remove(int index) {
                return function.apply(values.remove(index));
            }
		};
    }
	
	public static IntList map(IntList values, IntUnaryOperator function) {
		return new AIntList() {
			
			private static final long serialVersionUID = 1L;
			
			@Override
			public int getInt(int index) {
				return function.applyAsInt(values.get(index));
			}

			@Override
			public int size() {
				return values.size();
			}

			@Override
			public void add(int index, int k) {
				throw new UnsupportedOperationException();
			}

			@Override
			public int removeInt(int index) {
				return function.applyAsInt(values.removeInt(index));
			}

			@Override
			public int set(int index, int k) {
				throw new UnsupportedOperationException();
			}
		};
    }

    public static <R> IntList map(IntList values, IntProjectionFunction function) {
		return new AIntList() {
			
			private static final long serialVersionUID = 1L;
			
			@Override
			public int getInt(int index) {
				return function.applyAsInt(values.get(index));
			}

			@Override
			public int size() {
				return values.size();
			}

            @Override
            public int set(int index, int element) {
                return function.applyAsInt(values.set(index, function.invert(element)));
            }

            @Override
            public void add(int index, int element) {
                values.add(index, function.invert(element));
            }

            @Override
            public int removeInt(int index) {
                return function.applyAsInt(values.remove(index));
            }
            
		};
    }

    public static IntList produce(int size, IntUnaryOperator function) {
        return new IntVirtualList() {
			
			private static final long serialVersionUID = 1L;
			
            @Override
            public int size() {
                return size;
            }
            @Override
            public int getInt(int index) {
                return function.applyAsInt(index);
            }
        };
    } 
    
    public static IntList sublist(IntList list, int begin, int end) {
		NativeArrayAllocator.ensureFromTo(list.size(), begin, end);
		if(list instanceof RandomAccess) {
			return new RandomSubList(list, begin, end);
		} else {
			return new AIntList.SubList(list, begin, end);
		}
	}

	public static Swapper swapper(IntList list) {
	    return (a,b) -> swap(list,a,b);
    }

	public static void swap(IntList list, int a, int b) {
		int t = list.getInt(a);
		list.set(a, list.getInt(b));
		list.set(b, t);
	}
	
	private static class RandomSubList extends AIntList.SubList implements RandomAccess {

		private static final long serialVersionUID = 1L;
		
		public RandomSubList(IntList list, int begin, int end) {
			super(list, begin, end);
		}

		@Override
		public IntListIterator listIterator(int offset) {
			return new RandomAccessIntIterator(offset){
				@Override
				protected int size() {
					return RandomSubList.this.size();
				}

				@Override
				protected int get(int index) {
					return RandomSubList.this.get(index);
				}

                @Override
                protected void set(int index, int value) {
                    RandomSubList.this.set(index, value);
                }

                @Override
                protected void add(int index, int value) {
                    RandomSubList.this.add(index, value);
                }

                @Override
                protected void remove(int index) {
                    RandomSubList.this.remove(index);
                }
				
			};
		}
		
	}
	
}
