/*?
 * 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.AbstractSyntaxTree;
import ast.Node;
import java.util.ArrayList;
import java.util.HashMap;

/**
 *
 * @author Eugenio
 */
public class Tipos {
    
    public static AbstractSyntaxTree ast;
    public static HashMap<String, Node> _types = new HashMap<>();
    public static HashMap<String, Integer> _sizes = new HashMap<>();
    public static HashMap<String, Integer> primitiveTypes = new HashMap<>();
    public static HashMap<String, HashMap<String, String>> _propList = new HashMap<>();
    public static HashMap<String, Integer> _offsetList = new HashMap<>();
    public static String REFERENCE = "reference";
    public static String INTEGER = "int";
    public static String BOOLEAN = "bool";
    public static String UNDEFINED = "undefined";
    public static String CHAR = "char";
    public static String VOID = "void";
    private static String STRING = "string";
    private static int SIZE_POINTER = 1;
    
    public static HashMap<String, Integer> getSizes() {
        return _sizes;
    }
    
    public static void _init() {
        
    }

    /**
     * Adiciona um tipo a linguagem
     *
     * @param id
     * @param type
     * @throws java.lang.Exception
     */
    public static void Add(String id, Node type) throws Exception {
        
        if (_types.containsKey(id)) {
            throw new Exception(String.format("Tipo '%s' previamente definido na linha %s", id, _types.get(id).get("line", "")));
        }
//        System.out.println("AddType:" + id);
        if (!type.Has("public")) {
            type.S("public", Base.IsPublic(id) ? "true" : "false");
        }
        Node def;
        // Completa a definicao da struct importando os atributos herdados
        for (Node t : type.findAll("type", "class", 1)) {
//            System.out.println("import type:" + t.G("type"));

//            if (t.G("type").equals("null")) {
//                // todo - adicionar erro ja que o tipo deveria existir
//                continue;
//            }
//            System.out.println("Include type:" + t.G("type"));
            def = Get(t.G("type"));
            if (def == null) {
                continue;
            }
            for (Node attr : def.findAll("attr", "class")) {
                attr.S("path", def.getText());
                type.addFilho(attr);
            }
        }
        // Atualiza o tamanho to tipo
        int size = 0;
        if (type.Has("size")) {
            size = type.getInt("size");
        } else {
            for (Node attr : type.findAll("attr", "class")) {
//            System.out.println("Add(" + id + "|" + attr.G("type") + "):" + attr.childrens().G(0));
                size += _sizes.get(attr.childrens().get(0).G("type"));
            }
        }
        _types.put(id, type);
        _sizes.put(id, size);
        if (type.eq("primitive", "true")) {
            getPrimitiveTypes().put(type.G("value"), 1);
        }
    }

    /**
     * Retorna verdadeiro Se o tipo exiStir
     *
     * @param type
     * @return true Se o tipo exiSte
     * @throws Exception caSo tipo não exiSta
     */
    public static Node Get(String id) {
        return _types.get(id);
    }
    
    public static boolean Defined(String id) {
        return _types.containsKey(id);
    }
    
    public static void List() {
        System.out.println("Types:\n" + _types.keySet());
    }
    
    public static int Size(String type) {
//        System.out.println("Size of " + type + ">" + _sizes.G(type));
        return _sizes.get(type);
    }
    
    public static int Shift(String type, ArrayList<String> path) {
        return Shift(type, path.get(path.size() - 1));
    }
    
    public static int Shift(String type, String attrib) {
        
        Node T = Get(type);
        String attrType;
        int shift = 0;
//        System.out.println("Shift header:" + type + ":" + attrib + ":" + T);
//        System.out.println("Shift:{");
        for (Node attr : T.findAll("attr", "class", 1)) {
            
            attrType = attr.childrens().get(0).G("type");

//            System.out.println("Shift:" + attr.getText() + "[" + attrib + "]");
            if (!attr.getText().equals(attrib)) {
                shift += Size(attrType);
                //incrementar e buscar no proximo
                continue;
            }
            break;
        }
//        System.out.println("}");
        return shift;
    }
    
