/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package IntermediaryCode;

import API.Api;
import API.Functions;
import API.Instruction;
import API.Tipos;
import API.Utils;
import API.Variaveis;
import ast.AbstractSyntaxTree;
import ast.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Pattern;
//            a0      a1
//    sync    recebe  mandando
//    exec    recebe  mandando 
//    synexec recebe  mandando

/**
 *
 * @author Eugenio
 */
public final class BaseTacGen implements TacGenInterface {

    protected boolean inicializado = false;
    protected int incLabel = 0;
    protected int tmpVarCount = 1;
    protected AbstractSyntaxTree ast;
    protected Code code;
    protected Node calEndAtt;
    protected Node compileParams;
    protected String label;
    protected String calEndVar;
    protected String calEndSize;
    protected String calEndTipo;
    protected Stack<Node> master = new Stack<>();
    protected ArrayList<String> line = new ArrayList<>();
    protected LinkedList<Boolean> branckInvert = new LinkedList<>();
    protected HashMap<String, String> arrayAcesso = new HashMap<>();
    protected HashMap<String, String> varGenMap = new HashMap<>();
    protected HashMap<String, String> funcGenMap = new HashMap<>();
    protected HashMap<String, Integer> systemCalls = new HashMap<>();
    protected ArrayList<String> currentMatrixvalues = new ArrayList<>();
    protected HashMap<String, ArrayList<String>> matrixValues = new HashMap<>();
//    protected ArrayList<CodeProcessing> afterCreate = new ArrayList<CodeProcessing>();
    protected HashMap<String, String> Complements = new HashMap<String, String>() {
        {
            put("!=", "==");
            put("==", "!=");
            put("<", ">=");
            put("<=", ">");
            put(">", "<=");
            put(">=", "<");
        }
    };
    public static Pattern AddressOperator = Pattern.compile("(\\&|\\*)");
    protected boolean enableOtimization = true;
    protected HashMap<String, String> ArrayShiftMap = new HashMap<>();

    public BaseTacGen() {

    }

    public BaseTacGen(AbstractSyntaxTree ast) {
        Ast(ast);
    }

//    @Override
//    public TacGenInterface AfterCreate(CodeProcessing p) {
//        this.afterCreate.add(p);
//        return this;
//    }
//
//    public TacGenInterface AfterCreate() throws Exception {
//        for (CodeProcessing x : afterCreate) {
//            x.Exec(code);
//        }
//        return this;
//    }
    public void Ast(AbstractSyntaxTree ast) {
        this.ast = ast;
        code = new Code("TAC", ast);
//        code.AfterClose("otimization.1", new CodeOtimizadorProcessor(enableOtimization));
        compileParams = ast.BuildParams();

        code.Template(new template.Template())
                .Formats(new HashMap<String, String>() {
                    {

                        String base = "{[PAD(global.position,_addressLen,' ')]':'}{[T(1)]}{[PAD(block.position,_addressLen,' ')]':'}{[T(2)]}";
                        String end = "{[T(2)]}{'  T< '[type]' >'}";

                        put("label", "{[PAD(global.position,_addressLen,' ')]':'}{[T(2)]}{'<'[label]'>'}':'");
                        put("assign", base + "{[dst]}' := '{[p1]}{' '[op]' '}{[p2]}{' --'[comment]}" + end);
                        put("pointer_assignment", base + "{[dst.pointer]}{[dst]' := '}{[op]}{[p1]}{' --'[comment]}" + end);
                        put("unary", base + " {[dst]} ' := '{[op]' '}{[p1]} {' --'[comment]}" + end);
                        put("copy", base + " {[dst]} ' := '{[p1]} {' --'[comment]}" + end);
                        put("jump", base + " 'goto ' {'<'[label]'>'} {' --'[comment]}" + end);
                        put("branch", base + " 'if '{[p1]} {' '[op]' '} {[p2]} ' goto ' {'<'[label]'>'} {' --'[comment]}" + end);
                        put("call", base + " {[dst]' := '} 'call <' {[funcname]} '>, ' {[nump]} {' --'[comment]}" + end);
                        put("return", base + " 'return ' {[p1]} {' --'[comment]}" + end);
                        put("push_param", base + " 'push_param '{[p1]} {' --'[comment]}" + end);
                        put("push_return", base + " 'push_return '{[p1]} {' --'[comment]}" + end);
                        put("pop_param", base + " 'pop_param '{[p1]} {' --'[comment]}" + end);
                        put("pop_return", base + " 'pop_return '{[p1]} {' --'[comment]}" + end);
                    }
                });
    }

    @Override
    public Code Create(AbstractSyntaxTree ast, HashMap<String, String> settings) throws Exception {
        Ast(ast);
//        setOtimizacao(settings.G("clearCode").equals("true"));
        Code code = run();
        // Executa todos os tratametos definidos apos geracao do codigo
//        AfterCreate();

        printVarMap();

//        code.PrintData();
        code.Print();
        code.printOcorrences();
        return code;
    }

    protected Node visit(Node n) throws Exception {
        if (null == n || n.eq("visited", "true")) {
            return n;
        }
        n.S("visited", "true");
//        System.out.println("Visit:" + n.Class());
        switch (n.Class()) {
            case "dec.const":
                gerarConstante(n);
                break;
            case "if.stmt":
                IfStmts(n);
                break;
            case "selector":
                Selector(n);
                break;
            case "dec.var":
//                Log.PrintInfo("TAC", new Instruction().S("msg", "DECVAR." + n));
                if (n.find("exprs") != null) {
                    n.S("class", "dec.var.short")
                            .S("value", "dec.var.short");
                    genAssign(n);
                    break;
                }
            case "def.assign":
                genAssign(n);
                break;
            case "call":
                genCall(n);
                break;
            case "ID":
                genID(n);
                break;
            case "incdec":
                IncDecStmt(n);
                break;
            case "for.stmt":
                genFor(n);
                break;
            case "expr":
                _visitExpr(n);
                break;
            case "unary":
                unaryStmt(n);
                break;
            case "switch.stmt":
                switchStmt(n);
                break;
            case "test":
                test(n);
                break;
            case "return":
                visitChildrensFirst(n);
                break;
            default:
                visitRootFirst(n);
                break;
        }
        return n;
    }

