import java.util.Stack;
import java.util.HashMap;

/**
 * Class that stores variable bindings in the SILLY language.
 *   @author Dave Reed
 *   @version 2/20/12
 */
public class Bindings {
    private Stack< HashMap<Token, Value> > runTimeStack;  // stores activation records
    private HashMap<Token, Sub> codeSegment;              // stores subroutine definitions

    /**
     * Constructor for objects of class Bindings
     */
    public Bindings() {
         this.runTimeStack = new Stack< HashMap<Token, Value> >();
         this.codeSegment = new HashMap<Token, Sub>();
         this.beginNewScope();
    }
    
    /**
     * Begins a new scope by pushing an activation record on the runtime stack.
     */
    public void beginNewScope() {
        this.runTimeStack.push(new HashMap<Token, Value>());
    }
    
    /**
     * Ends the current scope by popping an activation record off the runtime stack.
     */
    public void endCurrentScope() {
        this.runTimeStack.pop();
    }
    
    /**
     * Declares a local variable (in the current scope) and initializes it to zero.
     *   @param var the variable (Token) being declared
     */
    public void declareLocal(Token var) {
        this.runTimeStack.peek().put(var, new Value(0));
    }
    
    /**
	 * Assigns a new value to a variable in the table.
	 *   @param var the variable (Token) to be assigned to
	 *   @param val the value to be assigned to var
	 */
	public void assign(Token var, Value val) {
	    HashMap<Token, Value> localScope = this.runTimeStack.peek();
	    HashMap<Token, Value> globalScope = this.runTimeStack.get(0);
	    if (localScope.containsKey(var)) {
	        localScope.put(var, val);
	    }
	    else {
	        globalScope.put(var, val);
	    }
	}
	
	/**
	 * Looks up the value of a variable, assigning value 0 if first occurrence.
	 *   @param var the variable (Token) to be looked up
	 *   @return the value associated with that variable
	 */
	public Value lookup(Token var) throws Exception {
	    HashMap<Token, Value> localScope = this.runTimeStack.peek();
	    HashMap<Token, Value> globalScope = this.runTimeStack.get(0);
	    if (localScope.containsKey(var)) {
	        return localScope.get(var);
	    }
	    else if (globalScope.containsKey(var)) {
	        return globalScope.get(var);
	    }
	    else {
	        throw new Exception("RUNTIME ERROR: Undefined/undeclared variable");
	    }
	}

	/**
	 * Stores the subroutine code in the code segment.
	 *   @param subName the name (Token) of the subroutine
	 *   @param subCode the Sub object that contains the subroutine code
	 */
	public void storeSubroutineCode(Token subName, Sub subCode) {
	    this.codeSegment.put(subName, subCode);
	}

    /**
	 * Looks up the subroutine code from the code segment.
	 *   @param subName the name (Token) of the subroutine
	 *   @return the Sub object that contains the subroutine code
	 */
	public Sub lookupSubroutineCode(Token subName) throws Exception {
	   if (this.codeSegment.containsKey(subName)) {
	       return this.codeSegment.get(subName);
	   }
	   else {
	       throw new Exception("RUNTIME ERROR: Undefined subroutine");
	   }
	}
}
