/*
 * @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.io.Serializable;
import java.util.*;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import net.ranides.assira.collection.CollectionUtils;
import net.ranides.assira.collection.Swapper;
import net.ranides.assira.collection.arrays.NativeArrayAllocator;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.iterators.RandomAccessIterator;
import net.ranides.assira.functional.ProjectionFunction;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class ListUtils {
	
	private ListUtils() {
		/* utility class */
	}
	
	public static <T> Optional<T> first(List<T> values) {
		if(values.isEmpty()) {
			return Optional.empty();
		}
		return Optional.of(values.get(0));
	}
	
	public static <T> Optional<T> first(List<T> values, Predicate<? super T> predicate) {
		return CollectionUtils.first(values, predicate);
	}
	
	public static <T> Optional<T> first(List<T> values, int begin, int end, Predicate<? super T> predicate) {
		return first(values.subList(begin, end), predicate);
	}
	
	public static <T> Optional<T> last(List<T> values) {
		if(values.isEmpty()) {
			return Optional.empty();
		}
		return Optional.of(values.get(values.size()-1));
	}
	
	public static <T> Optional<T> last(List<T> values, Predicate<? super T> predicate) {
		ListIterator<T> iterator = values.listIterator(values.size());

        while(iterator.hasPrevious()) {
            T v = iterator.previous();
            if( predicate.test(v)) {
                return Optional.of(v);
            }
        }
        return Optional.empty();
	}
	
	public static <T> Optional<T> last(List<T> values, int begin, int end, Predicate<? super T> predicate) {
		return last(values.subList(begin, end), predicate);
	}
	
	public static <T> List<T> filter(List<T> values, Predicate<? super T> predicate) {
		return values.stream().filter(predicate).collect(Collectors.toList());
	}
    
    public static <T> List<T> limit(List<T> values, Predicate<? super T> predicate) {
        return IteratorUtils.collect(IteratorUtils.limit(values.iterator(), predicate), new ArrayList<>());
	}
	
	public static <S,T> List<T> map(List<S> values, Function<? super S,? extends 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 <S,T> List<T> map(List<S> values, ProjectionFunction<S,T> function) {
        return new VirtualList<T>() {
			@Override
			public T get(int index) {
				return function.apply(values.get(index));
			}

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

            @Override
            public T set(int index, T element) {
                S prev = values.set(index, function.invert(element));
                return null==prev ? null : function.apply(prev);
            }

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

            @Override
            public T remove(int index) {
                return function.apply(values.remove(index));
            }
            
		};
    }

    public static <Q> List<Q> produce(int size, IntFunction<Q> function) {
        return new VirtualList<Q>() {
            @Override
            public int size() {
                return size;
            }
            @Override
            public Q get(int index) {
                return function.apply(index);
            }
        };
    }  
	
	public static <T> List<T> sublist(List<T> list, int begin, int end) {
		NativeArrayAllocator.ensureFromTo(list.size(), begin, end);
		if(list instanceof RandomAccess) {
			return new RandomSubList<>(list, begin, end);
		} else {
			return new SubList<>(list, begin, end);
		}
	}
    
    public static <T> boolean equals(List<T> a, List<T> b, BiPredicate<T,T> predicate) {
        return (a.size() == b.size()) && IteratorUtils.equals(a.iterator(), b.iterator(), predicate);
    }
    
    public static <T> int compare(List<T> a, List<T> b, Comparator<? super T> cmp) {
        return IteratorUtils.compare(a.iterator(), b.iterator(), cmp);
    }

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

    public static <T> void swap(List<T> list, int a, int b) {
        T t = list.get(a);
        list.set(a, list.get(b));
        list.set(b, t);
    }
	
	private static class RandomSubList<T> extends SubList<T> implements RandomAccess {
		
		public RandomSubList(List<T> list, int begin, int end) {
			super(list, begin, end);
		}

		@Override
		public ListIterator<T> listIterator(int offset) {
			return new RandomAccessIterator<T>(offset){
				@Override
				protected int size() {
					return RandomSubList.this.size();
				}

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

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

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

                @Override
                protected void remove(int index) {
                    RandomSubList.this.remove(index);
                }
                
                
				
			};
		}
		
	}
	
	private static class SubList<T> extends AbstractList<T> implements Serializable {

        private static final long serialVersionUID = 2;

        protected final List<T> data;
        protected final int begin;
        protected int end;

        public SubList(List<T> list, int begin, int end) {
            this.data = list;
            this.begin = begin;
            this.end = end;
        }

        @Override
        public void add(int index, T value) {
            data.add(begin + index, value);
            end++;
        }

        @Override
        public boolean addAll(int index, Collection<? extends T> values) {
            if( data.addAll(begin + index, values) ) {
				end += values.size();
				return true;
			}
            return false;
        }

        @Override
        public T get(int index) {
            return data.get(begin + index);
        }

        @Override
        public T remove(int index) {
            T prev = data.remove(begin + index);
			end--;
			return prev;
        }

        @Override
        public T set(int index, T value) {
            return data.set(begin + index, value);
        }

        @Override
        public void clear() {
			super.clear();
			end = begin;
        }

        @Override
        public int size() {
            return end - begin;
        }

        @Override
        public ListIterator<T> listIterator(int index) {
            return new SIterator(index);
        }

        @Override
        public List<T> subList(int from, int to) {
			NativeArrayAllocator.ensureFromTo(size(), from, to);
            return new SubList<>(this, from, to);
        }

        protected final class SIterator implements ListIterator<T> {
			
			private final ListIterator<T> delegate;

			public SIterator(int index) {
				this.delegate = data.listIterator(begin + index);
			}

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

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

			@Override
			public boolean hasPrevious() {
				return delegate.previousIndex() >= begin;
			}

			@Override
			public T previous() {
				if(!hasPrevious()) {
					throw new NoSuchElementException();
				}
				return delegate.previous();
			}

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

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

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

			@Override
			public void set(T e) {
				delegate.set(e);
			}

			@Override
			public void add(T e) {
				delegate.add(e);
				end++;
			}
			
        }
    }

}
