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

import ast.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 *
 * @author Eugenio
 */
public class Variaveis {

    public static Integer indexCount = 0;
    public static HashMap<String, Node> vars;
    public static HashMap<String, Instruction> scopes;
    public static String UNDEFINED = "undefined";
    public static String currentKey;
    public static Pattern BASEVEXPR = Pattern.compile("(\\w+(\\.@\\w+)*\\.\\w+)");
    public static String CLEARBASEVAR = "(\\w+(\\.@\\w+)*\\.)";

    public static void _init() {
        vars = new HashMap<>();
        scopes = new HashMap<>();
    }

    public static void Add(String id, Node var) throws Exception {
        if (vars.containsKey(id)) {
            String[] parts = id.split("\\.");
            throw new Exception("'" + parts[parts.length - 1] + "' previamente declarado em na linha " + vars.get(id).get("line", ""));
//            throw new Exception("'" + id + "' previamente declarado em na linha " + vars.get(id).get("line", ""));
        }
        var.S("public", Base.IsPublic(var.G("name")) ? "true" : "false");
        vars.put(id, var);
    }

    public static boolean Defined(String id) {
        return Defined(id, true);
    }

    public static boolean Defined(String id, boolean clear) {
        if (clear) {
            id = BaseVar(id);
        }
//        System.out.println("Defined:"+id+":"+vars.containsKey(id));
        return vars.containsKey(id);
    }

    public static String BaseVar(String id) {
        Matcher match = BASEVEXPR.matcher(id);
        while (match.find()) {
            return match.group(1);
        }
        return id;
    }

    public static String TypeOf(String varname) throws Exception {
        Node var = Get(varname);
        Node tmp, t;
        String type = "undefined";
        if (var != null) {
            String path = varname.replace(var.G("name"), "");

            // Sem path
            if (path.length() > 0) {
                ArrayList<String> parts = new ArrayList<>(Arrays.asList(path.split("\\.")));
                parts.remove(0); // Remove a primaira parte que é uma string vazia
                String typed = var.G("type"), attr;
                t = Tipos.Get(typed);

                while (parts.size() > 0) {
                    if (Tipos.Primitive(typed)) {
                        return typed;
                    }

                    if (t == null) {
                        throw new Exception(String.format("Tipo '%s' não definido", typed));
                    }
                    attr = parts.remove(0);
                    tmp = t.find(attr);

                    if (tmp == null) {
                        throw new Exception(String.format("Tipo '%s' não possui o atributo '%s'", typed, attr));
                    }

                    typed = tmp.find("type").G("type");

                    t = Tipos.Get(typed);
                }
//                System.out.println("Type:" + t + type);

                type = t.G("type");
            } else {
                type = var.G("type");
            }
        }
//        System.out.println("Type of:" + varname + ":" + type);
        return type;
    }

    public static Node Get(String id) {

        String x = id;
        id = BaseVar(id);

//        System.out.println("Get:" + id + "=" + x);
        if (Defined(id, false)) {
//            System.out.println("Var defined:" + id);
            return vars.get(id);
        }
        List<String> items = Arrays.asList(id.split("\\."));
        // Minima representacao de uma variavel sendo o pacote e o id
        if (items.size() <= 2) {
            return null;
        }
        List<String> parts = items.subList(0, items.size() - 2);
        parts = new ArrayList<>(parts);
        parts.add(items.get(items.size() - 1));
//        System.out.println("Get ID:" + Utils.Join(parts, "."));
        return Get(Utils.Join(parts, "."));
    }

    /**
     * Metodos antigos
     */
    /**
     * Adiciona uma variavel em um eScopo
     *
     * @param name
     * @param type
     * @param scope
     * @param array
     */
    public static void add(String name, String type, String scope, Node n) {
        String key = name + ":" + scope;

        vars.put(key, n);

        if (!scopes.containsKey(scope)) {
            scopes.put(scope, new Instruction());
        }

        scopes.get(scope).S(name, type);
    }

    public static String type(String id, String scope) {
        if (exist(id, scope)) {
            return vars.get(currentKey).G("type");
        }
        return UNDEFINED;
    }

    /**
     * Retorna true Se uma variavel é aceSSivel de um determinado eScopo.
     *
     * @param name
     * @param scope
     * @return
     */
    public static boolean accessible(String name, String scope) {
        return type(name, scope) != UNDEFINED;
    }