    protected void genID(Node n) throws Exception {
//        System.out.println("GenId:" + n);
        Node var = Variaveis.Get(n.getText());
        n.S("_return", gerarVariavel(var));
    }

    protected void test(Node n) throws Exception {
        Node x = null;

        ArrayList<Node> chils = n.childrens();
        int size = chils.size();
        for (int i = 0; i < size; i++) {
            x = chils.get(i);
            visit(x);
        }

        n.copy("_return", x);
    }

    protected void IncDecStmt(Node n) throws Exception {
        Instruction operacao = new Instruction();
        String dst = gerarVariavel(n.findByClass("selector"));
        operacao.S("type", "assign")
                .S("format", "assign")
                .S("cat", "exp")
                .S("_return", "exp")
                .S("dst", dst)
                .S("p1", dst)
                .S("p2", "1")
                .S("p1value", "" + Api.IsValue(dst))
                .S("p2value", "true")
                .S("op", n.findByClass("operator").eq("value", "++") ? "+" : "-");
        code.Add(operacao);
    }

    protected void Selector(Node n) throws Exception {
        String tmp;
        String shift = null;
        n.S("copymode", "copy");

        switch (n.G("subclass")) {
            case "operand":
                if (n.eq("classType", "value")) {
//                    n.S("value", Api.Value(n.getText()));
                    n.S("_return", Api.Value(n.getText()));
                } else {

                    String v = gerarVariavel(n);
                    if (n.eq("pointer", "*") && n.eq("access", "read")) {
                        String t = gerarVariavel("T");
                        Copy(t, v, false);
                        v = t;
                    }

                    n.S("_return", v);
                }
//                System.out.println("Seletor operand:" + n);
                break;
            case "index":
//                shift = n.childrens().G(0).getInt("value");
//                shift = n.childrens().G(0).getInt("value");

                shift = ArrayShift(n);

            case "selector":
                if (shift == null) {
                    shift = "" + Variaveis.Shift(n.getText());
                }

                tmp = gerarVariavel(n) + "[" + shift + "]";

                if (!n.eq("access", "write")) {
                    String t = gerarVariavel("T");
                    Copy(t, tmp, false);
                    tmp = t;
                }

                n.S("_return", tmp);
                break;

            case "arguments":
                break;
        }
    }

    protected String ArrayShift(Node n) throws Exception {
        // Cria a chave para a ocorrencia de um acesso ao array, evitando recalcular o endereco
        String key = n.getText();
        for (Node node : n.childrens()) {
            key += "_" + node.getText();
        }

        if (!ArrayShiftMap.containsKey(key)) {

//            String shift;
            Node var = Variaveis.Get(n.getText());
//            System.out.println("INDEX shift:" + n.childrens());

            for (int i = 0, j = 1; j < var.childrens().size(); j++) {
                n.childrens().get(i++).S("collNumber", var.childrens().get(j).getText());
            }

            String ret = CalcArrayAddress(n.childrens(), Tipos.Size(var.G("type")));
            // Se o offset for calculado em um inteiro atribui o mesmo como shift
//            if (Utils.isNumber(ret)) {
//                shift = ret;
//            } else {
////                 Se for um endereci faz a soma com o offset da varivel
////                __exp("+", ret, gerarVariavel(n), ret);
//                shift = ret;
//            }
            ArrayShiftMap.put(key, ret);
        }

        return ArrayShiftMap.get(key);
    }

    protected String CalcArrayAddress(ArrayList<Node> childrens, int Size) throws Exception {
        String offset = CalcArrayAddress(childrens, Size, 0);
        if (Utils.isNumber(offset)) {
            return "" + Integer.parseInt(offset) * Size;
        } else {
            String ret = gerarVariavel("T");
            __exp("*", offset, "" + Size, ret).S("comment", "Element count * size(Element)");
            return ret;
        }
    }

    protected String CalcArrayAddress(ArrayList<Node> childrens, int Size, int index) throws Exception {
        int nindex = index + 1;
        Node ind = childrens.remove(0);
        if (childrens.size() >= 1) {
            String ret = CalcArrayAddress(childrens, Size, nindex);
            int collNumber = ind.getInt("collNumber");

            if (Utils.isNumber(ret) && ind.isNumber("value")) {
                System.out.println("CalcArray:" + ind.getInt("value") + ":" + collNumber + ":" + Integer.parseInt(ret));
                return "" + ((ind.getInt("value") * collNumber) + Integer.parseInt(ret));
            } else {
                String retl = gerarVariavel("T");
                __exp("*", gerarVariavel(ind), "" + collNumber, retl)
                        .S("comment", "lines shift");
                __exp("+", retl, ret, retl)
                        .S("comment", "colls shift");
                return retl;
            }
        } else {

            if (Utils.isNumber(ind.getText())) {
                return ind.getText();
            } else {
                return gerarVariavel(ind);
            }

        }
    }

    /**
     * ProceSSa um node:<br>
     * Ordem:<br>
     * ->Primeiro filhoS;<br>
     * ->No principal;<br>
     *
     * @param n
     * @throws Exception
     */
    protected void visitChildrensFirst(Node n) throws Exception {
//        System.out.println("Visit:" + n.getText());

        for (Node n1 : n.childrens()) {
//            System.out.println("Visit:" + n1.getText());
            visit(n1);
        }
//        System.out.println("Process:" + n.getText());
        processarNode(n);
    }

    /**
     * ProceSSa um node:<br>
     * Ordem:<br>
     * ->Primeiro filho;<br>
     * ->No principal;<br>
     * ->Segundo filho;<br>
     *
     * @param n
     * @throws Exception
     */
    protected void visitNoMeio(Node n) throws Exception {
        visit(n.getFilho(0));
        processarNode(n);
        visit(n.getFilho(1));
    }

