/*******************************************************************************
 * Copyright (c) 2008 IBM Corporation & CWI
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation
 *    Jurgen Vinju (jurgen@vinju.org)
 ********************************************************************************/

package org.eclipse.imp.pdb.facts.impl.hash;

import java.util.Iterator;
import java.util.LinkedList;

import org.eclipse.imp.pdb.facts.IList;
import org.eclipse.imp.pdb.facts.IListWriter;
import org.eclipse.imp.pdb.facts.IValue;
import org.eclipse.imp.pdb.facts.impl.Value;
import org.eclipse.imp.pdb.facts.type.FactTypeError;
import org.eclipse.imp.pdb.facts.type.ListType;
import org.eclipse.imp.pdb.facts.type.Type;
import org.eclipse.imp.pdb.facts.type.TypeFactory;
import org.eclipse.imp.pdb.facts.visitors.IValueVisitor;
import org.eclipse.imp.pdb.facts.visitors.VisitorException;

public class List extends Value implements IList {
	private final Type eltType;
	private final LinkedList<IValue> content;

	private List(Type eltType, LinkedList<IValue> content){
		super(TypeFactory.getInstance().listType(eltType));
		
		this.eltType = eltType;
		this.content = content;
	}
	
	private List(List other){
		super(other);
		
		eltType = other.eltType;
		content = other.content;
	}
	
	

	public Type getElementType(){
		return eltType;
	}

	public Iterator<IValue> iterator(){
		return content.iterator();
	}

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

	public boolean isEmpty(){
		return content.isEmpty();
	}

	public IValue get(int i){
		return content.get(i);
	}

	public IList insert(IValue elem) throws FactTypeError{
		ListWriter w = new ListWriter(checkInsert(elem).getElementType());
		w.appendAll(this);
		w.insert(elem);
		
		return w.done();
	}

	public IList append(IValue elem) throws FactTypeError{
		ListWriter w = new ListWriter(checkInsert(elem).getElementType());
		w.appendAll(this);
		w.append(elem);
		
		return w.done();
	}

	public IList reverse(){
		ListWriter w = new ListWriter(getElementType());
		for (IValue e : this) {
			w.insert(e);
		}
		return w.done();
	}
	
	public IList concat(IList other) {
		ListWriter w = new ListWriter(getElementType());
		try{
			w.appendAll(this);
			w.appendAll(other);
		}catch(FactTypeError e){
			// This will never happen
		} 
		return w.done();
	}
	
	public <T> T accept(IValueVisitor<T> v) throws VisitorException{
		return v.visitList(this);
	}
	
	protected Object clone() throws CloneNotSupportedException{
		return new List(this);
	}

	public String toString(){
		StringBuilder sb = new StringBuilder();
		
		sb.append("[");
		
		Iterator<IValue> listIterator = iterator();
		if(listIterator.hasNext()){
			sb.append(listIterator.next());
			
			while(listIterator.hasNext()){
				sb.append(", ");
				sb.append(listIterator.next());
			}
		}
		
		sb.append("]");
		
		return sb.toString();
	}
	
	public boolean equals(Object o){
		if(!(o instanceof List)){
			return false;
		}
		List other = (List) o;
		return content.equals(other.content);
	}
	
    /*package*/ static ListWriter createListWriter(Type eltType){
		return new ListWriter(eltType);
	}
	
	private ListType checkInsert(IValue elem) throws FactTypeError{
		checkInsert(elem, eltType);
		return (ListType) getType().getBaseType();
	}
	
	private static void checkInsert(IValue elem, Type eltType) throws FactTypeError{
		Type type = elem.getType();
		if(!type.isSubtypeOf(eltType)){
			throw new FactTypeError("Element type " + eltType + " is not compatible with " + eltType);
		}
	}
	
	/**
	 * This class does not guarantee thread-safety. Users must lock the writer object for thread safety.
	 * It is thread-friendly however.
	 */
	private static class ListWriter implements IListWriter{
		private final Type eltType;
		private final LinkedList<IValue> listContent;
		
		private List constructedList;
		
		public ListWriter(Type eltType){
			super();
			
			this.eltType = eltType;
			listContent = new LinkedList<IValue>();
			
			constructedList = null;
		}
		
		private void checkMutation(){
			if(constructedList != null) throw new UnsupportedOperationException("Mutation of a finalized list is not supported.");
		}
		
		private void put(int index, IValue elem){
			checkInsert(elem, eltType);
			listContent.add(index, elem);
		}
		
		public void insert(IValue elem) throws FactTypeError{
			checkMutation();
			
			put(0, elem);
		}
		
		public void insert(IValue[] elems, int start, int length) throws FactTypeError{
			checkMutation();
			checkBounds(elems, start, length);
			
			for(int i = start + length - 1; i >= start; i--){
				put(0, elems[i]);
			}
		}

		public void insert(IValue... elems) throws FactTypeError{
			insert(elems, 0, elems.length);
		}
		
		public void insert(int index, IValue elem) throws FactTypeError{
			checkMutation();
			
			put(index, elem);
		}
		
		public void insertAt(int index, IValue[] elems, int start, int length) throws FactTypeError{
			checkMutation();
			checkBounds(elems, start, length);
			
			for(int i = start + length - 1; i >= start; i--){
				put(index, elems[i]);
			}
		}

		public void insert(int index, IValue... elems) throws FactTypeError{
			insertAt(index,  elems, 0, 0);
		}
		
		public void append(IValue elem) throws FactTypeError{
			checkMutation();
			
			put(listContent.size(), elem);
		}

		public void append(IValue... elems) throws FactTypeError{
			checkMutation();
			
			for(IValue elem : elems){
				put(listContent.size(), elem);
			}
		}
		
		public void appendAll(Iterable<? extends IValue> collection) throws FactTypeError{
			checkMutation();
			
			for(IValue v : collection){
				put(listContent.size(), v);
			}
		}

		public IList done(){
			if(constructedList == null) constructedList = new List(eltType, listContent);
			
			return constructedList;
		}
		
		private void checkBounds(IValue[] elems, int start, int length){
			if(start < 0) throw new ArrayIndexOutOfBoundsException("start < 0");
			if((start + length) > elems.length) throw new ArrayIndexOutOfBoundsException("(start + length) > elems.length");
		}
	}
}
@@ -3062,17 +3062,16 @@
 ppend(%22,
- 
 %22);%0A%09%09%09%09

removed spaces in toString