/**
 * 
 */
package dspl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import antlr.collections.AST;

import dspl.frontend.ParserTokensTokenTypes;
import dspl.frontend.SymbolicException;
import dspl.intermediate.variable.Variable;

/**
 * SymbolTable serves as a storage for all variables.<br>
 * SymbolTable internally uses a HashMap to map identifiers
 * to their corresponding Variable
 * @author David
 */
public class SymbolTable implements ParserTokensTokenTypes{
	/**
	 * HashMap used to map identifiers to Variable-typed objects
	 * for the Variable represented by the provided identifier
	 */
	private HashMap symbolHashMap; 
	
	/**
	 * Constructor for SymbolTable
	 *
	 */
	public SymbolTable() { 
		symbolHashMap = new HashMap(); 
	}
	/**
	 * When strings are added to the symbol table, their identifier is set as "stringLabel#" where # is this number, must be unique, so we increment it on each call
	 */
	private int nextStringIDInt = 0; 
	
	/**
	 * Provides unique label for each string added to the symbol table 
	 * @return
	 */
	private String getNextStringID() { 
		return new String("stringLabel"+ (nextStringIDInt++)); 
	}
	
	private int nextFloatConstIDInt = 0; 
	
	/**
	 * Provides unique label for each string added to the symbol table 
	 * @return
	 */
	public String getNextFloatConstID() { 
		return new String("floatConst"+ (nextFloatConstIDInt++)); 
	}
	
	/**
	 * Takes the AST that represents the program being compiled and adds all of
	 * the variables found to the Symbol table
	 * @param tree
	 */
	public void constructTable(AST tree) throws SymbolicException {
		if (tree == null || tree.getFirstChild() == null || tree.getFirstChild().getFirstChild() == null) return;
		AST variableTree = tree.getFirstChild().getFirstChild();
		int type = -1;
		do {
			Variable newVariable;
			String identifier;
			int tokenType = variableTree.getType();
			if(tokenType == ID) {
				identifier = variableTree.getText();
				type = variableTree.getFirstChild().getType();
				//TODO: Find a more robust method determining if a declaration is an array
				if (variableTree.getNumberOfChildren() == 2) { //this means it is an array declaration, at least for now
					int arrayLength = Integer.parseInt(variableTree.getFirstChild().getNextSibling().getText());
					if(arrayLength <= 0)
					{
						throw new SymbolicException(null, "Array Length Must Be Greater Than Zero");
					}
					if((type == Variable.getAntlrTypeFromVarTypeArray(Variable.DATA_TYPE_BYTE) || type == Variable.getAntlrTypeFromVarTypeArray(Variable.DATA_TYPE_UBYTE)) && arrayLength % 16 != 0)
					{
						throw new SymbolicException(null, "The length of a Byte array must be a multiple of 16");
					}
					else if((type == Variable.getAntlrTypeFromVarTypeArray(Variable.DATA_TYPE_INT) 
							|| type == Variable.getAntlrTypeFromVarTypeArray(Variable.DATA_TYPE_FLOAT)
							|| type == Variable.getAntlrTypeFromVarTypeArray(Variable.DATA_TYPE_UINT)
							) && (arrayLength % 4 != 0))
					{
						throw new SymbolicException(null, "The length of an Integer or Float array must be a multiple of 4");
					}
					newVariable = Variable.getNewArray(type, identifier, arrayLength, 0);
				}
				else { //Atomic declaration instead of an array
					newVariable = Variable.getNewAtomic(type, identifier, 0, false);
				}
				this.addVariable(newVariable);
			}
		} while ((variableTree = variableTree.getNextSibling()) != null);
	}
	
	/**
	 * Adds the provided Variable to the Symbol table<p>
	 * Note: The same symbol cannot be added to the symbol table multiple times
	 * @param v
	 */
	public void addVariable(Variable v) throws SymbolicException {
		if (v==null) { 
			throw new SymbolicException(v, " cannot be used without being declared"); 
		}
		if (v.getIdentifier()==null) { 
			throw new Error("addVariable to SymbolTable called on Variable with null identifier"); 
		}
		//make sure variable isnot already in the symbol table
		if (symbolHashMap.containsKey(v.getIdentifier())) {
			throw new SymbolicException(v, " already exists in sybmol table"); 
		}
		if (v.getDataType()==Variable.DATA_TYPE_STRING) {
			v.setIdentifier(getNextStringID()); 
		}
		if (v.getDataType() == Variable.DATA_TYPE_COMPLEX){
			throw new SymbolicException(v, " Complex is not a valid type");
		}
		symbolHashMap.put(v.getIdentifier(), v); 
	}
	
	/**
	 * Returns the Variable corresponding to the identifier provided<br>
	 * @param the identifier of the desired variable 
	 * @return variable corresponding to the provided identifier if in SymbolTable, else returns null
	 */
	public Variable getVariable(String identifier) throws SymbolicException{ 
		Variable v;
		if (identifier==null) {
			throw new Error("getVariable from SymbolTable called with null identifier"); 
		}
		v = (Variable) symbolHashMap.get(identifier);
		if(v == null)
		{
			throw new SymbolicException(null, identifier + " must be declared before use");
		}
		return v; 
	}
	
	public Variable addStringVariable(String identifier) throws SymbolicException
	{
		Variable v;
		v = (Variable) symbolHashMap.get(identifier);
		if(v==null)
		{
			v = Variable.getNewAtomic(Variable.LITERAL_string, identifier, 0, true);
			addVariable(v);
		}
		return v;	
	}
	
	
	public ArrayList getAllVariables() { 
		ArrayList vars = new ArrayList(); 
		
		Iterator it = symbolHashMap.keySet().iterator(); 
		
		while (it.hasNext()) {
			Variable v = (Variable) symbolHashMap.get(it.next());
			vars.add(v); 
		}
		return vars; 
	}
}
