// Mike Maxim
// Absyn tree printer. Fulfils the role of visitor in the Visitor pattern
// scheme

package edu.cmu.cs.l3.absyn;

import edu.cmu.cs.l3.general.*;
import java.util.List;
import edu.cmu.cs.l3.symbol.Symbol;

public class ASPrintAbsyn implements Visitor {

    java.io.PrintStream out;

    /** Build the printer
     * @param o Specify the output stream for the printer
     */
    public ASPrintAbsyn(java.io.PrintStream o) {
        out = o;
        indentLevel = 0;
    }

    /** Dispatch various visitor handlers depending on the type of obj
     * @param type The String type of the class
     * @param obj The object itself
     */
    public void visitResponse(String type, Object obj) {

        if (type.equals("ASSimpleLval")) {
            visitSimpleVar(obj);
        }
        else if (type.equals("ASIntConstant")) {
            visitIntConstant(obj);
        }
        else if (type.equals("ASLvalExpression")) {
            visitVarExp(obj);
        }
        else if (type.equals("ASAssignStatement")) {
            visitAssign(obj);
        }
        else if (type.equals("ASStatementList")) {
            visitStmList(obj);
        }
        else if (type.equals("ASOpExpression")) {
            visitOpExp(obj);
        }
        else if (type.equals("ASReturnStatement")) {
            visitReturn(obj);
        }
        else if (type.equals("ASBlock")) {
            visitBlock(obj);
        }
        else if (type.equals("ASBlankStatement")) {
            visitBlankStm(obj);
        }
        else if (type.equals("ASBoolConstant")) {
            visitBoolConstant(obj);
        }
        else if (type.equals("ASDeclarationList")) {
            visitDecList(obj);
        }
        else if (type.equals("ASDeclarationType")) {
            visitDecType(obj);
        }
        else if (type.equals("ASDeclarationVariable")) {
            visitDecVar(obj);
        }
        else if (type.equals("ASFieldLval")) {
            visitFieldVariable(obj);
        }
        else if (type.equals("ASIfStatement")) {
            visitIf(obj);
        }
        else if (type.equals("ASDerefLval")) {
            visitMemVar(obj);
        }
        else if (type.equals("ASNullConstant")) {
            visitNull(obj);
        }
        else if (type.equals("ASPointerExpression")) {
            visitPointerExp(obj);
        }
        else if (type.equals("ASPrimitiveType")) {
            visitPrimitiveType(obj);
        }
        else if (type.equals("ASExpressionStatement")) {
            visitStmExp(obj);
        }
        else if (type.equals("ASUserType")) {
            visitUserType(obj);
        }
        else if (type.equals("ASRecordType")) {
            visitRecordType(obj);
        }
        else if (type.equals("ASPointerType")) {
            visitPointerType(obj);
        }
        else if (type.equals("ASDeclarationProc")) {
            visitProc(obj);
        }
        else if (type.equals("ASProcedureExpression")) {
            visitProcExp(obj);
        }
        else if (type.equals("ASForStatement")) {
            visitFor(obj);
        }
        else if (type.equals("ASWhileStatement")) {
            visitWhile(obj);
        }
        else if (type.equals("ASBreakStatement")) {
            visitBreak(obj);
        }
        else if (type.equals("ASContinueStatement")) {
            visitContinue(obj);
        }
        else {
            System.out.println(
                "Severe Internal Compiler Warning: Visit passing unknown visitable!: " +
                type);
        }
    }

    private void visitBreak(Object b) {
        indent(); sayln("BreakStm()");
    }

    private void visitContinue(Object c) {
        indent(); sayln("ContinueStm()");
    }

    private void visitWhile(Object w) {

        ASWhileStatement aw = (ASWhileStatement) w;

        indent(); sayln("While(");
        indentLevel++;
        indent(); aw.getTest().visit(this); sayln(",");
        indent(); aw.getBody().visit(this);  say(")");
        indentLevel--;
    }