    public static HashMap<String, Integer> getPrimitiveTypes() {
        return primitiveTypes;
    }
    
    public static boolean Primitive(String type) {
        return primitiveTypes.containsKey(type);
    }

    /**
     * Metodos velhos
     */
//    /**
//     * Retorna verdadeiro se o tipo existir ou lança uma exceção caso contrario
//     *
//     * @param type
//     * @return true se o tipo existe
//     * @throws Exception caso tipo não exista
//     */
//    public static boolean exist(String type) {
//        return _types.containsKey(type);
//    }
//
//    /**
//     * Adiciona um tipo primitivo a linguagem
//     *
//     * @param tname
//     * @param tamanho quantidade de palavras que precisa para armazenamento em
//     * memoria
//     */
//    public static void add(String tname, int tamanho) {
//        Node n = new Node(tname);
//        n.S("primitive", "t");
//        _types.put(tname, n);
//        _sizes.put(tname, tamanho);
//    }
//
//    /**
//     * Adiciona um tipo a linguagem
//     *
//     * @param tname
//     * @param tipo Node com dados do tipo
//     * @param pl
//     */
//    public static void add(String tname, Node tipo, HashMap<String, String> pl) throws Exception {
//        int S = 0, _ts;
//        tipo.S("primitive", "f");
//        _types.put(tname, tipo);
//        _propList.put(tname, pl);
//
//        for (Node prop : tipo.childrens()) {
//            for (Node var : prop.find("vars").childrens()) {
//                _ts = size(var) * sumIndexes(var);
//                _offsetList.put(tname + "." + var.getText(), _ts);
//                var.S("size", _ts);
//                S += _ts;
//            }
//        }
//        tipo.S("size", S);
//        _sizes.put(tname, S);
//    }
//
//    protected static int sumIndexes(Node var) throws Exception {
//        int sum = 1, tmp;
//        Node indexes = var.find("indexes");
//
//        if (indexes != null) {
//            for (Node index : indexes.childrens()) {
//                if (index.isNumber("value")) {
//                    tmp = index.getInt("value");
//                } else {
//                    tmp = Constantes.intVal(index);
//                }
//                sum *= tmp;
//            }
//        }
//        return sum;
//    }
//
//    public static int size(Node prop) throws Exception {
//        return size(prop.G("type"));
//    }
//
//    public static int size(String type) throws Exception {
//        if (type.contains("*")) {
//            return Tipos.SIZE_POINTER;
//        }
//
//        if (!exist(type)) {
//            throw new Exception("Type " + type + " not defined!");
//        }
//        return _sizes.G(type);
//    }
//
//    /**
//     * Retorna o tipo com base no valor, apenas funciona com tipos primitivos
//     * com exceção de thread
//     *
//     * @param valor
//     * @return
//     */
//    public static String byValue(String value) {
//
//        Pattern p = Pattern.compile("(true|false)", Pattern.CASE_INSENSITIVE);
//
//        if (value.matches("-?[0-9]+")) {
//            return Tipos.INTEGER;
//        } else if (p.matcher(value).matches()) {
//            return Tipos.BOOLEAN;
//        } else if (value.matches("'.'")) {
//            return Tipos.CHAR;
//        } else if (value.matches("\".*\"")) {
//            return Tipos.STRING;
//        }
//
//        return Tipos.UNDEFINED;
//    }
//
//    public static int offset(String type, String prop) throws Exception {
//        if (!exist(type)) {
//            throw new Exception("Type " + type + " not defined!");
//        }
//        int offset = 0;
//
//        if (_propList.G(type).containsKey(prop)) {
//            offset = _offsetList.G(type + "." + prop);
//        }
//
//        return offset;
//
////        for (Map.Entry<String, String> i : .entrySet()) {
////            if (i.getKey().equals("prop")) {
////                return i.getValue()
////            }
////            break;
////        }
//    }
//
//    public static String typeOfProp(String type, String prop) throws Exception {
//
//        if (!exist(type)) {
//            throw new Exception("Type " + type + " not defined!");
//        }
//
//        String t = "undefined";
//
//        if (_propList.G(type).containsKey(prop)) {
//            t = _propList.G(type).G(prop);
//        }
//
//        return t;
//    }
//
//    /**
//     *
//     *
//     * Daqui pra cima
//     *
//     *
//     *
//     */
////    /**
////     * Checa se um valor e de um determinado tipo
////     *
////     * @param tipo tipo esperado
////     * @param valor valor a ser testado
////     * @return true | false
////     */
////    public static boolean testeTipoValor(String tipo, String valor) {
////        switch (tipo) {
////            case "int":
////                return valor.matches("-?[0-9]+");
////            case "bool":
////                return valor.matches("(TRUE|FALSE)");
////            case "char":
////                return valor.matches("'.'");
////            case "string":
////                return valor.matches("\".*\"");
////            case "void":
////                return valor.equals("") || valor.equals("void");
////            case "thread":
////            case "undefined":
////                return false;
////        }
////        return true;
////    }
////    public static Node G(String tipo) {
////        tipo = clearName(tipo);
////        if (_types.containsKey(tipo)) {
////            return _types.G(tipo);
////        }
////        return null;
////    }
////
////    public static String clearName(String tipo) {
////        return tipo.replace("*", "");
////    }
////
////    /**
////     * Remove o nome list do tipo.
////     *
////     * @param nome
////     * @return
////     */
////    protected static String baseName(String nome) {
////        return nome.replace("*", "");
////    }
////
//////    public static String tipoOfMatrixAccess() {
//////
//////    }
////    public static String tipoMatrixAcesso(String acesso, String tipo) {
////        int indices = Api.countRegexOcorrences("\\[[0-9A-Za-z_\\(.*\\)]+\\]", acesso);
////        int numinde = (Api.countRegexOcorrences("\\*", tipo) - indices);
//////        int numinde = (Api.countChatOcorrece(tipo, '*') - indices);
//////        System.out.println("num:" + numinde + "::" + indices);
////        String t = baseName(tipo);
////        if (numinde > 0) {
////            t += String.format("%0" + numinde + "d", 0).replace("0", "*");
////        }
////        return t;
////    }
////
////    public static String tipoDe(Node n, String tipo) throws Exception {
////        if (ePrimitivo(tipo)) {
////            return (n.igual("matrix", "true"))
////                    ? tipoMatrixAcesso(n.getText(), tipo) : tipo;
////
////        }
////        if (n.temAtributo("fullAccess")) {
////            return byAcesso(n);
////        } else if (n.igual("class", "dec::var")) {
////            return tipo;
////        }
////        return tipo;
////    }
////
////    public static String tipoDe(String acesso, String tipo) throws Exception {
//////        tipo = baseName(tipo);
////        String[] lista = acesso.split("\\.");
////        ArrayList<String> a = new ArrayList<>(Arrays.asList(lista));
////        // Se for acesso a um atributo de uma variavel
////        if (a.size() > 1) {
////            //Remove a base do nome da variavel
////            a.remove(0);
////            return tipoDe(a, tipo);
////        } else {
////            return tipo;
////        }
////
////    }
////
////    public static String tipoDe(ArrayList<String> parts, String tipo) throws Exception {
////
////        if (tipo.equals("magica")) {
////            return "undefined";
////        }
////
////        String esperado;
////        if (!exist(tipo)) {
////            throw new Exception("Tipo inválido. Tipo {" + baseName(tipo) + "} não existe ou não foi declarado!");
//////            return "undefined";
////        }
////
////        String atributo = parts.remove(0);
////
////        if (!temAtributo(tipo, atributo)) {
////            throw new Exception("Tentando acessar um atributo {" + atributo + "} não existente no {" + baseName(tipo) + "}!");
////        }
////        esperado = getTipoAtributo(tipo, atributo);
//////        System.out.println("TIPO_ESPERADO:[" + atributo + "]" + esperado);
////
////        if (parts.size() != 0) {
////            if (ePrimitivo(esperado)) {
////                throw new Exception("Tentativas de acessar um atributo {" + atributo
////                        + "} de um tipo primitivo { " + esperado + " } não são permitidas");
////            }
////            esperado = tipoDe(parts, esperado);
////        }
//////        System.out.println("atributo::" + atributo);
////        if (atributo.matches(".*(\\[.*\\])+")) {
////            esperado = esperado.replace("*", "");
////        }
////        return esperado;
////    }
////
////    public static boolean ePrimitivo(String tipo) {
////        tipo = baseName(tipo);
////        return primitives.containsKey(tipo);
////    }
////
////    public static String getTipoAtributo(String tipo, String atributo) {
////        tipo = baseName(tipo);
////        atributo = ivannosysUtils.clearName(atributo);
////        Node attrib = _types.G(tipo).first().encontre(atributo);
////        return (null == attrib) ? "undefined" : attrib.G("tipo");
////    }
////
////    /**
////     * Verifica se um tipo possui um determinado atributo.
////     *
////     * @param tipo nome do tipo
////     * @param atributo atributo a ser identificado
////     * @return true | false
////     */
////    public static boolean temAtributo(String tipo, String atributo) {
////        tipo = baseName(tipo);
////        if (!exist(tipo)) {
////            return false;
////        }
////        atributo = ivannosysUtils.clearName(atributo);
//////        System.out.println("TemATRIBUTO:" + tipo + ":" + atributo);
////        if (!_types.containsKey(tipo)) {
////            return false;
////        }
////        Node declaracao = _types.G(tipo).first().encontre(atributo);
////        return !(declaracao == null);
//////        if (declaracao == null) {
//////            return false;
//////        }
//////        return true;
////    }
////
////    /**
////     *
////     * @param dec variavel node
////     * @return quantidade de palavras de um tipo
////     * @throws Exception caso o tipo não exista ou caso o indice seja uma
////     * contante e a mesma não exista
////     */
////    public static int tamanho(Node dec) {
////        int count = 1;
////
////        if (dec.igual("matrix", "true")) {
////            for (Node indice : dec.encontre("indices").filhos()) {
////                count *= __indiceValor(indice);
////            }
////        }
////
////        return tamanho(dec.G("tipo"), count);
////    }
////
////    /**
////     * Retorna o valor do indice de um vetor ou uma matrix, valor inteiro. Caso
////     * seja uma constante o valor e substituido pelo valor da constante caso
////     * exista, caso contrario é lançado uma exceção para constante indefinida.
////     *
////     * @param indice
////     * @return
////     * @throws Exception
////     */
////    protected static int __indiceValor(Node indice) {
////        int sizet = 0;
////        String indValor;
////        if (indice.isNumber("value")) {
////            try {
////                sizet = indice.getInt("value");
////            } catch (Exception ex) {
////                Logger.getLogger(Tipos.class.getName()).log(Level.SEVERE, null, ex);
////            }
////        } else {
////            indValor = indice.getText();
////            Constantes.existe(indValor);
//////            sizet = Constantes.intVal(indValor);
////        }
////        return sizet;
////    }
////
////    /**
////     *
////     * @param tipo identificador do tipo
////     * @return quantidade de palavras de um tipo
////     * @throws Exception caso o tipo não exista
////     */
////    public static int tamanho(String tipo, int vetorSize) {
////        return _size(tipo, vetorSize);
////    }
////
////    /**
////     *
////     * @param tipo identificador do tipo
////     * @return quantidade de palavras de um tipo
////     * @throws Exception caso o tipo não exista
////     */
////    public static int tamanho(String tipo) {
////        return _size(tipo, 1);
////    }
////
////    protected static int _size(String tipo, int vetorSize) {
////        tipo = baseName(tipo);
////
//////        System.out.println("Tamanho:{" + tipo + "}{" + vetorSize + "} {" + tamanhos.G(tipo) + "}");
//////        System.out.println("tamanhos:" + tamanhos);
////        return (exist(tipo) ? _sizes.G(tipo) * vetorSize : -1);
////    }
////
////
////
//////    public static void testeTipoLista(String retornoTipo, HashMap<String, String> retornos, String escopo) {
//////        for (Map.Entry<String, String> en : retornos.entrySet()) {
//////            String tipo = en.getValue();
//////            String[] nome = en.getKey().split(":");
////////            System.out.println("TIPO:" + tipo + ":" + nome[0] + ":" + escopo + "\n");
//////            if (tipo.equals("undefined")) {
////////                System.out.println("==>" + nome[0] + "--" + escopo);
////////                tipo = Variaveis.getVariavelCascade(nome[0], escopo).getTipo();
////////                Node n = Variaveis.find(nome[0], escopo);
//////                tipo = (n == null) ? "undefined" : n.G("tipo");
//////
//////            }
//////            if (!tipo.equals(retornoTipo)) {
//////                System.out.println("ERROR:Linha[" + nome[1] + "]Incorreto retorno tipo[" + tipo + "] esperando [" + retornoTipo + "]");
//////            }
//////        }
//////    }
////    public static int tamanhoVariavel(Node var) throws Exception {
////
////        int size = Tipos.tamanho(var);
////
////        if (var.igual("matrix", "true")) {
////            Node declaracao = ast.encontreDeclaracao(var, var.parent);
////            Node indices = getIndicesDaMatriz(declaracao);
////            for (Node ind : indices.filhos()) {
////                size = size * ind.getInt("value");
////            }
////        }
////        return size;
////    }
////
////    protected static Node getIndicesDaMatriz(Node n) {
////        if (n.igual("class", "assign::op")) {
////            n = n.first();
////        }
////        return n.encontre("indices");
////    }
////
////    /**
////     * Valida acesso a um atributo de uma determinada variavel do tipo estrutura
////     *
////     * @param current
////     */
////    public static void validarAtribuicao(Node current, String atribuicao) throws Exception {
////        Tipos.temAtributo(null, null);
////        String tipoEsperado = tipoDe(atribuicao, atribuicao);
////        String tipoAtribuido = porValor(atribuicao);
////        if (!tipoEsperado.equals(tipoAtribuido)) {
////            throw new Exception("Atribuição invalida tipo esperado {" + tipoEsperado + "} e o tipo atribuido foi{" + tipoAtribuido + "}");
////        }
////    }
////
////    static boolean structAcesso(String varpath) {
////        return varpath.indexOf(".") >= 0;
////    }
////
////    public static ArrayList<String> getIndices(Node acesso) {
//////        System.out.println("tipos:_intd:" + acesso);
////        ArrayList<String> indices = new ArrayList<>();
////
////        if (acesso.igual("class", "atributo")) {
////            String tipo = acesso.parent.G("tipo");
////            if (exist(tipo)) {
////                Node t = _types.G(tipo).encontre("variaveis").encontre(ivannosysUtils.clearName(acesso.getText()));
////                for (Node n : t.encontre("indices").filhos()) {
////                    indices.add(n.getText());
////                }
////            }
////        } else {
////            indices = acesso.getList("indices");
////        }
////        return indices;
////
////    }
////
////    public static ArrayList<String> getIndicesOfAttrib(String tipo, ArrayList<String> parts) {
////        ArrayList<String> lista = new ArrayList<>();
////        Node t = _types.G(tipo);
////        String attrOrigin = parts.remove(0);
////        String attr = ivannosysUtils.clearName(attrOrigin);
////        Node atributo = t.first().encontre(attr);
////        /*Equanto houver atributo segue com recursao, caso seja o ultimo sera avaliado*/
////        if (!parts.isEmpty()) {
////            if (!Tipos.temAtributo(tipo, attr)) {
////                return lista;
////            }
////            if (atributo.igual("matrix", "true")) {
////                ArrayList<String> indices = getIndicesOfAttrib(tipo, parts);
////
////            }
////            return getIndicesOfAttrib(atributo.G("tipo"), parts);
////        }
////
////        if (atributo.igual("matrix", "true")) {
////            for (Node indice : atributo.encontre("indices").filhos()) {
////                lista.add(indice.getText());
////            }
////        }
////        return lista;
////
////    }
////
////    public static void avaliarIndices(Node current, ArrayList<String> decindices, boolean eDeclaracao) {
////        int i = 0, limit, acesso;
////
////        System.out.println("xxxx:" + decindices);
////        try {
////            for (Node indice : current.filhos()) {
//////                System.out.println("decindices.G(i++):" + decindices.G(i));
////                limit = Integer.parseInt(decindices.G(i++));
////                if (!indice.isNumber("value")) {
////                    AnalizadorSemantico.testExist(indice);
////                    continue;
////                }
////                if (eDeclaracao) {
////                    continue;
////                }
////                acesso = indice.getInt("value");
////                if (acesso >= limit || acesso < 0) {
////                    AnalizadorSemantico.addErroComPosicao("Acesso indevido a um array, limite {"
////                            + (limit - 1) + "} enquanto a tentativa foi {"
////                            + acesso + "} na posição {" + i + "}");
////                }
////            }
////        } catch (Exception ex) {
//////            Logger.getLogger(AnalizadorSemantico.class.getName()).log(Level.SEVERE, null, ex);
////            AnalizadorSemantico.addErroComPosicao(ex.getMessage());
//////            System.err.println(e.getMessage());
////            ex.printStackTrace();
////        }
////    }
////
////    public static int validarAcesso(Node current) {
////
////        return 1;
////
////    }
////
////    /**
////     * Retorna o tipo de um atributo a partir do no var
////     *
////     * @param param
////     * @return
////     * @throws Exception
////     */
////    public static String byAcesso(Node param) throws Exception {
////        Node atribute = param.encontreByClass("atributo");
////        if (null != atribute) {
////            return byAcesso(atribute);
////        }
////        return param.G("tipo");
////    }
////
////    /**
////     * Retorna o Node de um atributo de um tipo
////     *
////     * @param tipo
////     * @param atributo
////     * @return
////     */
////    public static Node getAtributo(String tipo, String atributo) {
////        tipo = baseName(tipo);
//////        System.out.println("GerATributo::" + tipo + ":::" + atributo);
////        return _types.G(tipo).encontre("variaveis").encontre(Variaveis.clearName(atributo));
////    }
////
////    /**
////     * Retorna o node da ast contendo os dados do tipo declarado
////     *
////     * @param tipo
////     * @return
////     */
////    public static Node declaracao(String tipo) {
////        return _types.G(tipo);
////    }
////
////    /**
////     * Retorna o deslocamento ate o endereco base de um atributo dentro de um
////     * tipo.
////     *
////     * @param tipo a ser analisado
////     * @param atributo atributo do tipo analisado
////     * @return inteiro referente ao deslocamento ate a base do atributo
////     * @throws Exception
////     */
////    public static int enderecoBase(String tipo, String atributo) throws Exception {
////        tipo = baseName(tipo);
////        atributo = Variaveis.clearName(atributo);
////        Node t = _types.G(tipo).encontre("variaveis");
////        int base = 0;
////        for (Node n : t.filhos()) {
////            if (n.igual("value", atributo)) {
////                break;
////            }
////            base += Tipos.tamanho(n);
////        }
////        return base;
////    }
////
////    public static String toAsteriscoNotation(Node declaracao, Node atributo) {
////        String nome = atributo.getText();
////        String baseTipo = "undefined";
////
////        Node decAtributo = declaracao.first().encontre(Variaveis.clearName(nome));
////
////        if (decAtributo.igual("matrix", "true")) {
////
//////          decAtributo.G("tipo")  
////        }
////
////        System.out.println("Dec:" + decAtributo + "\natr:" + atributo);
////
////        return baseTipo;
////    }
}