    /**
     * ProceSSa um node:<br>
     * Ordem:<br>
     * ->No princial;<br>
     * ->DepoiS oS filhoS
     *
     * @param n
     * @throws Exception
     */
    protected void visitRootFirst(Node n) throws Exception {
        processarNode(n);
        for (Node n1 : n.childrens()) {
            visit(n1);
        }
    }

    /**
     * Aloca endereço no inicio da memoria para conStanteS ApenaS tipoS<br>
     * [inteiro, booleano e char]
     *
     * @param n
     * @throws Exception
     */
    protected void gerarConstante(Node n) throws Exception {
        gerarVariavel(n);
//        getLabel("C", n);
//        if (n.closest("block", "class") != null) {
//            code.registerVar(n.G("_return"), 1, DataFrame.TYPE_VAR);
//        }
    }

    /**
     * Executa a extração fo block principal
     *
     * @throws Exception
     */
    public void main() throws Exception {
        Node main = ast.getMain();
//        main.S("_return", Threads.getId("main"));
        Node stmts = main.find("stmts");
        if (code.OpenBlock("main")) {
            PushLabel("main", "block");
            visit(stmts);

            PushLabel("main-end", "block");
            code.CloseBlock();
        }
    }

    public Code run() throws Exception {
        if (!inicializado) {
            inicializado = true;
            main();
        }
        code.UpdatePositions();

        return code;
    }

    /**
     * ProceSSa If (ElSe'S)*
     *
     * @param n
     * @throws Exception
     */
    protected void IfStmts(Node n) throws Exception {

        String end = gerarLabel();
        if (n.childrens().size() > 1) { //(If)+ (else)?
            n.S("end", end);
        }

        for (Node stmt : n.childrens()) {
            switch (stmt.Class()) {
                case "if.case.stmt":
                    IfCaseStmt(n, stmt);
                    break;
                // Else case
                case "stmts":
                    String next = gerarLabel();
                    n.S("next", next);

                    visit(stmt.S("next", next));
                    // Cria um PushLabel final 
                    PushLabel(n.G("end"), "branch");
//                    PushLabel(end);
                    break;
            }
        }
    }

    /**
     * ProceSSa If, eSSe metodo deve Ser chamado pelo metodo IfStmtS
     *
     * @param n
     * @param _if
     * @throws Exception
     */
    protected void IfCaseStmt(Node n, Node _if) throws Exception {
        Node test = _if.first().first();
        Node stmts = _if.find("stmts");
        String next = gerarLabel();

        n.S("next", next);

//        test.S("false", next);
//        test.S("true", gerarLabel());
        test.S("and", next);
        test.S("next", next);
        test.S("or", gerarLabel());
//        test.S("false", next);

        stmts.S("next", next);
//        System.out.println("Open Branch:" + test.G("id"));
        Branch(test);

//        System.out.println("IF TESTE:" + test);
        if (test.Has("or")) {
            PushLabel(test.G("or"), "branch");
        }

        visit(stmts);

        if (n.Has("end")) {
            _goto(n.G("end"));
        }

        PushLabel(test.G("next"), "branch");
    }

    /**
     * Executa tratamento de um no eSpecifico, Selecionado pelo atributo claSS
     *
     * @param n
     * @throws Exception
     */
    protected void processarNode(Node n) throws Exception {
        switch (n.Class()) {
            case "return":
                genReturn(n);
                break;
            case "ctrl.stmt":
                ctrlStmt(n);
                break;
            case "label.stmt":
                PushLabel(n.getText(), "user");
                break;
        }
    }

    protected void _visitExpr(Node n) throws Exception {
//        System.out.println("VisitExpr:" + n);
        visit(n.childrens().get(0));
        visit(n.childrens().get(1));

        switch (n.G("subclass")) {
            case "arith":// ( + | - | * | / ) integer
                genExpArith(n);
                break;
        }
    }

    protected void Branch(Node n) throws Exception {

        // Se for um test class
//        if (n.eq("class", "test")) {
//            visit(n);
//            Branch(n.last().copy("true,false,and,or,next", n));
//            return;
//        }
        String op = n.getText(), p0, p1;
        boolean and = true;
        ArrayList<Node> x = n.childrens();
//        branckInvert.push(and);
        switch (op) {

            case "&&":
                // Deve inverter operacao
                branckInvert.push(and);
                _and(n);
                Branch(x.get(0));
                Branch(x.get(1));
                n.remove("or");
                branckInvert.pop();

                break;
            case "||":
                and = false;
                // Não deve inverter operacao
                branckInvert.push(and);
                _or(n);

                Branch(x.get(0));

                Branch(x.get(1));

                // Inverte a ultima operacao
                if (branckInvert.size() == 1) {
                    Instruction last = code.Last();
                    last.S("op", Complement(last.G("op"))).S("label", n.G("next"));
                }

                branckInvert.pop();

                break;
            default:
                boolean empty = branckInvert.isEmpty();

                if (empty) {
                    branckInvert.push(and);
                }
                visit(n);
                // Resultado do test class ou acesso a um id ou bool
                if (!n.eq("class", "expr")) {
                    op = "==";
                    p0 = n.G("_return");
                    p1 = "1";
                } else { //(<, <=, >, >=, ==, !=)
                    p0 = visit(x.get(0)).G("_return");
                    p1 = visit(x.get(1)).G("_return");
                }

                Branch(op, p0, p1, n.G("next"));

                if (n.Has("false")) {
                    _goto(n.G("false"));
                }
                if (empty) {
                    branckInvert.pop();
                }
        }

        if (and) {
            n.remove("or");
        }
//        branckInvert.pop();
    }

