package latte.lattice;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

import latte.FunctionPointer;
import latte.Latte;
import latte.lattice.LatticeFileReader.LatticeFileToken;


public class Lattice implements Cloneable {

	int type;
	Vector<Lattice> vector = null;
	Element word = null;

	public final static int WRD = 0, SEQ = 1, OR = 2;

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * *         CONSTRUCTING AND CLONING LATTICES         * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public Lattice() {
		type = WRD;
	}

	public Lattice(int _type) {
		type = _type;
	}

	public Lattice(int _type, Element _word) {
		type = _type;
		word = _word;
	}

	public Lattice(Element _word) {
		type = WRD;
		word = _word;
	}

	public Lattice(int _type, Vector<Lattice> elements) {
		type = _type;
		vector = elements;
	}

	/** Copy Constructor (clone every element) */
	public Lattice(Lattice lat) {
		this.type = lat.getType();
		this.word = null; this.vector = null;
		if (type == WRD) {
			this.word = lat.getWordClone();
		} else {
			this.vector = lat.getVectorClone();
		}
	}

	/** get a copy of this lattice without cloning every element.
	 * */
	public Lattice getCopyDontClone() {
		Lattice copy = new Lattice(this.type);
		copy.word = this.word; copy.vector = this.vector;  
		copy.iterator = this.iterator; copy.optimize = this.optimize;
		return copy;
	}
	
	/** copy the passed lattice 'lattice' to this lattice without cloning every element.
	 * */
	public Lattice copyThisDontClone(Lattice lattice) {
		this.type = lattice.type; this.word = lattice.word; this.vector = lattice.vector; 
		this.optimize = lattice.optimize; this.iterator = lattice.iterator; 
		return this;
	}
	
	/** clone every element in this lattice and return the clone lattice
	 * */
	public Lattice clone() {
		Lattice clon = null;
		try {
			clon = (Lattice) super.clone(); // unchecked warning
		} catch (CloneNotSupportedException e) {
			System.err.print("\nCannot Clone lattice:\n\t" + e.getMessage());
			e.printStackTrace();
			System.exit(-1);  
		}
		clon.word = this.getWordClone();
		clon.type = this.getType();
		clon.vector=this.getVectorClone();
		return clon;
	}

	public Element getWordClone() {
		return this.word.clone();
	}
	
	public void replace(Element _word) {
		replace(new Lattice(WRD, _word));
	}

	public void replace(Lattice lattice) {
		type = lattice.type;
		vector = lattice.vector;
		word = lattice.word;
	}