    /**
     * Verifica Se uma variavel foi declarada em um determinado eScopo
     *
     * @param name
     * @param scope
     * @return
     */
    public static boolean exist(String name, String scope) {
        String key = name + ":" + scope;

        if (vars.containsKey(key)) {
            currentKey = key;
            return true;
        }

        String[] path = scope.split(",", 2);

        if (path.length == 2) {
            return exist(name, path[1]);
        }

        key = name + ":" + path[0];

        if (vars.containsKey(key)) {
            currentKey = key;
            return true;
        }
        return false;
    }

    /**
     * Verifica Se uma variavel foi declarada por meio do no da AST
     *
     * @param nome
     * @param escopo
     * @return
     */
    public static boolean exist(Node var) {
        return exist(var.getText(), var.G("scope"));
    }

    public static Node get(Node n) {
        return exist(n) ? vars.get(currentKey) : null;
    }

    public static Node get(String name, String scope) {
        return exist(name, scope) ? vars.get(currentKey) : null;
    }

    public static String printScope(String scope) {

        if (!scopes.containsKey(scope)) {
            return "Scope " + scope + " não definido!";
        }

        return "Scope{" + scope + "}\n" + scopes.get(scope);

    }

    /**
     *
     * DAQUI PRA CIMA O RESTO APAGA
     *
     *
     */
    /**
     * Verifica Se uma dada variavel é um array
     *
     * @param varname
     * @return
     */
    public static boolean isArray(String varname) {
        return (varname.lastIndexOf("[") >= 0);
    }

    public static String clearName(String nome) {
        return nome.split("\\[")[0].replace("&", "");
    }
//    /**
//     * Retorna uma variavel em um dado escopo do programa
//     *
//     * @param nome nome da variavel
//     * @param escopo escopo da variavel (onde foi declarada)
//     * @return Node | null
//     * @throws Exception
//     */
//    public static Node var(String nome, String escopo) {
//        existe(nome, escopo);
//        return varsNodes.G(index.G(baseName(nome) + ":" + escopo));
//
//    }
//
//    /**
//     * Adiciona uma variavel em um dado escopo do programa
//     *
//     * @param nome nome da variavel
//     * @param escopo escopo da variavel (onde foi declarada)
//     * @param var no com todos os dados da variavel
//     * @throws Exception
//     */
//    public static void var(String nome, String escopo, Node var) {
//        existe(nome, escopo);
//        varsNodes.put(index.G(baseName(nome) + ":" + escopo), var);
//    }
//
//    public static String tipo(Node variavel) {
//        return tipo(variavel.G("value"), variavel.G("escopo"));
//    }
//    public static String tipo(String nome, String escopo) {
//        nome = baseName(nome);
//        try {
//            existe(nome, escopo);
//            return types.G(index.G(nome + ":" + escopo));
//        } catch (Exception e) {
//            return _tipo(nome, escopo);
//        }
//    }
//    protected static String _tipo(String nome, String escopo) {
//        nome = baseName(nome);
//        try {
//            String[] escopos = escopo.split(",", 2);
//            if (escopos.length == 2) {
//                return tipo(nome, escopos[1]);
//            }
//            existe(nome, escopos[0]);
//            return types.G(index.G(nome + ":" + escopos[0]));
//        } catch (Exception e) {
//            return null;
//        }
//    }
//    /**
//     * Retorna uma variavel do escopo, caso não a encontre vai buscar no escopo
//     * pai. Se chegar ao no root e não encontrar a variavel, será retornado o
//     * valor null.
//     *
//     * @param nome
//     * @param escopo
//     * @return
//     */
//    public static Node find(Node variavel) {
//        if (variavel == null) {
//            return null;
//        }
//        return find(variavel.getText(), variavel.G("escopo"));
//    }
//
//    /**
//     * Retorna uma variavel do escopo, caso não a encontre vai buscar no escopo
//     * pai. Se chegar ao no root e não encontrar a variavel, será retornado o
//     * valor null.
//     *
//     * @param nome
//     * @param escopo
//     * @return
//     */
//    public static Node find(String nome, String escopo) {
//        nome = baseName(nome);
//        return existe(nome, escopo)
//                ? varsNodes.G(index.G(nome + ":" + escopo))
//                : _find(nome, escopo);
//    }
//    /**
//     * Auxiliar do metodo find
//     *
//     * @param nome
//     * @param escopo
//     * @return
//     */
//    protected static Node _find(String nome, String escopo) {
//        nome = baseName(nome);
//        String[] escopos = escopo.split(",", 2);
//        if (escopos.length == 2) {
//            return find(nome, escopos[1]);
//        }
//        return existe(nome, escopos[0])
//                ? varsNodes.G(index.G(nome + ":" + escopos[0]))
//                : null;
//    }
//    /**
//     * Retorna o escopo onde a variavel foi declarada.
//     *
//     * @param nome
//     * @param escopo
//     * @return
//     */
//    public static String declaracao(String nome, String escopo) {
//        nome = baseName(nome);
//        try {
//            existe(nome, escopo);
//            return escopo;
//        } catch (Exception e) {
//            return _declaracao(nome, escopo);
//        }
//    }
//
//    /**
//     * Auxiliar do metodo declaracao.
//     *
//     * @param nome
//     * @param escopo
//     * @return
//     */
//    protected static String _declaracao(String nome, String escopo) {
//        nome = baseName(nome);
//        try {
//            String[] escopos = escopo.split(",", 2);
//            if (escopos.length == 2) {
//                return declaracao(nome, escopos[1]);
//            }
//            existe(nome, escopos[0]);
//            return escopos[0];
//        } catch (Exception e) {
//            return null;
//        }
//    }
//    public static void var(int cod, Node var) {
//        varsNodes.put(cod, var);
//    }