    protected void Branch(String op, final String p1, final String p2, String label) throws Exception {

//        System.out.println("Add branch:" + branckInvert.peek() + branckInvert);
        Instruction r1 = new Instruction();
        r1.S("format", "branch")
                .S("type", "branch")
                .S("op", (!branckInvert.isEmpty() && branckInvert.peek()) ? Complement(op) : op)
                .S("p1", p1)
                .S("p2", p2)
                .S("p1value", "" + Api.IsValue(p1))
                .S("p2value", "" + Api.IsValue(p2))
                .S("label", label);

        code.Add(r1);
    }

    protected String Complement(String op) {
        return Complements.get(op);
    }

    /**
     * ProceSSa um teSte logico AND
     *
     * @param n
     * @throws Exception
     */
    protected void _and(Node n) throws Exception {
        Node b1 = n.getFilho(0).copy("next,or,and", n);
        Node b2 = n.getFilho(1).copy("next,or,and", n);

        System.out.println("_AND:" + n);
        b1.S("true", n.G("next"));
//        b1.S("false", b.G("false"));

        b2.S("true", n.G("next"));
//        b2.S("false", b.G("false"));
//        b1.S("true", gerarLabel());
//        b1.S("false", b.G("false"));
//
//        b2.S("true", b.G("true"));
//        b2.S("false", b.G("false"));
    }

    /**
     * ProceSSa um teSte logico OR
     *
     * @param n
     * @throws Exception
     */
    protected void _or(Node n) throws Exception {

        Node b1 = n.getFilho(0).copy("next,or,and", n);
        Node b2 = n.getFilho(1).copy("next,or,and", n);

//        System.out.println("Or:" + n + ":" + n.G("or"));
        b1.S("next", n.G("or"));
        b2.S("next", n.G("or"));

//        n.S("true", gerarLabel());
//        b1.S("false", gerarLabel());
        // Invert op
//        b2.S("false", b.G("true"));
//        b2.S("true", b.G("true"));
//        b2.S("false", b.G("false"));
    }

    /**
     * Cria uma linha de Salto para PuShLabel eSpecificado no parametro.
     *
     * @param label
     * @throws Exception
     */
    protected void _goto(String label) throws Exception {
        Instruction r = new Instruction();
        r.S("label", label)
                .S("type", "jump")
                .S("format", "jump")
                .set("locker", getIdMaster());
        code.Add(r);
    }

    /*Inicio das avaliacoes booleanas*/
    /**
     * Gera um linha de inStrução de retorno no block de codigo
     *
     * @param n
     * @throws Exception
     */
    protected void genReturn(Node n) throws Exception {

        Instruction nreg;
        ArrayList<Node> returns = n.find("exprs").childrens();
        for (Node ret : returns) {
//            System.out.println("Return p:" + ret);
            nreg = new Instruction()
                    .S("type", "push_return")
                    .S("format", "push_return")
                    .S("p1value", "" + Api.IsValue(ret.getText()))
                    .S("p1", ret.G("_return"));
            nreg.set("locker", getIdMaster());
            code.Add(nreg);
        }

        code.Add(new Instruction()
                .S("type", "return")
                .S("format", "return")
                .set("p1", returns.size())
                .S("p1value", "true")
                .set("locker", getIdMaster()));

    }
//
//    protected String updateLabel(Node n) {
//        String PushLabel = n.G("_return");
////        if (!PushLabel.substring(0, 1).equals("_R")) {
////        }
////        n.S("_return", PushLabel);
//        return PushLabel;
//    }

    /**
     * Gera todoS oS SaltoS de um loop for
     *
     * @param n
     * @throws Exception
     */
    protected void genFor(Node n) throws Exception {
        setMaster(n);
        String start = gerarLabel(), next = gerarLabel();

        n.S("next", next);
        n.S("start", start);

        Node stmt = n.find("stmts");
        stmt.S("next", start);
        /*Inicializacoes e atribuicoes*/
        visit(n.find("initialization"));
        // Label que marca o inicio do for
        PushLabel(start, "branch");

        Node test = n.find("test").first();
//        if (test.hasChildrens())
//        test.S("true", gerarLabel());
//        test.S("false", n.G("next"));
//        test.S("true", next);
//        test.S("false", n.G("next"));
        test.S("and", next);
        test.S("next", next);
        test.S("or", gerarLabel());

        Branch(test);

        if (test.Has("or")) {
            PushLabel(test.G("or"), "branch");
        }

//        PushLabel(test.G("true"), "branch");
        /*Executa o bloco do for*/
        visit(stmt);
        /*Executa os incrementos*/
        visit(n.find("increment"));
        /*Salta para inicio do laço*/
        _goto(start);
        /*Marca o endereço final para sair do laço*/
        PushLabel(test.G("next"), "branch");

        setNullMaster();
    }

    protected void genExpArith(Node n) throws Exception {
        Node op1 = n.childrens.get(0);
        Node op2 = n.childrens.get(1);

        String _op = n.getText();
//
//        

//        // se os dois valores são
//        if (op1.eq("type", "value") && op2.eq("type", "value")) {
//
//            n.S("_return", resolveExpr(_op, op1.G("value"), op2.G("value")));
//        }
//        if (op1.isNumber("value") && op2.isNumber("value")) {
//
//        } else {
        // Inverte a ordem dos operandos se o segundo for seletor e o primeiro for valor
        if (op1.isNumber("value") && !op2.isNumber("value")) {
            Node aux = op1;
            op1 = op2;
            op2 = aux;
        }

//        System.out.println("GerarArit" + op1 + "-" + op2);
        String _p1 = op1.G("_return");
        String _p2 = op2.G("_return");

        String _return = gerarVariavel("T");

        n.S("_return", _return);

        Instruction instruction = __exp(_op, _p1, _p2, _return);

        if (instruction.eq("type", "copy")) {
            n.S("_p1", instruction.G("p1"));
            n.S("value", instruction.G("p1"));
        } else {

            n.S("_op", _op);
            n.S("_p1", _p1);
            n.S("_p2", _p2);
        }
    }