    private void visitFor(Object f) {

        ASForStatement af = (ASForStatement) f;

        indent(); sayln("For(");
        indentLevel++;
        af.getAssignment().visit(this);
        af.getEndTest().visit(this);
        af.getForWorkStatement().visit(this);
        af.getBody().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitProcExp(Object p) {

        ASProcedureExpression ap = (ASProcedureExpression) p;
        int i;

        indent(); say("Call(");
        if (ap.getParameters().size() > 0)
            sayln(ap.getName().toString() + " ");
        else
            say(ap.getName().toString());

        indentLevel++;
        for (i = 0; i < ap.getParameters().size(); i++) {
            ASExpression param = (ASExpression) ap.getParameters().get(i);
            param.visit(this);
        }
        indentLevel--;
        if (ap.getParameters().size() > 0) indent(); sayln(")");
    }


    private void visitProc(Object pp) {

       ASDeclarationProc ap = (ASDeclarationProc) pp;
       int i;

       indent(); sayln("FunDecl(" + ap.getName().toString() + ") {");
       indentLevel++;
       for (i = 0; i < ap.getParams().size(); i++) {
           Pair p = (Pair) ap.getParams().get(i);
           indent(); sayln(((Symbol)p.getLeft()).toString() + " ");
           ((ASType)p.getRight()).visit(this);
       }
       if (ap.getBody() != null) ap.getBody().visit(this);
       indentLevel--;
       indent(); sayln("}");
   }


    /** Print out the return absyn */
    private void visitReturn(Object r) {

        ASReturnStatement ar = (ASReturnStatement) r;

        indent(); sayln("Return(");
        indentLevel++;
        if (ar.hasReturnExpression()) {
            ar.getReturnExpression().visit(this);
        }
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitPointerType(Object p) {

        ASPointerType ap = (ASPointerType) p;

        indent(); sayln("PointerType(");
        indentLevel++;
        ap.getUnderType().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitUserType(Object t) {

        ASUserType at = (ASUserType) t;

        indent(); sayln("UserType(" + at.getName().toString() + ")");
    }

    private void visitNull(Object n) {
        indent(); sayln("NULL()");
    }

    private void visitMemVar(Object v) {

        ASDerefLval av = (ASDerefLval) v;

        indent(); sayln("Deref(");
        indentLevel++;
        av.getVariableExpression().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitIf(Object i) {

        ASIfStatement ifstm = (ASIfStatement) i;

        indent(); sayln("If(");
        indentLevel++;
        ifstm.getTest().visit(this);
        indentLevel--; indent(); sayln("Then("); indentLevel++;
        ifstm.getThenClause().visit(this);
        if (ifstm.getElseClause() != null) {
            indentLevel--; indent(); sayln("Else("); indentLevel++;
            ifstm.getElseClause().visit(this);
            indentLevel--;
            indent(); sayln(")");
        }
        else {
            indentLevel--;
            indent(); sayln(")");
        }
    }

    private void visitBlock(Object b) {

        ASBlock ab = (ASBlock) b;

        indent(); sayln("Block(");
        indentLevel++;
        if (ab.getDeclaration() != null) {
            ab.getDeclaration().visit(this);
        }
        if (ab.getStatement() != null) {
            ab.getStatement().visit(this);
        }
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitFieldVariable(Object v) {

        ASFieldLval av = (ASFieldLval) v;

        indent(); sayln("Field(");
        indentLevel++;
        indent(); sayln(av.getField().toString());
        av.getRecord().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitPointerExp(Object e) {

        ASPointerExpression ae = (ASPointerExpression) e;

        if (ae.getOperation() == ASPointerExpression.AMP ||
            ae.getOperation() == ASPointerExpression.OFFSET ||
            ae.getOperation() == ASPointerExpression.SIZE) {
            switch (ae.getOperation()) {
                case ASPointerExpression.AMP:
                    indent(); sayln("Reference(");
                    indentLevel++;
                    break;
                case ASPointerExpression.OFFSET:
                    indent(); sayln("Offset(");
                    indentLevel++;
                    break;
                case ASPointerExpression.SIZE:
                    indent(); sayln("Size(");
                    indentLevel++;
                    break;
                case ASPointerExpression.INC:
                    indent(); sayln("Inc(");
                    indentLevel++;
                    break;
                case ASPointerExpression.DEC:
                    indent(); sayln("Dec(");
                    indentLevel++;
                    break;
            }
            ae.getOperand().visit(this);
        }
        else {
            indent(); sayln("Alloc(");
            indentLevel++;
            ae.getType().visit(this);
            ae.getNumber().visit(this);
            if (ae.getInit() != null) {
                ae.getInit().visit(this);
            }
        }

        indentLevel--;
        indent(); sayln(")");
    }

    private void visitRecordType(Object t) {

        ASRecordType at = (ASRecordType) t;
        List els = at.getFields();
        int i;

        indent(); sayln("StructDecl(");
        indentLevel++;
        for (i = 0; i < els.size(); i++) {
            indent(); sayln(((Symbol)((Pair)els.get(i)).getLeft()).toString() + " ");
            ((ASType)((Pair)els.get(i)).getRight()).visit(this);
        }
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitPrimitiveType(Object t) {

        ASPrimitiveType at = (ASPrimitiveType) t;

        indent(); say("PrimitiveType(");
        sayln(at.getName().toString() + ")");
    }

    private void visitStmExp(Object s) {

        ASExpressionStatement as = (ASExpressionStatement) s;

        indent(); sayln("StmtExp(");
        indentLevel++;
        as.getExpression().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitBlankStm(Object b) {

        ASBlankStatement ab = (ASBlankStatement) b;
        indent(); sayln("BLANK()");
    }

    /** Print out the simple variable */
    private void visitSimpleVar(Object v) {
        ASSimpleLval av = (ASSimpleLval) v;

        indent(); say("Lval(");
        say(av.getName().toString());
        sayln(")");
    }

    /** Print out the integer constant */
    private void visitIntConstant(Object i) {

        ASIntConstant ai = (ASIntConstant) i;

        indent(); say("Const(");
        say( (int) ai.getValue());
        sayln(")");
    }

    /** Print out the operation expression */
    private void visitOpExp(Object e) {

        ASOpExpression ai = (ASOpExpression) e;

        indent(); say("OpExp(");
        switch (ai.getOperator()) {
            case ASOpExpression.PLUS:
                say("PLUS");
                break;
            case ASOpExpression.MINUS:
                say("MINUS");
                break;
            case ASOpExpression.MUL:
                say("MUL");
                break;
            case ASOpExpression.DIV:
                say("DIV");
                break;
            case ASOpExpression.MOD:
                say("MOD");
                break;
            case ASOpExpression.NEG:
                say("UMINUS");
                break;
            case ASOpExpression.EQ:
                say("EQ");
                break;
            case ASOpExpression.NE:
                say("NEQ");
                break;
            case ASOpExpression.GT:
                say("GT");
                break;
            case ASOpExpression.GE:
                say("GE");
                break;
            case ASOpExpression.LT:
                say("LT");
                break;
            case ASOpExpression.LE:
                say("LE");
                break;
            case ASOpExpression.AND:
                say("LOGICAND");
                break;
            case ASOpExpression.OR:
                say("LOGICOR");
                break;
            case ASOpExpression.XOR:
                say("LOGICXOR");
                break;
            case ASOpExpression.NOT:
                say("LOGICNOT");
                break;
            case ASOpExpression.BAND:
                say("BITAND");
                break;
            case ASOpExpression.BOR:
                say("BITOR");
                break;
            case ASOpExpression.BXOR:
                say("BITXOR");
                break;
            case ASOpExpression.BFLIP:
                say("BITNOT");
                break;
            case ASOpExpression.LSHIFT:
                say("SHIFTLEFT");
                break;
            case ASOpExpression.RSHIFT:
                say("SHIFTRIGHT");
                break;
            case ASOpExpression.PADD:
                say("PPLUS");
                break;
            case ASOpExpression.PMINUS:
                say("PMINUS");
                break;
            case ASOpExpression.PEQ:
                say("PEQ");
                break;
            case ASOpExpression.PNEQ:
                say("PNEQ");
                break;
            default:
                throw new Error("Unknown Op");
        }
        sayln(",");
        indentLevel++;
        ai.getLeft().visit(this);

        if (ai.getRight() != null) {
            ai.getRight().visit(this);
        }
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitBoolConstant(Object e) {

        ASBoolConstant ai = (ASBoolConstant) e;

        indent(); say("BoolConst(");
        say(ai.getValue());
        sayln(")");
    }

    private void visitDecVar(Object v) {

        ASDeclarationVariable d = (ASDeclarationVariable) v;

        indent(); sayln("DecVar(" + d.getVariableName().toString() + ",");
        indentLevel++;
        if (d.getTypeInfo().getASType() == null)
            System.out.println("null type!");
        d.getTypeInfo().getASType().visit(this);
        if (d.getInitExpression() != null) {
            d.getInitExpression().visit(this);
        }
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitDecType(Object v) {

        ASDeclarationType d = (ASDeclarationType) v;

        indent(); sayln("DecType(" + d.getName().toString());
        indentLevel++;
        d.getType().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    private void visitDecList(Object dl) {

        int i;
        ASDeclarationList adl = (ASDeclarationList) dl;

        indent(); say("DecList(");
        if (adl != null) {
            sayln("");
            indentLevel++;

            for (i = 0; i < adl.getSize(); i++) {
                adl.getDeclaration(i).visit(this);
            }

            indentLevel--;
        }
        indent(); sayln(")");
    }

    /** Print out the variable expression */
    private void visitVarExp(Object v) {

        ASLvalExpression av = (ASLvalExpression) v;

        indent(); sayln("VarExp(");
        indentLevel++;
        av.getVariable().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    /** Print out the assignment statement absyn */
    private void visitAssign(Object s) {

        ASAssignStatement as = (ASAssignStatement) s;

        indent(); sayln("Assign(");
        indentLevel++;
        as.getVariable().visit(this);
        as.getExpression().visit(this);
        indentLevel--;
        indent(); sayln(")");
    }

    /** Print out a statement list */
    private void visitStmList(Object ss) {

        int i;
        ASStatementList adl = (ASStatementList) ss;

        indent(); say("StmList(");
        if (adl != null) {
            sayln("");
            indentLevel++;

            for (i = 0; i < adl.getSize(); i++) {
                adl.getStatement(i).visit(this);
            }

            indentLevel--;
        }
        indent(); sayln(")");
    }

    void indent() {
        for (int i = 0; i < indentLevel; i++)
            out.print(' ');
    }

    void indent(int d) {
        for (int i = 0; i < d; i++)
            out.print(' ');
    }

    void say(boolean b) {
        Boolean local = new Boolean(b);
        out.print(local.toString());
    }

    void say(String s) {
        out.print(s);
    }

    void say(int i) {
        Integer local = new Integer(i);
        out.print(local.toString());
    }

    void sayln(String s) {
        say(s);
        say("\n");
    }

    int indentLevel = 0;
}