/*
 * @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.AbstractList;
import java.util.Collection;
import java.util.List;
import java.util.RandomAccess;
import net.ranides.assira.collection.arrays.ArrayUtils;

/**
 * <p>
 * That implementation stores internally as small array as possible. It reallocates
 * memory after every operation. Empty list does not allocate any memory. 
 * Especially helpful for rarely modified collections with small number of elements,
 * or no elements at all.
 * </p><p>
 * Based on idea from {@link javax.swing.event.EventListenerList}.
 * </p>
 * @param <T>
 * @author Ranides Atterwim <ranides@gmail.com>
 */
@SuppressWarnings("unchecked")
public class RTList<T> extends AbstractList<T> implements RandomAccess, Serializable {
	
	private static final long serialVersionUID = 1L;

    private final static Object[] EMPTY = new Object[0];
	
    private Object[] content;

    public RTList() {
        content = EMPTY;
    }
	
	public RTList(T[] values) {
		content = new Object[values.length];
		ArrayUtils.copy(values, 0, content, 0, values.length);
	}

    public RTList(Collection<? extends T> values) {
        content = values.toArray();
    }

    @Override
    public T get(int index) {
        return (T)content[index];
    }

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

    @Override
    public void clear() {
        content = EMPTY;
    }

    @Override
    public T set(int index, T element) {
        T prev = (T)content[index];
        content[index] = element;
        return prev;
    }

    @Override
    public void add(int index, T element) {
        checkIndex(index);
        if(content == EMPTY) {
            content = new Object[]{ element };
            return;
        }
        merge(index, element);
    }

    @Override
    public boolean addAll(int index, Collection<? extends T> c) {
        checkIndex(index);
        if(c.isEmpty()) {
            return false;
        }
        if(content == EMPTY) {
            content = c.toArray();
            return true;
        }
        return merge(index, c.toArray());
    }
    
    public boolean addAll(int index, T[] array) {
        checkIndex(index);
        if(0 == array.length) {
            return false;
        }
        if(content == EMPTY) {
            content = ArrayUtils.copy(array);
            return true;
        }
        return merge(index, array);
    }
    
    private void merge(int index, T element) {
        Object[] buffer = new Object[content.length + 1];
        if(index > 0) {
            System.arraycopy(content, 0, buffer, 0, index);
        }
        if(index < content.length) {
            System.arraycopy(content, index, buffer, index+1, content.length - index);
        }
        buffer[index] = element;
        content = buffer;
    }

    private boolean merge(int index, Object[] array) {
        int n = array.length;
        Object[] buffer = new Object[content.length + n];
        
        if(index > 0) {
            System.arraycopy(content, 0, buffer, 0, index);
        }
        int tail = content.length - index;
        if(tail > 0) {
            System.arraycopy(content, index, buffer, index+n, tail);
        }
        System.arraycopy(array, 0, buffer, index, n);
        content = buffer;
        return true;
    }
    
    private void checkIndex(int index) throws IndexOutOfBoundsException {
        if(index > content.length) {
            throw new IndexOutOfBoundsException(index + " out of bound " + content.length);
        }
    }

    @Override
    public boolean addAll(Collection<? extends T> values) {
        return addAll(content.length, values);
    }
    
    public boolean addAll(T[] values) {
        return addAll(content.length, values);
    }

    @Override
    public T remove(int index) {
        checkIndex(index);
        T prev = (T)content[index];
        if(content.length == 1) {
            content = EMPTY;
            return prev;
        }
        Object[] buffer = new Object[content.length - 1];
        if(index > 0) {
            System.arraycopy(content, 0, buffer, 0, index);
        }
        if( content.length - index > 1) {
            System.arraycopy(content, index+1, buffer, index, content.length - index - 1);
        }

        content = buffer;
        return prev;
    }

	@Override
	public List<T> subList(int begin, int end) {
		return ListUtils.sublist(this, begin, end);
	}
	
}