    /**
     * Para cima verificado <==>
     */
    protected void genArray(Node n) {

        if (n.eq("raiz", "t")) {
            currentMatrixvalues = new ArrayList<String>();
            matrixValues.put(n.G("id"), currentMatrixvalues);
            for (Node c : n.childrens()) {
                genArray(c);
            }
        } else if (n.eq("terminal", "t")) {
            currentMatrixvalues.add(n.getText());
        } else {
            for (Node c : n.childrens()) {
                genArray(c);
            }
        }
    }

    protected ArrayList<String> initValues(Node n) {
        ArrayList<String> values = new ArrayList<>();
        switch (n.G("class")) {
            case "_array":
                values = matrixValues.get(n.G("id"));
                break;
            /*valores e constantes*/
            default:
                String _return = n.G("_return");
                if (_return.matches("^_.*")) {
                    _return = "0";
                }
                values.add(_return);
        }
        return values;
    }

    /**
     * Gera a chamada para o metodo de deSalocacao de memoria
     *
     * @param n
     * @throws Exception
     */
    protected void genDelete(Node n) throws Exception {
        for (Node address : n.childrens()) {
            visit(address);
            __genParam(address.G("_return"));
            __gerarCall(Functions.FREE, "1", getIdMaster());
        }
    }

    /**
     * Gera a Switch caSe
     *
     * @param n
     */
    protected void switchStmt(Node n) throws Exception {
        /*Label que sera consultado pelo break para sair do case*/
        String end = gerarLabel();
        String dst = null, next = null;
        Node test = n.find("test");

        n.S("next", end);

        // Verifica se existe entrada para selecao
        if (test.childrens().size() > 0) {
            test = test.first();
            visit(test);
            dst = test.G("_return");
        }

        ArrayList<Node> cases = n.findAll("def.case", "class", 1);
        int id = 0, count = cases.size() - 1;

        for (Node _case : cases) {
            if (next != null) {
                _case.S("start", next);
            }
            _case.S("_input", dst);
            _case.S("end", end);

            genCase(_case, id == count);
            next = _case.G("next");
            id++;
        }
        /*Marca o endereço final para sair do switch*/
        PushLabel(end, "branch");
    }

    protected Node NodeBoolExpr(String op, Node e1, Node e2) {
        return new Node(op)
                .S("type", "bool")
                .S("class", "expr")
                .S("subclass", "bool")
                .addFilho(e1)
                .addFilho(e2);
    }

    protected ArrayList<Node> ExprsEq(Node n, Node input) throws Exception {
        ArrayList<Node> list = new ArrayList<>();
        switch (n.Class()) {
            case "exprs":
                for (Node e : n.childrens()) {
                    list.add(NodeBoolExpr("==", input, e));
                }
                break;
            default:
                list.add(NodeBoolExpr("==", input, n));
        }
        return list;
    }

    /**
     * Gera oS caSeS de um Switch
     *
     * @param n
     */
    protected void genCase(Node n, boolean last) throws Exception {

        String start = n.Has("start") ? n.G("start") : gerarLabel();
        String next = gerarLabel(), end;

//        System.out.println("GenCase:" + start + ":" + next);
        ArrayList<Node> itens = n.childrens();
        Node stmts = n.find("stmts");
        Node or;

        if (itens.size() >= 2) { // Tem teste
            String input = n.G("_input");
            Node test = n.first();
            ArrayList<Node> exprs;

            if (input != null) {
                exprs = ExprsEq(test, new Node(input).S("_return", input));
            } else {
                exprs = test.childrens();
            }

            /*Seleciona o endereco de saida se é no proximo case ou no fim do switch*/
            if (stmts.last().eq("value", "fallthrough")) {
                end = gerarLabel();
                n.S("next", end);
            } else {
                end = n.G("end");
            }

            // Atualiza o PushLabel de sucesso das exprs
            for (Node expr : exprs) {
                expr.S("true", !last ? next : end);
//                expr.S("false", next);
//                expr.S("false", start);
            }

            Branch(OR(exprs).S("next", next));

            /*Se nenhum dos casos for compativel salta para o proximo*/
//            _goto(!last ? next : end);
            /*Marca o PushLabel inicial do caso*/
            PushLabel(start, "branch");
            /*Visita o bloco do case*/
            visit(stmts);

            if (!last) {
                /*Salta para o proximo case ou para o fim do switchcase*/
                _goto(end);
                /*Label do inicio do proximo case*/
                PushLabel(next, "branch");
            }
        } else {  // Default statiment
            visit(stmts);
        }
    }

    protected Node OR(ArrayList<Node> n) throws Exception {
        switch (n.size()) {
            case 0:
                return null;
            case 1:
                return n.get(0);
            default:
                Node x = n.get(0);
                n.remove(x);
                return NodeBoolExpr("||", x, OR(n)).copy("true,false", x);
        }
    }

    protected int qtdByIndex(Node n) throws Exception {
        Node indexes = n.find("indexes");
        if (indexes == null) {
            return 1;
        } else {
            int sum = 0;
            for (Node index : indexes.childrens()) {
                sum += index.getInt("_return");
            }
            return sum;
        }

    }

