/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.shared_core.structure;

import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;

/**
 * An array list that has a null array backing it when created and cleared.
 *
 * @author Fabio
 */
public class LowMemoryArrayList<E> implements List<E> {

    private transient E[] data;
    private int size;

    public LowMemoryArrayList() {

    }

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

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    @Override
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public Iterator<E> iterator() {
        return new Iterator() {
            private int curr;

            @Override
            public boolean hasNext() {
                if (data == null) {
                    return false;
                }
                return curr < size;
            }

            @Override
            public Object next() {
                E e = data[curr];
                curr++;
                return e;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    @Override
    public Object[] toArray() {
        Object[] result = new Object[size];
        if (data != null) {
            System.arraycopy(data, 0, result, 0, size);
        }
        return result;
    }

    public E[] internalArray() {
        return this.data;
    }

    @Override
    public <T> T[] toArray(T[] a) {
        if (a.length < size) {
            a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
        }
        if (data != null) {
            System.arraycopy(data, 0, a, 0, size);
        }
        if (a.length > size) {
            a[size] = null;
        }
        return a;
    }

    public void sortAndTrim(Comparator<? super E> comparator) {
        trim();
        if (size > 1) {
            //Only need to sort it if we have more than 1 element
            Arrays.sort(data, comparator);
        }
    }

    public void trim() {
        if (size == 0) {
            data = null;

        } else if (size > 0) {
            //Trim if needed
            int oldCapacity = data.length;
            if (size < oldCapacity) {
                Object oldData[] = data;
                data = (E[]) new Object[size];
                System.arraycopy(oldData, 0, data, 0, size);
            }
        }
    }

    public void ensureCapacity(int minCapacity) {
        if (data == null) {
            if (minCapacity < 3) {
                minCapacity = 3;
            }
            data = (E[]) new Object[minCapacity];
            return;
        }
        int oldCapacity = data.length;
        if (minCapacity > oldCapacity) {
            Object oldData[] = data;
            int newCapacity = (oldCapacity * 3) / 2 + 1;
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }
            data = (E[]) new Object[newCapacity];
            System.arraycopy(oldData, 0, data, 0, size);
        }
    }

    @Override
    public boolean add(E o) {
        ensureCapacity(size + 1); // Increments modCount!!
        data[size++] = o;
        return true;
    }

    @Override
    public boolean remove(Object o) {
        if (data == null) {
            return false;
        }
        if (o == null) {
            for (int index = 0; index < size; index++) {
                if (data[index] == null) {
                    fastRemove(index);
                    return true;
                }
            }
        } else {
            for (int index = 0; index < size; index++) {
                if (o.equals(data[index])) {
                    fastRemove(index);
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(data, index + 1, data, index, numMoved);
        }
        data[--size] = null; // Let gc do its work
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacity(size + numNew); // Increments modCount
        System.arraycopy(a, 0, data, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public void clear() {
        if (data == null) {
            return;
        }
        data = null; //Let the GC work!

        size = 0;
    }

    @Override
    public E get(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        //No need to check for null here!
        return data[index];
    }

    @Override
    public E set(int index, E element) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public void add(int index, E element) {
        if (index > size || index < 0) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }

        ensureCapacity(size + 1); // Increments modCount!!
        System.arraycopy(data, index, data, index + 1, size - index);
        data[index] = element;
        size++;
    }

    @Override
    public E remove(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }

        E oldValue = data[index];

        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(data, index + 1, data, index, numMoved);
        }
        data[--size] = null; // Let gc do its work

        return oldValue;
    }

    @Override
    public int indexOf(Object elem) {
        if (data == null) {
            return -1;
        }
        if (elem == null) {
            for (int i = 0; i < size; i++) {
                if (data[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (elem.equals(data[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    @Override
    public int lastIndexOf(Object elem) {
        if (data == null) {
            return -1;
        }
        if (elem == null) {
            for (int i = size - 1; i >= 0; i--) {
                if (data[i] == null) {
                    return i;
                }
            }
        } else {
            for (int i = size - 1; i >= 0; i--) {
                if (elem.equals(data[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    @Override
    public ListIterator<E> listIterator() {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public String toString() {
        FastStringBuffer sb = new FastStringBuffer();
        sb.append('[');
        sb.append(StringUtils.join(", ", this));
        sb.append(']');
        return sb.toString();
    }
}
