/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ca.mcmaster.cas.matlab2smt;

import ca.mcmaster.cas.matlab2smt.MatlabParserParser.G1Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.G2Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.G3Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.G4Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E0Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E1Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E2Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E3Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E4Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E5Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E6Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E7Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E8Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E9Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E10Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.E11Context;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.Literal_numberContext;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.Id_plus_indexersContext;
import ca.mcmaster.cas.matlab2smt.MatlabParserParser.Prefix_operatorContext;
import java.util.ArrayDeque;
import java.util.Deque;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

/**
 *
 * @author matthew
 */
final public class MatlabParser implements MatlabExpressionWithSubExpression {

    public MatlabParser(VariableParser variableListing, String matlabCode) {
        m_variableParser = variableListing;
        m_opStack.addFirst(new ExpressionStackContainer(0, this));
        parseMatlabCode(matlabCode + "\n");
    }

    private void parseMatlabCode(String matlabCode) {
        // create a CharStream that reads from standard input
        ANTLRInputStream input = new ANTLRInputStream(matlabCode);

        // create a lexer that feeds off of input CharStream
        MatlabParserLexer lexer = new MatlabParserLexer(input);

        // create a buffer of tokens pulled from the lexer
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        // create a parser that feeds off the tokens buffer
        MatlabParserParser parser = new MatlabParserParser(tokens);

        ParseTree tree = parser.expression();
        ParseTreeWalker walker = new ParseTreeWalker();
        MatlabParserListener listener = new MatlabParser.MatlabParserToMatlabExpressions();
        walker.walk(listener, tree);
    }

    public MatlabExpression getRootExpression() {
        return m_rootExpression;
    }

    private void incrementLevel() {
        this.m_level++;
    }

    private void decrementLevel() {
        this.m_level--;
        if (!m_hiddenOpStack.isEmpty() && m_hiddenOpStack.peekFirst().level == m_level) {
            // We have a hidden op here.  The normal methods won't find it, so make use of it!
            HiddenOpStackContainer hiddenOp = m_hiddenOpStack.peekFirst();
            addExpressionBinaryOperation(new MatlabExpressionBinaryOperation(null, hiddenOp.op, null));
            if (--hiddenOp.count == 0) {
                m_hiddenOpStack.removeFirst();
            }
        }

        if (!m_opStack.isEmpty() && m_opStack.peekFirst().level > m_level) {
            m_opStack.removeFirst();
        }
    }

    private void enqueHiddenOp(MatlabBinaryOperation op, int count) {
        m_hiddenOpStack.addFirst(new HiddenOpStackContainer(m_level, op, count));
    }

    private void addExpressionValue(MatlabExpressionValue expressionValue) {
        m_opStack.peekFirst().op.setSubExpression(expressionValue);
    }

    private void addExpressionBinaryOperation(MatlabExpressionBinaryOperation op) {
        if (m_rootExpression == null) {
            throw new IllegalStateException("It should not be possible to have"
                    + "an op where the left hand side is a null expression!");
        } else {
            if (m_opStack.peekFirst().level >= m_level) { //This current op happens after the root expression op.  Pull it up.
                op.setLHS((MatlabExpression) m_opStack.removeFirst().op);
                m_opStack.peekFirst().op.setSubExpression(op);
            } else { //This op happens before.  Push it down the tree.
                op.setLHS(m_opStack.peekFirst().op.getSubExpression());
                m_opStack.peekFirst().op.setSubExpression(op);
            }
            m_opStack.addFirst(new ExpressionStackContainer(m_level, op));
        }
    }

    private void addPrefixUnaryOperation(MatlabExpressionUnaryOperation op) {
        /* Unlike Binary operations, unary operations just always insert
         * themselves into the operation stack exactly where they appear.
         * The whole left hand side handling never happens, since there is no
         * left hand side.  So just put it in place.
         *
         * Also, an unary operation is allowed to deal with a null root expression,
         * since it doesn't use it!
         */
        m_opStack.peekFirst().op.setSubExpression(op);
        m_opStack.addFirst(new ExpressionStackContainer(m_level, op));
    }

    @Override
    public MatlabExpression getSubExpression() {
        return m_rootExpression;
    }