    /**
     * Gera inStrução de atribuicao
     *
     * @param n
     * @throws Exception
     */
    protected void genAssign(Node n) throws Exception {
        ArrayList<String> localAttribs = new ArrayList<>(),
                //                copymode = new ArrayList<>(),
                //                operators = new ArrayList<>(),
                returns;
        ArrayList<Boolean> copyaddress = new ArrayList<>();
        String p1, declare = "" + n.getText().equals("dec.var.short");

        Node dsts = n.childrens().get(0);

        if (n.in("value", new String[]{"dec.var.short", "def.assign"})) {

            for (Node attrib : n.childrens().get(1).childrens()) {
                visit(attrib);
//        System.out.println("n:" + n);

//                System.out.println("genAssign:" + attrib.G("class") + ":" + attrib.G("value"));
                if (attrib.eq("class", "call")) {

                    returns = attrib.getList("returns");

                    if (returns != null) {
                        for (String _return : returns) {
                            localAttribs.add(_return);
                            copyaddress.add(attrib.eq("subclass", "address"));
                        }
                    }
                } else if (attrib.eq("class", "index")) {

                    System.out.println("Attr <- :" + attrib);

                } else if (attrib.eq("class", "literal")) {
//                    System.out.println("literal<- :" + attrib);
                    switch (attrib.G("subclass")) {
                        case "array":
                            for (Node element : attrib.find("exprs").childrens()) {
                                visit(element);
                                copyaddress.add(element.eq("subclass", "address"));
                                localAttribs.add(element.G("_return"));
                            }
                            break;
                    }

                } else {
//                    System.out.println("Assign:" + attrib);
                    copyaddress.add(attrib.eq("subclass", "address"));
                    localAttribs.add(attrib.G("_return"));
                }
            }
        }

//        System.out.println("Copy::" + localAttribs);
//        System.out.println("Lista de atribuicoes locais:\n" + dsts + "\n>>\n" + n);
//        System.out.println("Lista de atribuicoes locais:\n" + dsts + "\n>>\n" + localAttribs);
        // Executa a copia dos valores
        for (int dstPosition = 0, i = 0; i < localAttribs.size(); dstPosition++) {

            Node d = dsts.childrens().get(dstPosition);
            d.S("declare", declare);
            visit(d);

//            System.out.println("D:" + d.childrens());
            if (d.eq("class", "unary") && d.eq("dst.pointer", "*")) {

                p1 = localAttribs.get(i);
                code.Add(new Instruction()
                        .S("format", "pointer_assignment")
                        .S("type", "pointer_assignment")
                        .S("dst", d.childrens().get(1).G("_return"))
                        .S("p1", p1)
                        .S("p1value", "" + Api.IsValue(p1))
                        .S("dst.pointer", "*")
                        .set("locker", getIdMaster()));
                i++;
                // Salva o valor em memoria
            } else if (d.eq("array", "true")) {
                for (int j = 0; j < d.getInt("array_size"); j++, i++) {
//                    System.out.println("ARRAY:" + d.G("_return") + "[" + j + "]" + " -- " + localAttribs.get(i));

                    Copy(d.G("_return") + "[" + j + "]", localAttribs.get(i), copyaddress.get(i));
                }
            } else {
                // Copia valor para endereco
//                System.out.println("Attrib:" + d.G("_return") + "<" + localAttribs.get(i));
                Copy(d.G("_return"), localAttribs.get(i), copyaddress.get(i));
                i++;
            }
        }
    }

    protected void __genParam(String param) throws Exception {
        Instruction r = new Instruction();
        r.S("format", "push_param")
                .S("type", "push_param")
                .S("p1", param)
                .S("p1value", "" + Api.IsValue(param))
                .set("locker", getIdMaster());
        code.Add(r);
    }

    protected void genFunction(String id, Node func) throws Exception {
        if (!funcGenMap.containsKey(id)) {
            funcGenMap.put(id, "true");

            Node block = func.find("stmts");

            code.OpenBlock(id);
            /*Cria a PushLabel inicial da funcao*/
            PushLabel(id, "block");

            /*Declara as variaveis de parametro*/
            genPopParams(func);
            /*Declara as variaveis de retorno caso sejam nomeadas*/
//            visit(func.find("def::return"));

//            System.out.println(Variaveis.printScope(func.G("scope")));

            /*Processa o bloco de instrucoes*/
            visit(block);
            /*Cria a PushLabel final da funcao*/
            PushLabel(id + "-end", "block");
            code.CloseBlock();
        }
    }

    protected void genCall(Node n) throws Exception {
        int argsNum = 0;
        // Se for metodo carrega o contexto no primeiro parametro

//        System.out.println("GenCall:" + n);
        if (!n.eq("ctx", "")) {
            Node var = Variaveis.Get(n.G("ctx"));
//            System.out.println("");
//            __genParam("&" + gerarVariavel(var));
//            code.Add(new Instruction()
//                    .S("", PushLabel));
//            gerarVariavel(var)
            __genParam(gerarVariavel(var));
            argsNum++;
        }

        // Gera os argumentos
        Node args = n.find("arguments").find("exprs");
        if (args != null) {
            for (Node param : args.childrens()) {
                // Processa o argumento para obter o _return
                visit(param);
                // Cria a instrução de parametro
                __genParam(param.G("_return"));
            }
            argsNum += args.getChildrenCount();
        }

//        System.out.println("GenCall:" + n.getText());
        Node def = Functions.Get(n.getText());

        genFunction(n.getText(), def);

        // Cria a instrução de chamada
        Instruction r = __gerarCall(n.getText(), "" + argsNum, getIdMaster(), def.find("dec.return"));

//        System.out.println("CALL -> " + r.getList("returns"));
        n.addList("returns", r.getList("returns"));

    }

    protected String UnaryResolve(String op, String value) throws Exception {
        switch (op) {
            case "-":
                return "-" + value;
            case "!":
                return "" + (value.equalsIgnoreCase("false") || value.equals("0") ? "1" : "0");
        }
        throw new Exception(String.format("Invalid [%s]%s ", op, value));
    }
    /*Fim dos laços*/
    /*Inicio das operacoes aritimeticas*/

