/*
 * 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.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

    public static Pattern TYPEANDMETHOD = Pattern.compile("\\((?<type>\\w+(\\.\\w+)+)\\)\\.(?<method>\\w+)");
    /*Key: Nome da funcao, Val: Tipo da funcao */
    protected static HashMap<String, Integer> funcoes;

    /*Key: Nome da funcao, Val: Tipo da funcao */
    protected static HashMap<String, HashMap<String, String>> params;

    /*Key: Nome da funcao, Val: Numero de parametros */
    protected static HashMap<String, Integer> numparms;

    /*Key: Nome da funcao, Val: Tipo de retorno */
    protected static HashMap<String, HashMap<String, String>> retorno;

    /*Key: Nome da funcao, Val: escopo */
    protected static HashMap<String, String> escopo;

    /*Key: Nome da funcao, Val: Node function */
    protected static HashMap<String, Node> funcs;
    public static String FREE = "__FREE__";
    public static String NEW = "__NEW__";

    protected static Node callTree;
    protected static Node currentCall;
    protected static HashMap<String, Integer> called;

    public static void _init() {
        System.out.println("Inicializando API.Funcao");
        funcoes = new HashMap<>();
        params = new HashMap<>();
        numparms = new HashMap<>();
        retorno = new HashMap<>();
        escopo = new HashMap<>();
        funcs = new HashMap<>();
        called = new HashMap<>();
    }

    public static void List() {
        System.out.println("Funcoes:\n" + funcs.keySet());
    }

    public static void Add(String id, Node func) throws Exception {
        if (funcs.containsKey(id)) {
            throw new Exception(
                    String.format("Função ou metodo '%s' definido previamente na linha %s",
                            id,
                            funcs.get(id).get("line", "")
                    ));
        }
        func.S("public", Base.IsPublic(func.G("name")) ? "true" : "false");
//        System.out.println("Add FUNC:" + id);
        funcs.put(id, func);
//        System.out.println("Funcs:" + funcs.keySet());
    }

    public static boolean Defined(String id) {
        return funcs.containsKey(id);
    }

    public static Node Get(String id) throws Exception {
        Node n;
        if (!Defined(id)) {
            // Se for metodo de uma struct busca por tipos
            n = DeepGet(id);
        } else {
            n = funcs.get(id);
        }
//        if (n == null) {
//            throw new Exception(String.format("Função ou Metodo '%s' não definido", id));
//        }
        return n;
    }

    public static Node DeepGet(String id) throws Exception {
        Matcher matcher = TYPEANDMETHOD.matcher(id);
        String type = null, method = null;

        while (matcher.find()) {
            type = matcher.group("type");
            method = matcher.group("method");
            break;
        }
        Node T = Tipos.Get(type);
//        System.out.println("DeepGet:" + type + ":" + method + ":" + T);
        if (T != null) {
            Node def;
            String packageName;
            for (Node e : T.findAll("type", "class", 1)) {
                type = e.G("type");
                packageName = type.split("\\.")[0];
                id = packageName + ".method.(" + type + ")." + method;
//                System.out.println("Deep:" + id);
                def = Get(id);
                if (def != null) {
                    return def;
                }
            }
        }

        return null;
    }

    // Metodos antigos
    public static boolean setCurrentCall(String function) {
        Node n = new Node(function);
        boolean _continue = true;
        if (!called.containsKey(function)) {
            called.put(function, 0);
        } else {
            called.put(function, called.get(function) + 1);
            _continue = false;
        }
        if (currentCall != null) {
            currentCall.addFilho(n);
        }
        currentCall = n;
        return _continue;
    }

    public static void returnCallLevel() {
        if (currentCall.parent != null) {
            String longid = currentCall.getText();

            if (currentCall.closest(longid, "value") != null) {
                currentCall.S("recursive", "t");
            }
            currentCall.set("multi_calls", called.get(longid));
            currentCall = currentCall.parent;
        }
    }

    public static void printCallTree() {
        if (callTree != null) {
            System.out.println(callTree.stringfy());
        }
    }

    public static Node getCallTree() {
        return callTree;
    }

    public static void genCallTree(AbstractSyntaxTree ast) throws Exception {
        Node main = ast.getMain();
        String id = main.getText();
        setCurrentCall(id);
        Node parent;

        callTree = new Node(id);

        currentCall = callTree;

        for (Node call : main.find("stmts").findAll("call", "class")) {
            System.out.println("GenCallTree:" + call.getText());

            parent = call.parent;
//            genCallTree(findDefinition(parent), parent.G("longid"));
        }

        returnCallLevel();
    }
