/*
 * @author Ranides Atterwim {@literal <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.stream.Stream;

/**
 * Dynamic view with elements from merged lists.
 * It supports all modifications, including adding new elements:
 * target list is selected based on the index (we know which list covers which index range).
 *
 * Because we know type of merged collections, we can provide better functionality and performance that FlatCollection.
 *
 * We extend {@link AbstractSequentialList} because even {@link #get} or {@link #size} require calculations
 * proportional to number of merged lists.
 *
 * @param <T> T
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class FlatList<T> extends AbstractSequentialList<T> implements Serializable {
	
	private static final long serialVersionUID = 1L;

    private final LinkedList<List<T>> content;

    /**
     * Creates empty list
     */
    public FlatList() {
        content = new LinkedList<>();
    }

    /**
     * Creates merged view of provided lists
     * @param values values
     */
    @SafeVarargs
    public FlatList(List<T>... values) {
        this();
        content.addAll(Arrays.asList(values));
    }

    /**
     * Creates merged view of provided lists
     * @param values values
     */
    public FlatList(Stream<List<T>> values) {
        this();
        values.forEachOrdered(content::add);
    }

    /**
     * Creates merged view of provided lists
     * @param values values
     */
    public FlatList(Collection<List<T>> values) {
        this();
        content.addAll(values);
    }

    /**
     * Inserts list at the end of merged view.
     * Please note that it stores container, not actual elements from it.
     *
     * @param list list
     * @return this
     */
    public FlatList<T> join(List<T> list) {
        content.add(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++;
        }
        
    }
    
}