    /**
     * Unary pode Ser uma expreSSão do tipo <br>
     * (&|*)addreSS - operacao de ponteiroS <br>
     * (-) addreSS|value - negação aritimetica <br>
     * (!) addreSS|value - negação booleana
     *
     * @param n
     * @throws Exception
     */
    protected void unaryStmt(Node n) throws Exception {

        String op = n.childrens().get(0).getText().trim();
        Node value = n.childrens().get(1);
        visit(value);

        String _ret,
                _val = value.G("_return");
        // Se for escrita verifica se é escrita no ponteir ou no lugar referenciado

        if (Api.IsValue(_val)) {
            _ret = UnaryResolve(op, _val);

//        } else {
        } else if (n.eq("access", "write")) {
            _ret = "ERR";
            n.S("dst.pointer", op);
        } else {

//            System.out.println("N:[" + n.G("id") + "]");
            _ret = gerarVariavel("T");
//            System.out.println("Unary:" + n.childrens().G(1));
            String type;
            if (AddressOperator.matcher(op).matches()) {
                type = "pointer_assignment";
            } else {
                type = "unary";
            }

            Instruction r = new Instruction()
                    .S("type", type)
                    .S("format", type)
                    .S("dst", _ret)
                    .S("p1", _val)
                    .S("op", op)
                    .S("copymode", "unary")
                    .S("p1value", "" + Api.IsValue(_val))
                    .set("locker", getIdMaster());
            code.Add(r);
//            System.out.println("N:" + n);
//            throw new Exception("Unary Stmt not defined");
        }

        n.S("_return", _ret);
//        System.out.println("UnaryStmt:[" + _val + ":" + op + "]" + n);
    }

    protected Integer _ExprResolve(String op, String p1, String p2) throws Exception {
        int p1v = Integer.parseInt(p1),
                p2v = Integer.parseInt(p2);
        switch (op) {
            case "+":
                return p1v + p2v;
            case "-":
                return p1v - p2v;
            case "*":
                return p1v * p2v;
            case "/":
                return p1v / p2v;
            case "%":
                return p1v % p2v;
        }
        return null;
    }

    protected Instruction __exp(String op, String p1, String p2, String ret) throws Exception {
        boolean p1value = Api.IsValue(p1),
                p2value = Api.IsValue(p2);

        Instruction r = new Instruction();
        if (p1value && p2value) {
            r.S("type", "copy")
                    .S("format", "copy")
                    .S("p1value", "true")
                    .S("p1", "" + _ExprResolve(op, p1, p2));

        } else {
            r.S("type", "assign")
                    .S("format", "assign")
                    .S("cat", "exp")
                    .S("p1", p1)
                    .S("p2", p2)
                    .S("copymode", "assign")
                    .S("p1value", "" + p1value)
                    .S("p2value", "" + p2value);
//            System.out.println("__EXP:"+p1value+":"+p2value);
        }
        r.S("dst", ret)
                .S("op", op)
                .set("locker", getIdMaster());
//        System.out.println("EXP:"+r);

        return code.Add(r);
    }

    /**
     * Gera Salto para inStruçõeS de continue e break;
     *
     * @param n
     * @throws Exception
     */
    protected void ctrlStmt(Node n) throws Exception {
        Node container = n.closest("for.stmt,switch.stmt", "class");

        switch (n.getText()) {
            case "continue":
                _goto(container.G("start"));
                break;
            case "break":
                _goto(container.G("next"));
                break;
            case "goto":
                _goto(n.G("label"));
                break;
        }

    }

    /**
     * Executa a extração do codigo baSeado na aSt, e retorna um objeto
     * CodigoTreSEnderecoS contendo oS blockS em Formato de treS endereçoS.
     *
     * @return
     * @throws Exception
     */
    public Code getCodigoDeTresEnderecos() throws Exception {
        return run();
    }

    /**
     * Ativa ou deSativa a otimizacao
     *
     * @param lc
     */
    public void setOtimizacao(boolean lc) {
        code.setOtimizacao(lc);
    }

    protected void setMaster(Node n) {
        master.push(n);
    }

    protected Node getMaster() {
        return master.peek();
    }

    protected void setNullMaster() {
        master.pop();
    }

    protected int getIdMaster() throws Exception {
        if (master.empty()) {
            return 0;
        }
        return master.peek().getInt("id");
    }

    /**
     * Adiciona aS declaraçõeS bem como carregaoS parametroS e referenciaS
     *
     * @param n
     * @throws Exception
     */
    protected void genPopParams(Node func) throws Exception {
        int reg = 0;
        String destino, atribuido;
        /**
         * Se for method declara e atribui o endereco a variavel
         */
        Node receive = func.find("receive.type");
        if (receive != null) {
//            atribuido = "_A" + (reg++);
//            destino = clearPointer(atribuido, gerarVariavel(receive.childrens().G(0)));
            destino = gerarVariavel(receive.childrens().get(0));
            // copy_pointer?
            Copy(Api.clearID(destino), __popParam().G("_return"), false);
        }

        for (Node argument : func.find("arguments").childrens()) {
            for (Node arg : argument.find("ids").childrens()) {
                /**
                 * Gera uma instrução de copia do registrador a{i},i = [0-4],
                 * para o endereço de memoria dos parametros
                 */
                //                atribuido = "_Ra" + (reg++);
                //                atribuido = "_A" + (reg++);
                //                System.out.println("PopArg:" + arg);
                //                destino = clearPointer(atribuido, gerarVariavel(arg));
                // copy_pointer?
                Copy(Api.clearID(gerarVariavel(arg)), __popParam().G("_return"), false);
            }
        }
    }

    protected Instruction __popParam() throws Exception {
        String varname = gerarVariavel("T");
        Instruction r = new Instruction()
                .S("type", "pop_param")
                .S("format", "pop_param")
                .S("p1", varname)
                .S("p1value", "false")
                .S("_return", varname);
        r.set("locker", getIdMaster());
        code.Add(r);
        return r;
    }

//    protected void inicializarVariaveis() throws Exception {
////        System.out.println("INICIALIZAR VARIAVEIS:");
//
//        for (Node container : ast.getRoot().childrens()) {
//            for (Node constant : container.findAll("dec::const,dec::var", "class", 1)) {
//                visit(constant);
//            }
//        }
//    }
    protected void Copy(String dst, String src, boolean address) throws Exception {

//        System.out.println("TAC:Copy:" + dst + "=" + src);
        String type, format, arrayPattern = ".*\\[.*\\]";

        Instruction r = new Instruction();

        if (dst.matches(arrayPattern) || src.matches(arrayPattern)) {
            type = "indexed_assign";
            format = "copy";
            if (Variaveis.isArray(src)) {
                r.S("src_indexed", "true");
                r.S("p1.indice", Api.getIndice(src));
            }

            if (Variaveis.isArray(dst)) {
                r.S("dst_indexed", "true");
                r.S("dst.indice", Api.getIndice(dst));
            } else {
                r.S("dst_indexed", "false");
            }

            if (Variaveis.isArray(src)) {
                r.S("indice", Api.getIndice(src));
            }

        } else {
            type = format = "copy";
        }
        // Identifica que o valor copiado é um endereco ou valor
        if (address) {
            r.S("src.address", "true");
        }

        r.S("format", format)
                .S("type", type)
                .S("dst", dst)
                .S("p1", src)
                .S("p1value", Api.IsValue(src) + "")
                .set("locker", getIdMaster());
        code.Add(r);
    }