//
//    protected static void genCallTree(Node n, String longid) throws Exception {
//
//        if (n == null) {
//            throw new Exception("Not found definition to " + longid + " !");
//        }
//
//        Node parent;
//
//        if (setCurrentCall(longid)) {
//            for (Node call : n.find("stmts").findAll(".call", "class")) {
//                parent = call.parent;
//                genCallTree(findDefinition(parent), parent.G("longid"));
//            }
//        }
//
//        returnCallLevel();
//
//    }

//    protected static Node findDefinition(Node call) {
//        String longid = call.G("longid");
//
//        switch (call.G("class")) {
//            case "function::call":
//                System.out.println("longid:" + longid);
//                return Functions.function(longid);
//            default:
//                return Methods.function(longid);
//        }
//    }
    /**
     * Adiciona uma nova função a linguagem
     *
     * @param nome da funcao
     * @param tipoRetorno tipo do retorno da funcao
     * @param paramsList liSta de parametroS com tipo
     * @param escopo eScopo em que função foi declarada
     * @param func
     */
    public static void add(String nome, HashMap<String, String> tipoRetorno, HashMap<String, String> paramsList, String escopo, Node func) {
        funcoes.put(nome, 1);
        retorno.put(nome, tipoRetorno);
        funcs.put(nome, func);
        params(nome, paramsList);
        escopo(nome, escopo);
    }

    /**
     * RegiStra uma definicao de uma função O nome deve Ser doS SeguinteS
     * formatoS <br>
     * -- funcname correSponde ao id da funcao <br>
     *
     * {funcname}() // CorreSponde a funcoeS definidaS no pacote local<br>
     * {package}.{funcname}() // CorreSponde a funcoeS importadaS de outroS
     * pacoteS<br>
     * {package}.{method}.{type}.{funcname}() //Corresponde a metodos de um tipo
     *
     * @param nome
     * @param func
     */
    public static void add(String nome, Node func) {
        funcs.put(nome, func);
//        escopo(nome, escopo);
    }

//    /**
//     * Adiciona uma nova função a linguagem
//     *
//     * @param nome da funcao
//     * @param tipoRetorno tipo do retorno da funcao
//     * @param escopo escopo em que função foi declarada
//     * @throws Exception
//     */
//    public static void add(String nome, String tipoRetorno, String escopo) {
//        HashMap<String, String> paramsList = new HashMap<>();
//        add(nome, tipoRetorno, paramsList, escopo);
//        numParams(nome, -1);
//    }
    public static boolean existe(String nome) {
        return funcoes.containsKey(nome);
    }

    public static HashMap<String, String> retorno(String nome) {
        return (existe(nome)) ? retorno.get(nome) : null;
    }

    protected static int numParams(String nome, int num) {
        if (existe(nome)) {
            numparms.put(nome, num);
            return num;
        }
        return -1;
    }

    public static int numParams(String nome) {
        return (existe(nome)) ? params.get(nome).size() : -1;
    }

    public static HashMap<String, String> params(String nome) {

        return existe(nome) ? params.get(nome) : null;
    }

    public static void params(String nome, HashMap<String, String> paramsList) {
        if (existe(nome)) {
            params.put(nome, paramsList);
            numParams(nome, paramsList.size());
        }
    }

    public static void escopo(String nome, String escpo) {
        if (existe(nome)) {
            escopo.put(nome, escpo);
        }
    }

    public static String escopo(String nome) {

        return existe(nome) ? escopo.get(nome) : "";
    }

    public static String toString(String nome) {
        existe(nome);

        String params = "";
        for (Map.Entry<String, String> entry : Functions.params.get(nome).entrySet()) {
            String varNome = entry.getKey();
            String tipo = entry.getValue();

            params += tipo + "," + varNome + ";";
        }

        return "Nome[" + funcoes.get(nome)
                + "]numParametros[" + numParams(nome)
                + "]params[" + params
                + "]return[" + retorno(nome) + "]";
    }

    public static Node function(String nome) {
//        System.out.println("Functions:" + funcs.keySet());
        return (existe(nome)) ? funcs.get(nome) : null;
    }

//    public static String retornoPonteiro(String nome) {
//        return (existe(nome))
//                ? (funcs.G(nome).eq("ponteiro", "true") ? "true" : "false")
//                : "false";
//    }
//    public static Node GetDef(String text) {
//        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
//    }
}