    public static String baseName(String text) {
        return clearName(text).split("\\.")[0];
    }

    public static void print() {
        String str = "Variaveis\n";
        for (Map.Entry<String, Node> entry : vars.entrySet()) {
            String varKey = entry.getKey();
            Node value = entry.getValue();

            str += "\n" + value;
        }
        System.out.println(str);
    }

//    /**
//     * Testa ocorrencia de uma variavel, verifica se a mesma existe e se o tipo
//     * corresponde com o esperado.
//     *
//     * @param current
//     * @param tipoEsperado
//     */
//    public static boolean teste(Node current, String tipoEsperado) {
//        /*Verifica se existe declaracao da variavel*/
//        String varpath = current.getText();
//        existe(varpath, current.G("escopo"));
//
////        if (Tipos.structAcesso(varpath)) {
////            Tipos.validarAtribuicao(current);
////        }
//        Node var = find(varpath, current.G("escopo"));
//        if (!var.G("tipo").equals(tipoEsperado)) {
//            return false;
//        }
//        return true;
//
//    }
//    static String getFullName(Node param) {
//        System.out.println("\n\nFULL:" + param);
//        String indices = "";
//        Node indicesNodes = param.encontre("indices");
//        if (null != indicesNodes) {
//            for (Node ind : indicesNodes.filhos()) {
//                indices += "[" + ind.getText() + "]";
//            }
//        }
//        return param.getText() + indices;
//    }
//    protected static void verificarIndices(ArrayList<Node> acessoIndices) {
//        for (Node indice : acessoIndices) {
//            if (!indice.isNumber("value")) {
//                AnalizadorSemantico.testExist(indice);
//            }
//        }
//    }
//    public static void acessoMatrixIsValid(Node var) throws Exception {
//        String tipoParent = var.parent.G("tipo");
//        String atributo = ivannosysUtils.clearName(var.getText());
//        int limit, acesso, i = 0;
//
//        Node indices = var.encontre("indices");
//
//        /*Caso não seja acessado nenhum indice da matrix return*/
//        if (null == indices) {
//            return;
//        }
//
//        ArrayList<Node> acessoIndices = indices.filhos();
//
//        verificarIndices(acessoIndices);
//
//        /*Se for declaração apenas verifica se indices são corretos*/
//        if (var.igual("class", "dec::var")) {
//            return;
//        }
//        /*Se for acesso de uma variavel e possuir indices verificar se os mesmos estao dentro dos limites*/
//        ArrayList<String> decIndices;
//        boolean isAtributo = var.igual("class", "atributo");
//        decIndices = isAtributo
//                ? Tipos.getIndices(var) : Variaveis.getIndices(var);
//        limit = decIndices.size();
//        acesso = acessoIndices.size();
//
////        if (acesso < limit) {
////            throw new Exception(Api.getFormatedPosition(var) + " Matriz acesso invalido. "
////                    + "Quantidade de indices inferior ao da matriz");
////        } else 
////        System.out.println("VAR:" + var.getText() + ",limit:" + limit + ", acesso:" + acesso);
//        if (acesso > limit) {
//            throw new Exception(Api.getFormatedPosition(var) + " Matriz acesso invalido. "
//                    + "Quantidade {" + acesso + "} de indices superior ao da matriz {" + limit + "} ");
//        }
//        if (decIndices.size() != 0) {
//            return;
//        }
//
//        for (Node indice : acessoIndices) {
//            if (indice.isNumber("value")) {
//                limit = Integer.parseInt(decIndices.G(i));
//                acesso = indice.getInt("value");
//                if (acesso >= limit || acesso < 0) {
//                    throw new Exception("Acesso indevido a um array, limite {"
//                            + (limit - 1) + "} enquanto a tentativa foi {"
//                            + acesso + "} na posição {" + i + "}");
//
//                }
//            }
//            i++;
//        }
//
//        Node attr = var.encontreByClass("atributo");
//        if (null == attr) {
//            return;
//        }
//        if (!temAtributo(tipoParent, atributo)) {
//            throw new Exception(" Acessando atributo [" + atributo + "] não definido no tipo[" + tipoParent + "]");
//        }
//
//        acessoMatrixIsValid(attr);
//
//    }
//
//    public static ArrayList<String> getIndices(Node var) {
//        Node declaracao = find(var);
//        ArrayList<String> indices = new ArrayList<>();
//        for (Node n : declaracao.encontre("indices").filhos()) {
//            indices.add(n.getText());
//        }
//        return indices;
//    }
//
//    public static boolean isMagica(String nome) {
//        return nome.matches("__.*__(\\[.*\\])*");
//    }
//    /**
//     * Verfica se todos os indices do acesso são numericos
//     *
//     * @param var
//     * @return
//     */
//    public static boolean todosIndicesNumericos(Node var) {
//        Node indices = var.encontre("indices");
//        if (null != indices) {
//            for (Node n : indices.filhos()) {
//                if (!n.isNumber("value")) {
//                    return false;
//                }
//            }
//        }
//        Node atributo = var.encontreByClass("atributo");
//        return (null != atributo) ? todosIndicesNumericos(atributo) : true;
//    }
//
//    public static int tamanhoVetor(Node n) throws Exception {
//        Node indices = n.encontre("indices");
//        int size = 1;
//        if (null != indices) {
//            for (Node indice : indices.filhos()) {
//                size *= indice.getInt("value");
//            }
//        }
//        return size;
//    }
    public static void List() {
        StringBuilder s = new StringBuilder();
        for (String var : vars.keySet()) {
            s.append(var + ": " + vars.get(var).G("type") + "\n");
        }
        System.out.println("Vars:\n" + s.toString());
    }