    /**
     * Gera um PuShLabel unico
     *
     * @param n
     * @return
     */
    protected String gerarLabel(Node n) {
//        return code.genLabel();
        return code.getCurrentBlockName() + "+_i" + (incLabel++);
    }

    protected String gerarLabel() {
//        return code.genLabel();
        return code.getCurrentBlockName() + "+_i" + (incLabel++);
    }

    /**
     * Cria uma linha de PuShLabel no block de codigo corrente
     *
     * @param label String
     * @throws Exception
     */
    protected Instruction PushLabel(String label, String type) throws Exception {
        Instruction r = new Instruction()
                .S("label", label)
                .S("type", "label")
                .S("label_type", type)
                .S("format", "label")
                .set("locker", getIdMaster());
        return code.Add(r);
    }

    /**
     * Gera um nome de variavel unico,
     *
     * @param pre prefixo da variavel
     * @param n
     * @return
     */
    protected String gerarVariavel(String pre) {
        return "_" + pre + (tmpVarCount++);
    }

    protected String gerarVariavel(Node n) throws Exception {
        String id = n.getText();
        if (!varGenMap.containsKey(id)) {
            varGenMap.put(id, gerarVariavelSet(id, n));
        }
        return varGenMap.get(id);
    }

    protected String gerarVariavelSet(String id, Node n) throws Exception {
        Node var = Variaveis.Get(id);
        String varname, alias, vn;

        if (var.eq("constant", "true")) {
            varname = id;
        } else {
            varname = var.getText();
        }
//        System.out.printf("GerarVariavelSet{%s\n,%s\n,%s\n}\n", id, varname, var);
//        if (var.eq("constant", "true")) {
//            System.out.printf("GerarVariavelSet{%s\n,%s\n,%s\n}\n", id, varname, var);
//        }
        if (varGenMap.containsKey(varname)) {
            alias = varGenMap.get(varname);
//            System.out.printf("GerarVariavelSet[MAPPED]{%s,%s}\n", varname, alias);
        } else {
            String prefix;
//            System.out.println("GerarVariavelSet\n" + varname + "\n" + id + "\n" + var);
            if (var.eq("class", "dec.const")) {
                prefix = "C";
            } else if (var.eq("global", "true")) {
                prefix = "G";
            } else {
                prefix = "V";
            }

//            System.out.println("DecVar:" + var);
//            alias = var.G("pointer") + gerarVariavel(prefix);
            alias = gerarVariavel(prefix);
            n.S("alias", alias);
            n.copy("pointer", var);
            
            varGenMap.put(varname, alias);
//            vn = var.G("scope");
            

            int size = Tipos.Size(var.G("type")), b = 1;

//            System.out.println("gerarVariave[" + prefix + "|" + id + "]:" + var);
            // Se for um array considera os filhos como index
            if (!var.eq("constant", "true") && var.childrens().size() > 0) {
                for (Node node : var.childrens()) {
                    b *= node.getInt("value");
                }
            }
            size *= b;

            var.set("size", size);

            DataFrame data;
            if (prefix.equals("G")) {
                data = code.GData();
            } else {
                data = code.Block().Data();
            }
            data.Add(alias, var);
        }
        return alias;
    }

    /**
     * Imprime o codigo de treS endereçoS
     *
     * @return
     */
    @Override
    public String toString() {
        return printVarMap() + code.toString();
    }

    public String printVarMap() {
        StringBuilder s = new StringBuilder();

        for (Map.Entry<String, String> e : varGenMap.entrySet()) {
            s.append(e.getKey() + " : " + e.getValue() + "\n");
        }

        System.out.println("Var Map:\n" + s.toString());
        return varGenMap.toString();
    }

    /**
     * Executa a extração da aSt para codigo de treS endereçoS
     *
     * @throws Exception
     */
    public void extrair() throws Exception {
        if (!inicializado) {
            inicializado = true;
        }
//        inicializarVariaveis();
//        extrairTraps();
//        extrairThreads();
//        extractFunctions();
//        extracMethods();
        main();
    }

    protected Instruction __gerarCall(String funcname, String numparam, int idMaster, Node returns) throws Exception {
        Instruction call = __gerarCall(funcname, numparam, idMaster);

        // Atribuição dos enderecos de retorn
        if (returns != null) {
            ArrayList<String> rts = new ArrayList<>();
            String t;
            for (Node r : returns.childrens()) {
                t = gerarVariavel("T");
                rts.add(t);
                code.Add(new Instruction()
                        .S("type", "pop_return")
                        .S("format", "pop_return")
                        .S("p1value", "false")
                        .S("p1", t));
            }

            call.addList("returns", rts);
        }

        return call;
    }

    /**
     * Gera inStrução de call
     *
     * @param funcname
     * @param numparam
     * @param idMaster
     * @param variavelRetorno
     * @throws Exception
     */
    protected Instruction __gerarCall(String funcname, String numparam, int idMaster) throws Exception {
        Instruction r = new Instruction();
        r.S("format", "call")
                .S("type", "call")
                .S("funcname", funcname)
                .S("nump", numparam).set("locker", idMaster);
//        if (!variavelRetorno.equals("")) {
//            r.S("dst", variavelRetorno);
//        }
        code.Add(r);
        return r;
    }

}
