/*
 * 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 ast;

import API.Utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;

/**
 * @author Eugenio
 */
public final class AbstractSyntaxTree {

    protected int count;
    protected Node current;
    protected Node root;
    protected Node compileParams;
    protected HashMap<String, ArrayList<Node>> ganchos;
    protected Stack<Node> ctxs = new Stack<>();
    protected static AbstractSyntaxTree instance;

    public AbstractSyntaxTree() {
        this.root = new Node("init");
        Current(root);
        compileParams = new Node("");

//        this.current = this.root;
//        ganchos = new HashMap<>();
//        ganchos.put("constantes", new ArrayList<>());
//        ganchos.put("variaveis", new ArrayList<>());
//        ganchos.put("funcoes", new ArrayList<>());
//        ganchos.put("threads", new ArrayList<>());
//        ganchos.put("traps", new ArrayList<>());
    }

    public Node getCompileParams() {
        return compileParams;
    }

    public void setCompileParams(Node compileParams) {
        this.compileParams = compileParams;
    }
    

    public static AbstractSyntaxTree getAbstractSyntaxTree() {
        if (instance == null) {
            instance = new AbstractSyntaxTree();
        }
        return instance;
    }

    public Node setNode(Node n) {
        addChildren(n);
        this.current = n;
        return this.current;
    }

    public Node Current(Node n) {
        ctxs.push(this.current);
        this.current = n;
        return this.current;
    }

    public Node Back() {
        this.current = ctxs.pop();
        return this.current;
    }

    public Node getTipoDefinicao(String tipo) {
        Node types = this.root.find("program").find("types");
        Node t = null;
        if (types != null) {
            t = types.find(tipo);
            if (t != null) {
                return t;
            }
            ArrayList<Node> librarys = this.root.encontrePrimeiroGrau("library", "class");
            for (Node library : librarys) {
                types = library.find("types");
                if (types == null) {
                    continue;
                }
                t = types.find(tipo);
                if (t != null) {
                    return t;
                }
            }
        }
        return t;
    }

    public Node addChildren(Node n) {
        return this.current.addFilho(n);
    }

    public Node setParentToCurrent() {
        if (this.current.parent != null) {
            this.current = this.current.parent;
        }
        return this.current;
    }

    public String visit() {
        return root.visit();
    }

    public String stringfy() {
        return root.stringfy();
    }

    public void setAtributo(String atrib, String valor) {
        this.current.S(atrib, valor);
    }

    public String getAtributo(String atrib) {
        return this.current.G(atrib);
    }

    public Node getCurrent() {
        return this.current;
    }

    protected Node encontreFuncao(String nome) {
//        System.out.println("this.root" + this.root);
        Node ret = null;
        for (Node node : this.root.childrens()) {
            Node funcoes = node.find("funcoes");
            if (funcoes == null) {
                continue;
            }
            for (Node func : funcoes.childrens()) {
                if (!func.getText().equals(nome)) {
                    continue;
                }
                ret = func;
            }
        }
        return ret;
    }

    public Node getMain() {
        return this.root.find("main").find("main");
    }

    protected Node encontrarDeclaracaoNaFuncao(Node var, Node parent) {
        Node funcao = parent.closest("dec::function", "class");
        Node parametros = funcao.find("parametros");
        String nome = Utils.clearName(var.getText());
        if (parametros != null) {
            for (Node p : parametros.childrens()) {
                if (p.getText().equals(nome)) {
                    return p;
                }
            }
        }
        return null;
//        return encontreDeclaracao(var, parent.parent);
    }

    protected Node encontrarDeclaracaoNoFor(Node var, Node parent) {
        Node _for = parent.closest("for", "class");
        String nome = Utils.clearName(var.getText());
        if (_for != null) {
            for (Node ini : _for.find("inicializacoes").childrens()) {
//                System.out.println("CLASS:" + ini.Class());
                if (ini.getText().equals(nome) && ini.eq("class", "dec::var")) {
                    return ini;
                }
            }
        }
//        System.out.println("Procurando no for e nao encontrei agora vou buscar[" + var.getText() + "] em " + parent.parent);
        Node val = encontreDeclaracao(var, parent.parent);
//        System.out.println("Retorno da busca no parent:" + val);
        return val;
    }

    public Node encontreDeclaracao(Node n, Node parent) {
//        System.out.println("Node:" + n + "\n|parent:" + parent);
        String nodeClass = n.Class();
        /*Se for a declaracao da variavel*/
        if (nodeClass.equals("dec::var")) {
            return n;
        }
        /*Se nao for uma variavel, atribuicao ou operacao unaria return null*/
        if (!n.in("class", new String[]{"var", "assign::op", "op::unaria", "atributo"})) {
            return null;
        }
        String nome = Utils.clearName(n.getText());

//        System.out.println("Nome1:" + nome);
        if (nome.contains(".")) {
            String[] partes = nome.split("\\.");
            nome = partes[0];
        }
//        System.out.println("Nome2:" + nome);
        String local = "declaracao";
        if (n.eq("declaracao", "library")) {
            local = "escopo";
        }
        String[] escopo = n.G(local).split("\\.");
        Node decNode = null;

        if (parent.eq("class", "dec::function") && escopo[0].equals("function")) {
            decNode = encontrarDeclaracaoNaFuncao(n, parent);
        } else if (parent.eq("class", "for")) {
            decNode = encontrarDeclaracaoNoFor(n, parent);
        } else if (parent.eq("class", "bloco")) {
            /*procurar no bloco*/
            ArrayList<Node> declaracoes = parent.filtrarFilhosPorClass("dec::var");
            for (Node node : declaracoes) {
                if (Utils.clearName(node.getText()).equals(nome)) {
                    decNode = node;
                    break;
                }
            }
        }
        if (decNode != null) {
            return decNode;
        } else if (parent.parent != null
                && !parent.parent.eq("value", "init")) {
//            System.out.println("Buscando no parent:" + n.getText() + ":" + parent.parent.G("id"));
            return encontreDeclaracao(n, parent.parent);
        } else {
//            System.out.println("parent.parent:" + parent.parent);
            Node programa = parent.parent.find("program");
            Node var = _buscar(programa, nome, "variaveis");

            if (var == null) {
                ArrayList<Node> librarys = parent.parent.filtrarFilhosPorClass("library");
//                System.out.println("librarys:" + librarys);
                for (Node library : librarys) {
                    var = _buscar(library, nome, "variaveis");
                    if (var != null) {
//                        System.out.println("Encontrei mat");
                        break;
                    }
                }
            }
            return var;
        }

    }

    protected Node _buscar(Node root, String nome, String quem) {
        Node n = root.find(quem);
//        System.out.println("Buscando [" + nome + "]" + n);
        if (n != null) {
            ArrayList<Node> variaveis = n.childrens();
            for (Node node : variaveis) {
                if (Utils.clearName(node.getText()).equals(nome)) {
                    return node;
                }
            }
        }
        return null;
    }

    public Node getVariaveis() {
        return root.find("program").find("variaveis");
    }

    public Node getRoot() {
        return root;
    }

    public ArrayList<Node> getVariaveisGlobais() {
        return this.getRoot().findAll("dec::globais", "class");
    }

    public Node getPackage(String packageName) {
//        System.out.println("Getpackage:" + packageName + getRoot());
        return getRoot().find(packageName);
    }

    public Node BuildParams() {
        return this.compileParams;
    }

}