    public static String Type(String id) {
        Node var = Get(id);
        if (var == null) {
            return "undefined";
        }
        return var.G("type");
    }

    public static int Shift(String id) {
        Node var = Get(id);
        if (var == null) {
            return 0;
        }
//        System.out.println("Var Shift:" + id);
        String[] varparts = var.getText().split("\\.");
        String varname = varparts[varparts.length - 1];
        String[] idparts = id.split(varname + "\\.?");
        if (idparts.length == 2) {
            ArrayList<String> path = new ArrayList<>(Arrays.asList(idparts[1].split("\\.")));

//            System.out.println("shift:" + var.G("type") + "-" + path);
            return Tipos.Shift(var.G("type"), path);
        }
        // Não é acesso a um atributo
        return 0;
    }
}

//            Node attrib = getAtributo(tipoParent, atributo);
//            if (attrib.igual("matrix", "false")) {
//                throw new Exception("Atributo [" + atributo + "] não é matrix");
//                return;
//            }
//
//        int i = 0, limit, limitCall, acesso;
//        ArrayList<Node> tipoIndices = new ArrayList<>();
//       
//        String atributo = ivannosysUtils.clearName(var.getText());
//        boolean eDeclaracao = var.igual("class", "dec::var");
//        if (!eDeclaracao) {
//
//            if (!temAtributo(tipoParent, atributo)) {
//                throw new Exception(" Acessando atributo [" + atributo + "] não definido no tipo[" + tipoParent + "]");
//            }
//
//            Node attrib = getAtributo(tipoParent, atributo);
//
//            if (attrib.igual("matrix", "false")) {
//                if (!acessoIndices.isEmpty()) {
//                    throw new Exception("Atributo [" + atributo + "] não é matrix");
//                }
//                return;
//            }
//            tipoIndices = attrib.encontre("indices").getChildrens();
//        }
//
//        for (Node indice : acessoIndices) {
//            if (!indice.eNumerico("value")) {
//                AnalizadorSemantico.testExist(indice);
//                continue;
//            } else if (!eDeclaracao) {
//                limit = tipoIndices.G(i).getInt("value");
//                acesso = indice.getInt("value");
//                if (acesso >= limit || acesso < 0) {
//                    throw new Exception("Acesso indevido a um array, limite {"
//                            + (limit - 1) + "} enquanto a tentativa foi {"
//                            + acesso + "} na posição {" + i + "}");
//
//                }
//            }
//            i++;
        //        }