    @Override
    public void setSubExpression(MatlabExpression subExpression) {
        m_rootExpression = subExpression;
    }

    private class MatlabParserToMatlabExpressions extends MatlabParserBaseListener {

        @Override
        public void enterE0(E0Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE1(E1Context ctx) {
            incrementLevel();
            if (ctx.LOG_OR().size() > 0) {
                enqueHiddenOp(MatlabBinaryOperation.BooleanOr, ctx.LOG_OR().size());
            }
        }

        @Override
        public void enterE2(E2Context ctx) {
            incrementLevel();
            if (ctx.LOG_AND().size() > 0) {
                enqueHiddenOp(MatlabBinaryOperation.BooleanAnd, ctx.LOG_AND().size());
            }
        }

        @Override
        public void enterE3(E3Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE4(E4Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE5(E5Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE6(E6Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE7(E7Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE8(E8Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE9(E9Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE10(E10Context ctx) {
            incrementLevel();
        }

        @Override
        public void enterE11(E11Context ctx) {
            incrementLevel();
        }

        @Override
        public void exitE0(E0Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE1(E1Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE2(E2Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE3(E3Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE4(E4Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE5(E5Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE6(E6Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE7(E7Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE8(E8Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE9(E9Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE10(E10Context ctx) {
            decrementLevel();
        }

        @Override
        public void exitE11(E11Context ctx) {
            decrementLevel();
        }

        @Override
        public void enterG1(G1Context ctx) {
            addExpressionBinaryOperation(new MatlabExpressionBinaryOperation(null, MatlabBinaryOperation.getOpForSymbol(ctx.getText()), null));
        }

        @Override
        public void enterG2(G2Context ctx) {
            addExpressionBinaryOperation(new MatlabExpressionBinaryOperation(null, MatlabBinaryOperation.getOpForSymbol(ctx.getText()), null));
        }

        @Override
        public void enterG3(G3Context ctx) {
            addExpressionBinaryOperation(new MatlabExpressionBinaryOperation(null, MatlabBinaryOperation.getOpForSymbol(ctx.getText()), null));
        }

        @Override
        public void enterG4(G4Context ctx) {
            addExpressionBinaryOperation(new MatlabExpressionBinaryOperation(null, MatlabBinaryOperation.getOpForSymbol(ctx.getText()), null));
        }

        @Override
        public void exitG1(G1Context ctx) {
        }

        @Override
        public void exitG2(G2Context ctx) {
        }

        @Override
        public void exitG3(G3Context ctx) {
        }

        @Override
        public void exitG4(G4Context ctx) {
        }

        @Override
        public void enterPrefix_operator(Prefix_operatorContext ctx) {
            addPrefixUnaryOperation(new MatlabExpressionUnaryOperation(MatlabUnaryOperation.getOpForSymbol(ctx.getText()), null));
        }

        @Override
        public void exitPrefix_operator(Prefix_operatorContext ctx) {
        }

        @Override
        public void enterLiteral_number(Literal_numberContext ctx) {
            addExpressionValue(new MatlabLiteral(ctx.getText()));
        }

        @Override
        public void enterId_plus_indexers(Id_plus_indexersContext ctx) {
            addExpressionValue(m_variableParser.getVar(ctx.getText()));
        }
    }
    private MatlabExpression m_rootExpression;
    private int m_level = 0;
    private VariableParser m_variableParser;

    private static class ExpressionStackContainer {

        public int level;
        public MatlabExpressionWithSubExpression op;

        public ExpressionStackContainer(int level, MatlabExpressionWithSubExpression op) {
            this.level = level;
            this.op = op;
        }
    }
    Deque<ExpressionStackContainer> m_opStack = new ArrayDeque<MatlabParser.ExpressionStackContainer>();

    private static class HiddenOpStackContainer {

        public int level, count;
        public MatlabBinaryOperation op;

        public HiddenOpStackContainer(int level, MatlabBinaryOperation op, int count) {
            this.level = level;
            this.op = op;
            this.count = count;
        }
    }
    Deque<HiddenOpStackContainer> m_hiddenOpStack = new ArrayDeque<HiddenOpStackContainer>();
}
