/**
 * MVEL 2.0
 * Copyright (C) 2007 The Codehaus
 * Mike Brock, Dhanji Prasanna, John Graham, Mark Proctor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mvel2.util;

import org.mvel2.ImmutableElementException;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.*;

public class FastList<E> extends AbstractList<E> implements Externalizable {
  private E[] elements;
  private int size = 0;
  private boolean updated = false;

  public FastList(int size) {
    elements = (E[]) new Object[size == 0 ? 1 : size];
  }

  public FastList(E[] elements) {
    this.size = (this.elements = elements).length;
  }

  public FastList() {
    this(10);
  }

  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeInt(size);
    for (int i = 0; i < size; i++) {
      out.writeObject(elements[i]);
    }
  }

  public void readExternal(ObjectInput in) throws IOException,
      ClassNotFoundException {
    elements = (E[]) new Object[size = in.readInt()];
    for (int i = 0; i < size; i++) {
      elements[i] = (E) in.readObject();
    }
  }

  public E get(int index) {
    return (E) elements[index];
  }

  public int size() {
    return size;
  }

  public boolean add(E o) {
    if (size == elements.length) {
      increaseSize(elements.length * 2);
    }

    elements[size++] = o;
    return true;
  }

  public E set(int i, E o) {
    if (!updated) copyArray();
    E old = elements[i];
    elements[i] = o;
    return old;
  }

  public void add(int i, E o) {
    if (size == elements.length) {
      increaseSize(elements.length * 2);
    }

    for (int c = size; c != i; c--) {
      elements[c] = elements[c - 1];
    }
    elements[i] = o;
    size++;
  }

  public E remove(int i) {
    E old = elements[i];
    for (int c = i + 1; c < size; c++) {
      elements[c - 1] = elements[c];
      elements[c] = null;
    }
    size--;
    return old;
  }

  public int indexOf(Object o) {
    if (o == null) return -1;
    for (int i = 0; i < elements.length; i++) {
      if (o.equals(elements[i])) return i;
    }
    return -1;
  }

  public int lastIndexOf(Object o) {
    if (o == null) return -1;
    for (int i = elements.length - 1; i != -1; i--) {
      if (o.equals(elements[i])) return i;
    }
    return -1;
  }

  public void clear() {
    elements = (E[]) new Object[1];
    size = 0;
  }

  public boolean addAll(int i, Collection<? extends E> collection) {
    int offset = collection.size();
    ensureCapacity(offset + size);

    if (i != 0) {
      // copy forward all elements that the insertion is occuring before
      for (int c = i; c != (i + offset); c++) {
        elements[c + offset + 1] = elements[c];
      }
    }

    int c = size == 0 ? -1 : 0;
    for (E o : collection) {
      elements[offset + c++] = o;
    }

    size += offset;

    return true;
  }

  public Iterator iterator() {
    final int size = this.size;
    return new Iterator() {
      private int cursor = 0;

      public boolean hasNext() {
        return cursor < size;
      }

      public Object next() {
        return elements[cursor++];
      }

      public void remove() {
        throw new ImmutableElementException("cannot change elements in immutable list");
      }
    };

  }

  public ListIterator<E> listIterator() {
    return new ListIterator<E>() {
      private int i = 0;

      public boolean hasNext() {
        return i < size;
      }

      public E next() {
        return elements[i++];
      }

      public boolean hasPrevious() {
        return i > 0;
      }

      public E previous() {
        return elements[i--];
      }

      public int nextIndex() {
        return i++;
      }

      public int previousIndex() {
        return i--;
      }

      public void remove() {
        throw new java.lang.UnsupportedOperationException();
      }

      public void set(E o) {
        elements[i] = o;
      }

      public void add(Object o) {
        throw new java.lang.UnsupportedOperationException();
      }
    };
  }

  public ListIterator listIterator(int i) {
    return super.listIterator(i);
  }

  public List subList(int i, int i1) {
    return super.subList(i, i1);
  }

  public boolean equals(Object o) {
    if (o == this)
      return true;
    if (!(o instanceof List))
      return false;

    ListIterator e1 = listIterator();
    ListIterator e2 = ((List) o).listIterator();
    while (e1.hasNext() && e2.hasNext()) {
      Object o1 = e1.next();
      Object o2 = e2.next();
      if (!(o1 == null ? o2 == null : o1.equals(o2)))
        return false;
    }
    return !(e1.hasNext() || e2.hasNext());
  }

  public int hashCode() {
    return super.hashCode();
  }

  protected void removeRange(int i, int i1) {
    throw new RuntimeException("not implemented");
  }

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

  public boolean contains(Object o) {
    return indexOf(o) != -1;
  }

  public Object[] toArray() {
    return toArray(new Object[size]);
  }

  public Object[] toArray(Object[] objects) {
    if (objects.length < size) objects = new Object[size];
    for (int i = 0; i < size; i++) {
      objects[i] = elements[i];
    }
    return objects;
  }

  public boolean remove(Object o) {
    throw new RuntimeException("not implemented");
  }

  public boolean containsAll(Collection collection) {
    throw new RuntimeException("not implemented");
  }

  public boolean addAll(Collection collection) {
    return addAll(size, collection);
  }

  public boolean removeAll(Collection collection) {
    throw new RuntimeException("not implemented");
  }

  public boolean retainAll(Collection collection) {
    throw new RuntimeException("not implemented");
  }

  private void ensureCapacity(int additional) {
    if ((size + additional) > elements.length) increaseSize((size + additional) * 2);
  }

  private void copyArray() {
    increaseSize(elements.length);
  }

  private void increaseSize(int newSize) {
    E[] newElements = (E[]) new Object[newSize];
    for (int i = 0; i < elements.length; i++)
      newElements[i] = elements[i];

    elements = newElements;

    updated = true;
  }


  public String toString() {
    return super.toString();
  }
}
