package funjay.astvisitor;

import funjay.abssyntree.*;
import java.util.*;

public class InterpretingVisitor extends DepthFirstVisitor {

    private int result;
    private boolean boolResult() { return result != 0; }
    private void setBoolResult(boolean b) {  result = b? 1 : 0; }

    private HashMap<String, Integer> gammaG;
    private HashMap<String, Integer> gammaM;
    private int[] mu;
    private int a;
    private int sigma(String id) { return mu[gammaM.get(id)];  }
    private void sigmaU(String id, int val) { mu[gammaM.get(id)] = val; }

    private void allocate(Iterator<Identifier> ids) {
        for (; ids.hasNext(); )  
            gammaM.put(ids.next().getValue(), ++a);
    }

    private void deallocate(Iterator<Identifier> ids) {
        for (; ids.hasNext(); ) {
            a--;
            gammaM.remove(ids.next().getValue());
        }
    }

    private HashMap<String, Vector<Identifier>> paramMap;
    private HashMap<String, Block> bodyMap;

    public void visit(Program n) {
        mu = new int[1024];
        a = 0;
        
        // This is a bit of a hack. Declaration is hard-wired to
        // add to gammaM, but this time we want it to populate
        // gammaG. So, we just copy gammaM to gammaG when
        // it's done.
        gammaM = new HashMap<String,Integer>();
        addDecl = true;
        for (Iterator<Declaration> it = n.f0(); it.hasNext(); ) 
            it.next().accept(this);
        gammaG = (HashMap<String,Integer>) gammaM.clone();

        paramMap = new HashMap<String, Vector<Identifier>>();
        bodyMap = new HashMap<String, Block>();
        for (Iterator<Method> it = n.f1(); it.hasNext(); ) 
            it.next().accept(this);
        n.f2().accept(this);
    }

    private boolean addDecl;
    public void visit(Declaration n) {
        if (addDecl)
            allocate(n.f1());
        else
            deallocate(n.f1());
    }

    // When we visit methods and their parameters, it's not to
    // interpret them but to populate the paramMap and bodyMap.
    private Vector<Identifier> currentParams;
    public void visit(Method n) {
        currentParams = new Vector<Identifier>();
        for (Iterator<Parameter> it = n.f2(); it.hasNext(); )
            it.next().accept(this);
        paramMap.put(n.f1().getValue(), currentParams);
        bodyMap.put(n.f1().getValue(), n.f3());
    }

    public void visit(Parameter n) {
        currentParams.add(n.f1());
    }
    

    // skip can be inherited from DepthFirstVisitor.

    public void visit(Block n) {
        addDecl = true;
        for (Iterator<Declaration> it = n.f0(); it.hasNext(); ) 
            it.next().accept(this);
        for (Iterator<Statement> it = n.f1(); it.hasNext(); ) 
            it.next().accept(this);
        addDecl = false;
        for (Iterator<Declaration> it = n.f0(); it.hasNext(); ) 
            it.next().accept(this);        
    }


    public void visit(Assignment n) {
        n.f1().accept(this);
        sigmaU(n.f0().getValue(), result);
    }

      public void visit(Conditional n) {
        n.f0().accept(this);
        if (boolResult())
            n.f1().accept(this);
        else
            n.f2().accept(this);
      }
  
    public void visit(Loop n) {
        for (;;) {
            n.f0().accept(this);
            if (! boolResult()) break;
            n.f1().accept(this);
        }
    }
    
    public void visit(Print n) {
         n.f0().accept(this);
         System.out.println(result);
   }

