/*
 * @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.*;

/**
 * Klasa reprezentująca złączenie wielu list jako jedną kolekcję.
 * @param <T>
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public class FlatList<T> extends AbstractSequentialList<T> implements Serializable {
	
	private static final long serialVersionUID = 1L;

    private final LinkedList<List<T>> content;

    public FlatList() {
        content = new LinkedList<>();
    }
    
    @SafeVarargs
    public FlatList(List<T>... values) {
        this();
        joinAll(Arrays.asList(values));
    }
    
    public FlatList(List<List<T>> values) {
        this();
        joinAll(values);
    }
    
    /**
     * Dodaje do kolekcji kolejną listę. Uwaga: dodawana jest <b>lista</b>, a nie
     * jej elementy.
     * @param list
     * @return this
     */
    public FlatList<T> join(List<T> list) {
        content.add(list);
        return this;
    }
    
    /**
     * Dodaje do kolekcji kolejną listę. Uwaga: dodawana jest <b>lista</b>, a nie
     * jej elementy.
     * @param list
     * @return this
     */
    public FlatList<T> join(int index, List<T> list) {
        content.add(index, list);
        return this;
    }
    
    /***
     * Dodaje do kolekcji kolejne listy. Uwaga: dodawane są <b>listy</b>, a nie
     * ich elementy.
     * @param list
     * @return 
     */
    public final FlatList<T> joinAll(List<List<T>> list) {
        content.addAll(list);
        return this;
    }
    
    /**
     * Dodaje do kolekcji kolejne listy. Uwaga: dodawane są <b>listy</b>, a nie
     * ich elementy.
     * @param list
     * @return this
     */
    public final FlatList<T> joinAll(int index, List<List<T>> list) {
        content.addAll(index, list);
        return this;
    }

    @Override
    public int size() {
        int size = 0;
        for(List<T> list : content) {
            size += list.size();
        }
        return size;
    }

    @Override
    public boolean contains(Object value) {
        for(List<T> list : content) {
            if( list.contains(value) ) { return true; }
        }
        return false;
    }

    @Override
    public T get(int index) {
        int begin, end = 0;
        for(List<T> list : content) {
            begin = end;
            end += list.size();
            if(index < end) {
                return list.get(index - begin);
            }
        }
        throw new IndexOutOfBoundsException("index=" + index + " size=" + end );
    }
    
    @Override
    public void add(int index, T element) {
        int begin, end = 0;
        for(List<T> list : content) {
            begin = end;
            end += list.size();
            if(index < end) {
                list.add(index - begin, element);
                return;
            }
        }
		if(index == end) {
			content.getLast().add(element);
			return;
		}
		throw new IndexOutOfBoundsException("index=" + index + " size=" + end );
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> values) {
        int begin, end = 0;
        for(List<T> list : content) {
            begin = end;
            end += list.size();
            if(index < end) {
                return list.addAll(index - begin, values);
            }
        }
		if(index == end) {
			return content.getLast().addAll(values);
		}
		throw new IndexOutOfBoundsException("index=" + index + " size=" + end );
    }

    @Override
    public T remove(int index) {
        int begin, end = 0;
        for(List<T> list : content) {
            begin = end;
            end += list.size();
            if(index < end) {
                return list.remove(index - begin);
            }
        }
        throw new IndexOutOfBoundsException("index=" + index + " size=" + end );
    }

    

    @Override
    public T set(int index, T element) {
        int begin, end = 0;
        for(List<T> list : content) {
            begin = end;
            end += list.size();
            if(index < end) {
                return list.set(index - begin, element);
            }
        }
        throw new IndexOutOfBoundsException("index=" + index + " size=" + end );
    }

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

	@Override
	public List<T> subList(int begin, int end) {
		return ListUtils.sublist(this, begin, end);
	}
    
    private static final class JoinIterator<T> implements ListIterator<T> {
        
        private final ListIterator<List<T>> li;
        private List<T> cc;
        private ListIterator<T> ci;
        private int csize;
        private int begin;

        public JoinIterator(List<List<T>> data, int index) {
            li = data.listIterator();
            csize = 0;
			cc = Collections.emptyList();
            ci = Collections.emptyListIterator();
			
			while(li.hasNext()) {
				index -= csize;
				begin += csize;
                
				List<T> list = li.next();
                csize = list.size();
				
				if(index <= csize) {
					cc = list;
					ci = list.listIterator(index);
					return;
				}
			}
			throw new IndexOutOfBoundsException("index=" + index);
        }

        @Override
        public boolean hasNext() {
            if(ci.hasNext()) {
                return true;
            }
            while(li.hasNext()) {
                begin += csize;
                List<T> list = li.next();
                csize = list.size();
				cc = list;
				ci = list.listIterator();
                if(csize>0) {
                    return true;
                }
            }
            return false;
        }

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

        @Override
        public int nextIndex() {
            return begin + ci.nextIndex();
        }

        @Override
        public boolean hasPrevious() {
            return begin>0 || ci.hasPrevious();
        }

        @Override
        public T previous() {
            if(!hasPrevious()) {
                throw new NoSuchElementException();
            }
			while(!ci.hasPrevious()) {
                List<T> list = li.previous();
				
				if(cc == list) { // NOPMD
					list = li.previous();
				}
				
				csize = list.size();
				begin -= csize;
                ci = list.listIterator(list.size());
			}
            return ci.previous();
        }

        @Override
        public int previousIndex() {
            return begin + ci.previousIndex();
        }

        @Override
        public void remove() {
            ci.remove();
            csize--;
        }

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

        @Override
        public void add(T e) {
            if(ci==null && !hasNext()) {
                throw new IllegalStateException("JoinList must contain at least one list if you want to add elements");
            }
            ci.add(e);
            csize++;
        }
        
    }
    
}
