import java.util.ArrayList;

/**
 * Derived class that represents an expression in the SILLY language.
 *   @author Dave Reed
 *   @version 2/8/12
 */
public class Expression {
    private ArrayList<Token> expr;
    
    /**
     * Creates an expression from the specified TokenStream.
     *   @param input the TokenStream from which the program is read
     */
    public Expression(TokenStream input) throws Exception {
        this.expr = new ArrayList<Token>();
        
        Token token1 = input.next();
        if (!token1.toString().equals("(")) {
            this.expr.add(token1);
        }
        else {
            Token token2 = input.next();
            while (!token2.toString().equals(")")) {
                this.expr.add(token2);
                token2 = input.next();
            }
        }
        
        if (this.expr.size() % 2 == 0) {
            throw new Exception("SYNTAX ERROR: Malformed expression");
        }
        else {
            for (int i = 0; i < this.expr.size(); i++) {
                if ((i % 2 == 0 && this.expr.get(i).getType() != Token.Type.INTEGER && 
                                   this.expr.get(i).getType() != Token.Type.STRING &&
                                   this.expr.get(i).getType() != Token.Type.IDENTIFIER) ||
                    (i % 2 == 1 && this.expr.get(i).getType() != Token.Type.OPERATOR)) {
                      throw new Exception("SYNTAX ERROR: Malformed expression");  
                }
            }
        }
    }
    
    /**
     * Private helper method, called by evaluate.
     */
    private Value getValue(Token t, Bindings bindings) throws Exception {        
        if (t.getType() == Token.Type.IDENTIFIER) {
            return bindings.lookup(t);
        }
        else if (t.getType() == Token.Type.INTEGER) {
            return new Value(Integer.parseInt(t.toString()));
        }
        else {
            return new Value(t.toString());
        }
    }
    
    /**
     * Evaluates the current expression involving integers and/or variables.
     *   @param bindings the current variable bindings
     *   @return the value represented by the expression
     */
    public Value evaluate(Bindings bindings) throws Exception {
        Value value = this.getValue(this.expr.get(0), bindings);
    
        for (int i = 1; i < this.expr.size(); i+=2) {
            Token operator = this.expr.get(i);
            Value operandVal = this.getValue(this.expr.get(i+1), bindings);

            if (operator.toString().equals("+")) {
                if (value.getType() == Value.Type.INTEGER &&
                     operandVal.getType() == Value.Type.INTEGER) {
                    value = new Value((Integer)value.getValue() +
                                      (Integer)operandVal.getValue());
                }
                else {
                    if (value.getType() == Value.Type.INTEGER) {
                        value = new Value("\""+(Integer)value.getValue()+"\"");
                    }
                    else if (operandVal.getType() == Value.Type.INTEGER) {
                        operandVal = new Value("\""+(Integer)operandVal.getValue()+"\"");
                    }
                    String str1 = (String)value.getValue();
                    String str2 = (String)operandVal.getValue();
                    value = new Value(str1.substring(0, str1.length()-1) +
                                      str2.substring(1, str2.length()));
                }
            }
            else if (value.getType() != Value.Type.INTEGER ||
                     operandVal.getType() != Value.Type.INTEGER) {
                         throw new Exception("RUNTIM ERROR: incompatible types");
            }
            else if (operator.toString().equals("-")) {
                    value = new Value((Integer)value.getValue() -
                                      (Integer)operandVal.getValue());
            }          
        }
        return value;
    }
      
	/**
	 * Converts the current expression into a String.
	 *   @return the String representation of this expression
	 */
	public String toString() {
        if (this.expr.size() == 0) {
            return "";
        }
        else if (expr.size() == 1) {
            return this.expr.get(0).toString();
        }
        else {
            String str = "(";
            for (Token t : this.expr) {
                str += " " + t;
            }
            return str + " )";
        }
    }
}