    public void visit(CallStmt n) {
        // don't evaluate the actual parameters; assume they're identifiers
        /*
        Vector<Integer> actuals = new Vector<Integer>();
        for (Iterator<Expression> it = n.f1(); it.hasNext(); ) {
            it.next().accept(this);
            actuals.add(result);
        }
        */

        // set up the new environment, starting from the globals
        // and adding the formal parameters
        HashMap<String,Integer> gammaT = gammaM;
        gammaM = (HashMap<String,Integer>) gammaG.clone();
        Vector<Identifier> formals = paramMap.get(n.f0().getValue());

        // don't allocate for the formals.
        /* 
        allocate(formals.iterator());
        Iterator<Identifier> formIt = formals.iterator();
        for (Iterator<Integer> actIt = actuals.iterator();
             actIt.hasNext() && formIt.hasNext(); ) 
            sigmaU(formIt.next().getValue(), actIt.next());
        */
        // make them aliases instead
        Iterator<Identifier> formIt = formals.iterator();
        for (Iterator<Expression> actIt = n.f1(); actIt.hasNext() && formIt.hasNext(); )
            gammaM.put(formIt.next().getValue(), 
                       gammaT.get(((Variable) actIt.next()).f0().getValue()));
        
        // "call" the funtion
        bodyMap.get(n.f0().getValue()).accept(this);
        // deallocate and restore
        //deallocate(formals.iterator());
        gammaM = gammaT;
    }

     public void visit(Return n) {
         n.f0().accept(this);
     }
   
     public void visit(Variable n) {
         result = sigma(n.f0().getValue());
     }


    public void visit(IntLitExpr n) {
        result = n.f0().getValue();
    }


    public void visit(BoolLitExpr n) {
        setBoolResult(n.f0().getValue());
    }


    public void visit(BinaryExpr n) {
        n.f0().accept(this);
        String op = n.f1().getValue();
        if (op.equals("+")) {
            int operand1 = result;
            n.f2().accept(this);
            result = operand1 + result;
        }
        if (op.equals("-")) {
            int operand1 = result;
            n.f2().accept(this);
            result = operand1 - result;
        }
        if (op.equals("*")) {
            int operand1 = result;
            n.f2().accept(this);
            result = operand1 * result;
        }
        if (op.equals("/")) {
            int operand1 = result;
            n.f2().accept(this);
            result = operand1 / result;
        }
        if (op.equals("&&")) {
            boolean operand1 = boolResult();
            n.f2().accept(this);
            setBoolResult(operand1 && boolResult());
        }
        if (op.equals("||")) {
            boolean operand1 = boolResult();
            n.f2().accept(this);
            setBoolResult(operand1 || boolResult());
        }
        if (op.equals("==")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 == result);
        }
        if (op.equals("!=")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 != result);
        }
        if (op.equals("<=")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 <= result);
        }
        if (op.equals(">=")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 >= result);
        }
        if (op.equals("<")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 < result);
        }
        if (op.equals(">")) {
            int operand1 = result;
            n.f2().accept(this);
            setBoolResult(operand1 > result);
        }
    }


    public void visit(UnaryExpr n) {
        n.f1().accept(this);
        setBoolResult(!boolResult());
    }

    public void visit(Call n) {
        // evaluate the actual parameters
        Vector<Integer> actuals = new Vector<Integer>();
        for (Iterator<Expression> it = n.f1(); it.hasNext(); ) {
            it.next().accept(this);
            actuals.add(result);
        }

        // set up the new environment, starting from the globals
        // and adding the formal parameters
        HashMap<String,Integer> gammaT = gammaM;
        gammaM = (HashMap<String,Integer>) gammaG.clone();
        Vector<Identifier> formals = paramMap.get(n.f0().getValue());
        allocate(formals.iterator());
        Iterator<Identifier> formIt = formals.iterator();
        for (Iterator<Integer> actIt = actuals.iterator();
             actIt.hasNext() && formIt.hasNext(); ) 
            sigmaU(formIt.next().getValue(), actIt.next());
            
        // "call" the funtion
        bodyMap.get(n.f0().getValue()).accept(this);

        // deallocate and restore
        deallocate(formals.iterator());
        gammaM = gammaT;
    }


}