/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 */
package org.hibernate.collection;

import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.loader.CollectionAliases;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.type.Type;

/**
 * A persistent wrapper for a <tt>java.util.List</tt>. Underlying
 * collection is an <tt>ArrayList</tt>.
 *
 * @see java.util.ArrayList
 * @author Gavin King
 */
public class PersistentList extends AbstractPersistentCollection implements List {

	protected List list;

	public Serializable getSnapshot(CollectionPersister persister) throws HibernateException {

		EntityMode entityMode = getSession().getEntityMode();

		ArrayList clonedList = new ArrayList( list.size() );
		Iterator iter = list.iterator();
		while ( iter.hasNext() ) {
			Object deepCopy = persister.getElementType()
					.deepCopy( iter.next(), entityMode, persister.getFactory() );
			clonedList.add( deepCopy );
		}
		return clonedList;
	}

	public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
		List sn = (List) snapshot;
	    return getOrphans( sn, list, entityName, getSession() );
	}

	public boolean equalsSnapshot(CollectionPersister persister) throws HibernateException {
		Type elementType = persister.getElementType();
		List sn = (List) getSnapshot();
		if ( sn.size()!=this.list.size() ) return false;
		Iterator iter = list.iterator();
		Iterator sniter = sn.iterator();
		while ( iter.hasNext() ) {
			if ( elementType.isDirty( iter.next(), sniter.next(), getSession() ) ) return false;
		}
		return true;
	}

	public boolean isSnapshotEmpty(Serializable snapshot) {
		return ( (Collection) snapshot ).isEmpty();
	}

	public PersistentList(SessionImplementor session) {
		super(session);
	}

	public PersistentList(SessionImplementor session, List list) {
		super(session);
		this.list = list;
		setInitialized();
		setDirectlyAccessible(true);
	}

	public void beforeInitialize(CollectionPersister persister, int anticipatedSize) {
		this.list = ( List ) persister.getCollectionType().instantiate( anticipatedSize );
	}

	public boolean isWrapper(Object collection) {
		return list==collection;
	}

	public PersistentList() {} //needed for SOAP libraries, etc

	/**
	 * @see java.util.List#size()
	 */
	public int size() {
		return readSize() ? getCachedSize() : list.size();
	}

	/**
	 * @see java.util.List#isEmpty()
	 */
	public boolean isEmpty() {
		return readSize() ? getCachedSize()==0 : list.isEmpty();
	}

	/**
	 * @see java.util.List#contains(Object)
	 */
	public boolean contains(Object object) {
		Boolean exists = readElementExistence(object);
		return exists==null ?
				list.contains(object) :
				exists.booleanValue();
	}

	/**
	 * @see java.util.List#iterator()
	 */
	public Iterator iterator() {
		read();
		return new IteratorProxy( list.iterator() );
	}

	/**
	 * @see java.util.List#toArray()
	 */
	public Object[] toArray() {
		read();
		return list.toArray();
	}

	/**
	 * @see java.util.List#toArray(Object[])
	 */
	public Object[] toArray(Object[] array) {
		read();
		return list.toArray(array);
	}

	/**
	 * @see java.util.List#add(Object)
	 */
	public boolean add(Object object) {
		if ( !isOperationQueueEnabled() ) {
			write();
			return list.add(object);
		}
		else {
			queueOperation( new SimpleAdd(object) );
			return true;
		}
	}

	/**
	 * @see java.util.List#remove(Object)
	 */
	public boolean remove(Object value) {
		Boolean exists = isPutQueueEnabled() ? readElementExistence(value) : null;
		if ( exists == null ) {
			initialize( true );
			if ( list.remove( value ) ) {
				dirty();
				return true;
			}
			else {
				return false;
			}
		}
		else if ( exists.booleanValue() ) {
			queueOperation( new SimpleRemove(value) );
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * @see java.util.List#containsAll(Collection)
	 */
	public boolean containsAll(Collection coll) {
		read();
		return list.containsAll(coll);
	}

	/**
	 * @see java.util.List#addAll(Collection)
	 */
	public boolean addAll(Collection values) {
		if ( values.size()==0 ) {
			return false;
		}
		if ( !isOperationQueueEnabled() ) {
			write();
			return list.addAll(values);
		}
		else {
			Iterator iter = values.iterator();
			while ( iter.hasNext() ) {
				queueOperation( new SimpleAdd( iter.next() ) );
			}
			return values.size()>0;
		}
	}

	/**
	 * @see java.util.List#addAll(int, Collection)
	 */
	public boolean addAll(int index, Collection coll) {
		if ( coll.size()>0 ) {
			write();
			return list.addAll(index,  coll);
		}
		else {
			return false;
		}
	}

	/**
	 * @see java.util.List#removeAll(Collection)
	 */
	public boolean removeAll(Collection coll) {
		if ( coll.size()>0 ) {
			initialize( true );
			if ( list.removeAll( coll ) ) {
				dirty();
				return true;
			}
			else {
				return false;
			}
		}
		else {
			return false;
		}
	}

	/**
	 * @see java.util.List#retainAll(Collection)
	 */
	public boolean retainAll(Collection coll) {
		initialize( true );
		if ( list.retainAll( coll ) ) {
			dirty();
			return true;
		}
		else {
			return false;
		}
	}

	/**
	 * @see java.util.List#clear()
	 */
	public void clear() {
		if ( isClearQueueEnabled() ) {
			queueOperation( new Clear() );
		}
		else {
			initialize( true );
			if ( ! list.isEmpty() ) {
				list.clear();
				dirty();
			}
		}
	}

	/**
	 * @see java.util.List#get(int)
	 */
	public Object get(int index) {
		if (index<0) {
			throw new ArrayIndexOutOfBoundsException("negative index");
		}
		Object result = readElementByIndex( new Integer(index) );
		return result==UNKNOWN ? list.get(index) : result;
	}

	/**
	 * @see java.util.List#set(int, Object)
	 */
	public Object set(int index, Object value) {
		if (index<0) {
			throw new ArrayIndexOutOfBoundsException("negative index");
		}
		Object old = isPutQueueEnabled() ? readElementByIndex( new Integer(index) ) : UNKNOWN;
		if ( old==UNKNOWN ) {
			write();
			return list.set(index, value);
		}
		else {
			queueOperation( new Set(index, value, old) );
			return old;
		}
	}

	/**
	 * @see java.util.List#add(int, Object)
	 */
	public void add(int index, Object value) {
		if (index<0) {
			throw new ArrayIndexOutOfBoundsException("negative index");
		}
		if ( !isOperationQueueEnabled() ) {
			write();
			list.add(index, value);
		}
		else {
			queueOperation( new Add(index, value) );
		}
	}

	/**
	 * @see java.util.List#remove(int)
	 */
	public Object remove(int index) {
		if (index<0) {
			throw new ArrayIndexOutOfBoundsException("negative index");
		}
		Object old = isPutQueueEnabled() ?
				readElementByIndex( new Integer(index) ) : UNKNOWN;
		if ( old==UNKNOWN ) {
			write();
			return list.remove(index);
		}
		else {
			queueOperation( new Remove(index, old) );
			return old;
		}
	}

	/**
	 * @see java.util.List#indexOf(Object)
	 */
	public int indexOf(Object value) {
		read();
		return list.indexOf(value);
	}

	/**
	 * @see java.util.List#lastIndexOf(Object)
	 */
	public int lastIndexOf(Object value) {
		read();
		return list.lastIndexOf(value);
	}

	/**
	 * @see java.util.List#listIterator()
	 */
	public ListIterator listIterator() {
		read();
		return new ListIteratorProxy( list.listIterator() );
	}

	/**
	 * @see java.util.List#listIterator(int)
	 */
	public ListIterator listIterator(int index) {
		read();
		return new ListIteratorProxy( list.listIterator(index) );
	}

	/**
	 * @see java.util.List#subList(int, int)
	 */
	public java.util.List subList(int from, int to) {
		read();
		return new ListProxy( list.subList(from, to) );
	}

	public boolean empty() {
		return list.isEmpty();
	}

	public String toString() {
		read();
		return list.toString();
	}

	public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner)
	throws HibernateException, SQLException {
		Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ) ;
		int index = ( (Integer) persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ) ).intValue();

		//pad with nulls from the current last element up to the new index
		for ( int i = list.size(); i<=index; i++) {
			list.add(i, null);
		}

		list.set(index, element);
		return element;
	}

	public Iterator entries(CollectionPersister persister) {
		return list.iterator();
	}

	public void initializeFromCache(CollectionPersister persister, Serializable disassembled, Object owner)
	throws HibernateException {
		Serializable[] array = ( Serializable[] ) disassembled;
		int size = array.length;
		beforeInitialize( persister, size );
		for ( int i = 0; i < size; i++ ) {
			list.add( persister.getElementType().assemble( array[i], getSession(), owner ) );
		}
	}

	public Serializable disassemble(CollectionPersister persister)
	throws HibernateException {

		int length = list.size();
		Serializable[] result = new Serializable[length];
		for ( int i=0; i<length; i++ ) {
			result[i] = persister.getElementType().disassemble( list.get(i), getSession(), null );
		}
		return result;
	}


	public Iterator getDeletes(CollectionPersister persister, boolean indexIsFormula) throws HibernateException {
		List deletes = new ArrayList();
		List sn = (List) getSnapshot();
		int end;
		if ( sn.size() > list.size() ) {
			for ( int i=list.size(); i<sn.size(); i++ ) {
				deletes.add( indexIsFormula ? sn.get(i) : new Integer(i) );
			}
			end = list.size();
		}
		else {
			end = sn.size();
		}
		for ( int i=0; i<end; i++ ) {
			if ( list.get(i)==null && sn.get(i)!=null ) {
				deletes.add( indexIsFormula ? sn.get(i) : new Integer(i) );
			}
		}
		return deletes.iterator();
	}

	public boolean needsInserting(Object entry, int i, Type elemType) throws HibernateException {
		final List sn = (List) getSnapshot();
		return list.get(i)!=null && ( i >= sn.size() || sn.get(i)==null );
	}

	public boolean needsUpdating(Object entry, int i, Type elemType) throws HibernateException {
		final List sn = (List) getSnapshot();
		return i<sn.size() && sn.get(i)!=null && list.get(i)!=null &&
			elemType.isDirty( list.get(i), sn.get(i), getSession() );
	}

	public Object getIndex(Object entry, int i, CollectionPersister persister) {
		return new Integer(i);
	}

	public Object getElement(Object entry) {
		return entry;
	}

	public Object getSnapshotElement(Object entry, int i) {
		final List sn = (List) getSnapshot();
		return sn.get(i);
	}

	public boolean equals(Object other) {
		read();
		return list.equals(other);
	}

	public int hashCode() {
		read();
		return list.hashCode();
	}

	public boolean entryExists(Object entry, int i) {
		return entry!=null;
	}

	final class Clear implements DelayedOperation {
		public void operate() {
			list.clear();
		}
		public Object getAddedInstance() {
			return null;
		}
		public Object getOrphan() {
			throw new UnsupportedOperationException("queued clear cannot be used with orphan delete");
		}
	}

	final class SimpleAdd implements DelayedOperation {
		private Object value;

		public SimpleAdd(Object value) {
			this.value = value;
		}
		public void operate() {
			list.add(value);
		}
		public Object getAddedInstance() {
			return value;
		}
		public Object getOrphan() {
			return null;
		}
	}

	final class Add implements DelayedOperation {
		private int index;
		private Object value;

		public Add(int index, Object value) {
			this.index = index;
			this.value = value;
		}
		public void operate() {
			list.add(index, value);
		}
		public Object getAddedInstance() {
			return value;
		}
		public Object getOrphan() {
			return null;
		}
	}

	final class Set implements DelayedOperation {
		private int index;
		private Object value;
		private Object old;

		public Set(int index, Object value, Object old) {
			this.index = index;
			this.value = value;
			this.old = old;
		}
		public void operate() {
			list.set(index, value);
		}
		public Object getAddedInstance() {
			return value;
		}
		public Object getOrphan() {
			return old;
		}
	}

	final class Remove implements DelayedOperation {
		private int index;
		private Object old;

		public Remove(int index, Object old) {
			this.index = index;
			this.old = old;
		}
		public void operate() {
			list.remove(index);
		}
		public Object getAddedInstance() {
			return null;
		}
		public Object getOrphan() {
			return old;
		}
	}

	final class SimpleRemove implements DelayedOperation {
		private Object value;

		public SimpleRemove(Object value) {
			this.value = value;
		}
		public void operate() {
			list.remove(value);
		}
		public Object getAddedInstance() {
			return null;
		}
		public Object getOrphan() {
			return value;
		}
	}
}