	public void replaceWithClone(Lattice lattice) {
		type = lattice.getType();
		vector = lattice.getVectorClone();
		word = lattice.getWordClone();
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *               GET/SET METHODS             * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public boolean isInitialized() {
		return ((type == WRD) && (word != null)) || ((vector != null) && (! vector.isEmpty()));
	}

	public Element getWord() throws LatteException {
		if (type == WRD) {
			return word;
		} else {
			throw new LatteException("Wrong Type: " + type
					+ ". Use getWord() only for WRD Lattices.");
		}
	}

	public int getType() {
		return type;
	}

	public boolean isElement() {
		return type == WRD;
	}

	public Vector<Lattice> getVector() {
		return vector;
	}

	/** Get a copy of the vector */
	public Vector<Lattice> getVectorClone() {
		Vector<Lattice> newV = new Vector<Lattice>(vector);
		return newV;
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *         ITERATING THROUGH LATTICES        * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	Iterator<Lattice> iterator = null;
	public void initIterator() {
		if (vector != null)
			iterator = vector.iterator();
		else {
			Latte.logError(new LatteException("Call for initIterator() while 'vector' is null")); 
		}
	}
	
	public boolean hasNext() {
		if (iterator == null)
			initIterator();
		return iterator.hasNext();
	}
	
	public Lattice next() {
		if (hasNext())
			return iterator.next();
		iterator = null;
		return null;
	}
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * *      CONVERT LATTICES INTO OTHER COLLECTIONS      * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	@Deprecated
	/** Return a list of lattice elements: string all altlats and seqlats to form one list of elements. 
	 * */
	public Vector<Element> getListOfElements() {
		Vector<Element> resultVector = new Vector<Element>();
		if (isElement()){
			resultVector.add(word);
		}
		else {
			while (hasNext()) {
				resultVector.addAll(next().getListOfElements());
			}
		}
		return resultVector;
	}
	
	@Deprecated
	/** Return a set of lattice elements: string all altlats and seqlats to form one set of elements. 
	 * */
	public Set<Element> getSetOfElements() {
		 
		Set<Element> resultSet = new HashSet<Element>(getListOfElements());
		if (isElement()){
			resultSet.add(word);
		}
		else {
			while (hasNext()) {
				resultSet.addAll(next().getListOfElements());
			}
		}
		return resultSet;
	}
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *              READING LATTICES             * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
	@Deprecated
	/**
	 * TODO: 
	 */
	public static Lattice readFromString(String fileName) 
		throws LatteException {
		return null;
	}
	
	@Deprecated
	public static Lattice readFromFile(String fileName)
	throws LatteException {
		LatticeFileReader reader = new LatticeFileReader(fileName);
		return readFromFile(reader);
	}
	
	@Deprecated
	public static Lattice readFromFile(LatticeFileReader reader)
			throws LatteException {
		Lattice lattice = null;// new Lattice();
		Stack<Lattice> latticeStack = new Stack<Lattice>();
		// final int rootLatticeType = 100;
		// latticeStack.push(new Lattice(rootLatticeType));

		if (!reader.hasNext()) { // File is empty:
			return null;
		}
		while (reader.hasNext()) {
			LatticeFileToken token = reader.getNext();
			lattice = token.lattice;
			// Test.println("\t\t\t\t"+token.toString());
			if (token.type == WRD) {
				// Add WRD to the top lattice in the stack.
				// //Test.println("Add word: "+lattice.typeToString());
				latticeStack.lastElement().vector.add(lattice);
			} else {
				if (token.action == LatticeFileReader.actionStart) {
					// Add it to the top lattice in the stack.
					if (!latticeStack.isEmpty()) {
						// Test.println("add to top: "+lattice.typeToString());
						latticeStack.lastElement().vector.add(lattice);
					}
					// Test.println("push: "+lattice.typeToString());
					// push it to the stack:
					latticeStack.push(lattice);
				} else {
					if (latticeStack.isEmpty()) { // PROBLEM: Reached the root
													// => an extra 'END' token
						throw new LatteException(
								"Error: Lattice file contains an extra 'END' token:\n\tEnd of Type "
										+ token.type
										+ "! Expected end of file!");
					}
					lattice = latticeStack.pop();
					// Test.println("pop: "+lattice.typeToString());
					if (latticeStack.isEmpty()) { // Finished reading the
													// lattice:
						return lattice;
					}
					if (! reader.restrictionEndOfBlockIsIdentical) {
						if (lattice.type != token.type) {
							throw new LatteException(
									"Block Intersection:\n\tExpected _type "
											+ lattice.type + ", found _type "
											+ token.type + "!");
						}
					}
				}
			}
		}
		if (latticeStack.isEmpty()) { // Finished reading the lattice:
			return lattice;
		} else {
			if (reader.restrictionAllowMissingEndOfBlock) {
				while (latticeStack.isEmpty())
					lattice = latticeStack.pop();
				return lattice;
			} else {
				throw new LatteException(
						"Missing Ends of lattice blocks!\n\tMissing "
								+ latticeStack.size() + " block ends.");
			}
		}

	}


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *           OPERATIONS ON LATTICES          * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public void reverse() {
		if (! this.isElement())
			Collections.reverse(vector);
	}
	
	/** resultLat = lat1 ~ lat2
	 * */
	static public Lattice concatenate(Lattice lat1, Lattice lat2) {
		Lattice resultLat = new Lattice(lat1);
		resultLat.concatenate(lat2);
		return resultLat;
	}

	/** For: 'x ~= 3;'
	 * */
	public void concatenate(Element _word) {
		concatenate(new Lattice(WRD, _word));
	}

	boolean optimize = true;
	/** For: 'x ~= y;'
	 * */
	public void concatenate(Lattice lattice) {
		if (! isInitialized()) {
			replace(lattice);
			return;
		}

		// First Lattice:
		switch (type) {
		case OR:
		case WRD:
			Lattice firstLattice = this.getCopyDontClone();
			type = SEQ;
			vector = new Vector<Lattice>();
			vector.add(firstLattice);
			break;
		case SEQ:
			if (optimize) {
				// Do nothing.
			} else {
				firstLattice = this.getCopyDontClone();
				// type is already SEQ
				vector = new Vector<Lattice>();
				vector.add(firstLattice);
			}
			break;
		}
		// Second Lattice:
		switch (lattice.getType()) {
		case SEQ:
			if (lattice.optimize) {
				vector.addAll(lattice.getVector()); // Add each element				
			} else {
				vector.add(lattice); // Add each element				
			}
			break;
		case OR:
		case WRD:
			vector.add(lattice);
			break;
		}

	}
	
	/** For: 'x |= y;'
	 * */
	public void addAlternative(Element _word) {
		addAlternative(new Lattice(WRD, _word));
	}

	public void addAlternative(Lattice lattice) {
		if (! isInitialized()) {
			replace(lattice);
			return;
		}
		// First Lattice:
		switch (type) {
		case OR:
			if (optimize) {
				// Do nothing
			} else {
				Lattice firstAltLattice = this.getCopyDontClone();
				// type is already OR
				vector = new Vector<Lattice>();
				vector.add(firstAltLattice);
			}
			break;
		case WRD:
		case SEQ:
			Lattice firstAltLattice = this.getCopyDontClone();
			type = OR;
			vector = new Vector<Lattice>();
			vector.add(firstAltLattice);
			break;
		}
		// Second Lattice:
		switch (lattice.getType()) {
		case OR:
			if (optimize) {
				vector.addAll(lattice.getVector()); // Add each element
			} else {
				vector.add(lattice);
			}
			break;
		case SEQ:
		case WRD:
			vector.add(lattice);
			break;
		}
	}

	public void remove(int coordinates) {
		
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *          LATTICE INDEXING AND SIZE        * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public Lattice getElementByOffset(int index, int offset) {
		int calculatedIndex = index + offset;
		if (calculatedIndex >= 0 && calculatedIndex < vector.size()) {
			return vector.get(index+offset);
		} else {
			return null;
		}
	}
	
	public Lattice getElementOfIndex(int index) {
		return vector.get(index);
	}
	
	public int length() {
		return (type == SEQ) ? vector.size() : 1;
	}

	public int count() {
		return (type == OR) ? vector.size() : 1;
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *                    LOOPS                  * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public void foreach(FunctionPointer function) {
		while (hasNext()) {
			function.execute(next());
		}
	}
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *                  TO STRING                * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public String toString() {
		// @TODO
		String latticeString = "";
		final String tab = "    ";
		if (type == WRD) {
			latticeString += "(WRD " + word.toString() + ")\n";
		} else {
			latticeString += "(" + typeToString() + "\n" + tab;
			Iterator<Lattice> itr = vector.iterator();
			while (itr.hasNext()) {
				latticeString += itr.next().toString()
						.replaceAll("\\n", "\n" + tab);
			}
			latticeString = latticeString.replaceFirst(tab + "$", ")\n");
			// latticeString += ")\n";
		}
		return latticeString;
	}

	public String typeToString() {
		switch (type) {
		case WRD:
			return "WRD";
		case OR:
			return "OR";
		case SEQ:
			return "SEQ";
		default:
			throw new IllegalStateException(
					"Lattice has illegal type: type ID is " + type);
		}
	}

	
    //We need the following functions for now:
    final private int compare(final Lattice secondLattice)
    {
    	int retVal = 0;
    	return retVal;
    }
    // it compares two lattices (efficiently) and returns 0 (if equal), -1 (if secondLattice > this), +1 (if  secondLattice < this). To be equal, they should be of the same "type" and the same "vector.size()". it is a recursive function (as the ones that I already wrote).

    boolean each; //field, if set to "true", any operation should apply to each element in this lattice, else it applies to the first element.
    final public Lattice forEach() 
    { 
	Lattice newLat = this.getCopyDontClone(); 
	newLat.each=true; 
	return newLat; 
    }
    
    protected void addElement(Lattice lat)
    {
    	this.vector.add(lat);
    }

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * * * * * * * * * *           Arithmetic Operations           * * * * * * * * * * * * * * 
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    /*
     * Notation: OR == alt // the same
	seq + seq:
	(a; b; c) + (x; y; z) = (a+x; b+y; c+z)
	(a; b; c) + (x; y) = (a+x; b+y; c)
	(a; b) + (x; y; z) = (a+x; b+y; z)

	seq + alt: we have to promote alt to seq ==> (alt) == (alt; epsilon; epsilon; ...; epsilon) to be of the same size
	(a; b; c) + (x | y | z) = (a; b; c) + ((x | y | z); epsilon; epsilon) = (a + (x | y | z); b; c)  // a + (x | y | z) is defined in WRD + OR

	seq + wrd: we have to promote wrd to seq ===> (wrd) == (wrd; epsilon; epsilon; ...; epsilon) to be of the same size
	(a; b; c) + x = (a; b; c) + (x; epsilon; epsilon) = (a + x; b; c)  // a + x is WRD + WRD

	wrd + seq: we have to promote wrd to seq (again) ===> (wrd) == (wrd; epsilon; epsilon; ...; epsilon) to be of the same size
	x + (a; b; c) = (x; epsilon; epsilon) + (a; b; c)  = (x + a; b; c)  // a + x is WRD + WRD

	wrd + alt: we have to convert alt to wrd ===> 
	x + (a | b | c) = x + (a + b + c)  = x + a + b + c // and that's why you should have a function "Lattice sum()" that sums all the elements in a lattice and returns a WRD lattice.

	wrd + wrd: cast and sum

	alt1 + alt2: 
	Lattice resultLat = new Lattice(Lattice.OR);
	Lattice a2sum = alt2.sum(); // returns a wrd lattice that is the sum of all elements. You don't need two for loops any more
	foreach a1 in alt1:
		resultLat.vector.add(a1.plusIntInt(a2sum));
	return resultLat;

	alt + seq: we have to promote seq to alt ==> (seq) == (seq | epsilon | epsilon | ... | epsilon)
	(x | y | z) + (a; b; c) = (x | y | z) + ((a; b; c) | epsilon | epsilon) = (x + (a; b; c) | y | z) // now it is wrd + seq above


	to simplify the alt + alt function, you'll need these function for each type pair (should be generated by your script): 
	Lattice sumIntInt(); // x+y+z
	Lattice subIntInt();  // x-y-z
	Lattice timesIntInt(); // x*y*z
	Lattice divideIntInt(); // x/y/z
     */
    //The following functions perform mathematical binary and unary operations:
    public Lattice operationTemplate(Lattice secondLat, LatticeOperation op)
    {
	Lattice retVal = new Lattice(); 
	if (this.type == WRD & secondLat.type == WRD) 
	    {
			try {
				retVal = op.DoOp(this,secondLat);
			} catch (LatteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    }
	else if( this.type == WRD && secondLat.type == OR )// do we need the reverse of this?
	{
		// wrd + alt: we have to convert alt to wrd ===> 
		// x + (a | b | c) = x + (a + b + c)  = x + a + b + c 
		// and that's why you should have a function "Lattice sum()" 
		// that sums all the elements in a lattice and returns a WRD lattice.
		Iterator<Lattice> itr2 = secondLat.vector.iterator();
		Lattice second = itr2.next();
		if(second != null)
		{
			retVal = this.operationTemplate(second, op); // do the op for this and the first element of second lattice
		}
		while ( itr2.hasNext() ) 
	    	{	
				second = itr2.next(); 
				retVal = operationTemplate(second, op);
	    	}
	}
	else if( this.type == OR && secondLat.type == WRD )//this is the reverse of the above
	{
		Iterator<Lattice> itr1 = this.vector.iterator();
		Lattice first = itr1.next();
		if(first != null)
		{
			retVal = secondLat.operationTemplate(first, op); // do the op for this and the first element of second lattice
		}
		while ( itr1.hasNext() ) 
	    	{	
				first = itr1.next(); 
				retVal = operationTemplate(first, op);
	    	}
	}
	else if( this.type == WRD && secondLat.type == SEQ ) // do we need the reverse of this?
	{
		//wrd + seq: we have to promote wrd to seq (again) ===> (wrd) == (wrd; epsilon; epsilon; ...; epsilon) to be of the same size
		//x + (a; b; c) = (x; epsilon; epsilon) + (a; b; c)  = (x + a; b; c)  // a + x is WRD + WRD
		Iterator<Lattice> itr2 = secondLat.vector.iterator();
		Lattice temp = itr2.next();
		retVal = this.operationTemplate(temp,op);
		while ( itr2.hasNext() ) 
	    	{	
				Lattice second = itr2.next(); 
				retVal.addElement(second);
	    	}		
	}
	else if( this.type == SEQ & secondLat.type == WRD ) // reverse of above
	{
		//wrd + seq: we have to promote wrd to seq (again) ===> (wrd) == (wrd; epsilon; epsilon; ...; epsilon) to be of the same size
		//x + (a; b; c) = (x; epsilon; epsilon) + (a; b; c)  = (x + a; b; c)  // a + x is WRD + WRD
		Iterator<Lattice> itr1 = this.vector.iterator();
		Lattice temp = itr1.next();
		retVal = this.operationTemplate(temp,op);
		while ( secondLat.hasNext() ) 
	    	{	
				Lattice first = itr1.next(); 
				retVal.addElement(first);
	    	}			
	}
	else if( this.type == SEQ & secondLat.type == SEQ )
	{
		// 	seq + seq:
		//(a; b; c) + (x; y; z) = (a+x; b+y; c+z)
		//(a; b; c) + (x; y) = (a+x; b+y; c)
		//(a; b) + (x; y; z) = (a+x; b+y; z)
		Iterator<Lattice> itr1 = this.vector.iterator();
		Iterator<Lattice> itr2 = secondLat.vector.iterator();
		while ( itr1.hasNext() | itr2.hasNext() ) 
	    	{
			Lattice first = itr1.next();
			Lattice second = itr2.next();
			if(first != null & second != null )
				{
					try {
						retVal.addElement(op.DoOp(first,second));
					} catch (LatteException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
		    	}	
			else if( first == null )
		    	{	
				retVal.addElement(second);
		    	}
			else if( second == null )
		    	{
				retVal.addElement(first);
		    	}
	    	}
	}
	else if( this.type == OR & secondLat.type == OR )
	{
		
		// alt1 + alt2: 
		// Lattice resultLat = new Lattice(Lattice.OR);
		// Lattice a2sum = alt2.sum(); // returns a wrd lattice that is the sum of all elements. You don't need two for loops any more
		// foreach a1 in alt1:
		// resultLat.vector.add(a1.plusIntInt(a2sum));
		// return resultLat;
		Lattice sum = this.sumTemplate(secondLat,op);
		Iterator<Lattice> itr1 = this.vector.iterator();
		while ( itr1.hasNext() ) 
	    	{	
				Lattice first = itr1.next(); // we get the rhs first value
				try {
					retVal.addElement(op.DoOp(first,sum));
				} catch (LatteException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
	    	}
	}
	else if( this.type == OR & secondLat.type == SEQ ) // the first one defines the behavior
	{
		//alt + seq: we have to promote seq to alt ==> (seq) == (seq | epsilon | epsilon | ... | epsilon)
		//(x | y | z) + (a; b; c) = (x | y | z) + ((a; b; c) | epsilon | epsilon) = (x + (a; b; c) | y | z) // now it is wrd + seq above
		Iterator<Lattice> itr1 = this.vector.iterator();
		Iterator<Lattice> itr2 = secondLat.vector.iterator();
		Lattice second = itr2.next();
		Lattice first = itr1.next();
		retVal.type = OR;
		retVal = second.operationTemplate(this, op); //a+(x|y|z)	
		while( itr2.hasNext() )
		{
			second = itr2.next();
			retVal.vector.add( second );
		}		
	}
	else if( this.type ==SEQ & secondLat.type == OR ) // the first one defines the behavior
	{
		// seq + alt: we have to promote alt to seq ==> (alt) == (alt; epsilon; epsilon; ...; epsilon) to be of the same size
		//(a; b; c) + (x | y | z) = (a; b; c) + ((x | y | z); epsilon; epsilon) = (a + (x | y | z); b; c)  // a + (x | y | z) is defined in WRD + OR
		Iterator<Lattice> itr1 = this.vector.iterator();
		Iterator<Lattice> itr2 = secondLat.vector.iterator();
		Lattice second = itr2.next();
		Lattice first = itr1.next();
		retVal.type = SEQ;
		retVal = first.operationTemplate(second, op); //a+(x|y|z)	
		while( itr1.hasNext() )
		{
			first = itr1.next();
			retVal.vector.add( first );
		}
	
	}	
	return retVal;
	    
    }
    /*****************************************************************************************/
    final public Lattice sumTemplate(Lattice lattice, LatticeOperation op)
    {
     // apply the operation to each element in the lattice 
    	Lattice retVal = new Lattice();
    	Iterator<Lattice> iter = lattice.vector.iterator();
    	Lattice temp;
    	while( iter.hasNext() )
    	{
    		temp = iter.next();// this is left to right evaluation
    		try {
				op.DoOp(retVal,temp);
			} catch (LatteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    	return retVal;
    }
    /*****************************************************************************************/
    final public Lattice plusFF(Lattice secondLat)
    {
   
    	LatticeOperation op = new AddFFOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice plusFI(Lattice secondLat)
    {
    	LatticeOperation op = new AddFIOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice plusFS(Lattice secondLat)
    {
    	LatticeOperation op = new AddFSOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice plusII(Lattice secondLat)
    {
    	LatticeOperation op = new AddIIOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice plusIS(Lattice secondLat)
    {
    	LatticeOperation op = new AddISOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice plusSS(Lattice secondLat)
    {
    	LatticeOperation op = new AddSSOp();
    	return this.operationTemplate(secondLat,op);
    }
    /*****************************************************************************************/
    final public Lattice minus(Lattice secondLat)
    {
    	Lattice retVal = new Lattice(); 
    	return retVal;
    }
    /*****************************************************************************************/
    final public Lattice times(Lattice secondLat)
    {
    	Lattice retVal = new Lattice(); 
    	return retVal;
    }
    /*****************************************************************************************/
    final public Lattice divide(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice mod(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * * * * * * * * * *          Binary Operations                * * * * * * * * * * * * * * 
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    final public Lattice or(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice and(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * * * * * * * * * *           Equality Operations             * * * * * * * * * * * * * * 
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    final public Lattice equals(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/

    final public Lattice notEqual(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice lessThan(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice lessThanOrEqual(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice greaterThan(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice greaterThanOrEqual(Lattice secondLat)
    {
	return this;
    }
    /*****************************************************************************************/

    /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     * * * * * * * * * *             Unary Operations              * * * * * * * * * * * * * * 
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

    final public Lattice unaryMinus()
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice negate()
    {
	return this;
    }
    /*****************************************************************************************/
    final public Lattice assign(Lattice rvalue)
    {
	return this;
    }
    /*****************************************************************************************/	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *               DEPRECATED!!!!!             * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	@Deprecated
	public static  Lattice fillTemplate (
			Lattice templateLattice, HashMap<String, Element> hash) {
		Lattice resultLattice = new Lattice (templateLattice.getType());
		if (templateLattice.isElement()){
			try {
				resultLattice.word = hash.get(templateLattice.getWord());
			} catch (LatteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				resultLattice.word = null;
			}
		}
		else {
			Iterator<Lattice> itr = templateLattice.vector.iterator();
			while (itr.hasNext()) {
				resultLattice.vector.add(Lattice.fillTemplate(itr.next(), hash));
			}
		}
		return resultLattice;
	}
	
	@Deprecated
	boolean visited = false;
	@Deprecated
	public boolean isAcyclic() {
		if (visited) {
			return false; // Is NOT acyclic.
		} 
		visited = true; // Make it visited in this path.
		if (! isElement()){ // Check if children are acyclic:
			Iterator<Lattice> itr = vector.iterator();
			while (itr.hasNext()){
				boolean acyclic = itr.next().isAcyclic();
				if (! acyclic){
					return false; // if one child is NOT acyclic then 'this' is NOT acyclic.
				} 
			}
		}
		visited = false; // Clear 'visited' because it can be visited in another path, but not from its children.
		return true;
	}

	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * * * * * * * * * *           EXPRESSION OPERATIONS           * * * * * * * * * * * * * * 
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	public static int Str=0, Int=1, Flt=2, Code=3; 
	public Lattice plusEqual(Lattice lattice) {
		Element result = null;
		if (lattice.type == WRD && this.type == WRD) {
			if (this.word.type == Str) {
				if (lattice.word.type == Str) {
					this.word.element = ((String) this.word.element) + (String) lattice.word.element;
					result = new Element(this.word.element);
				} else if (lattice.word.type == Int) {
					this.word.element = ((String) this.word.element) + (Integer) lattice.word.element;
					result = new Element(this.word.element);
				} // todo
				
			} else if (this.word.type == Int) {
				if (lattice.word.type == Str) {
					Latte.logError("Runtime: Type mismatch: expected Int found Str");
					return null;
				} else if (lattice.word.type == Int) {
					this.word.element = ((String) this.word.element) + (Integer) lattice.word.element;
					result = new Element(this.word.element);
				} // todo	
			}// todo
		}// todo
		return new Lattice(result);
	}

}
