"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PilhaEscoposExecucao = void 0;
const estruturas_1 = require("./estruturas");
const excecoes_1 = require("../excecoes");
const lexador_1 = require("../lexador");
const inferenciador_1 = require("../inferenciador");
const delegua_1 = __importDefault(require("../tipos-de-dados/delegua"));
class PilhaEscoposExecucao {
    constructor() {
        this.pilha = [];
    }
    empilhar(item) {
        this.pilha.push(item);
    }
    eVazio() {
        return this.pilha.length === 0;
    }
    elementos() {
        return this.pilha.length;
    }
    naPosicao(posicao) {
        return this.pilha[posicao];
    }
    topoDaPilha() {
        if (this.eVazio())
            throw new Error('Pilha vazia.');
        return this.pilha[this.pilha.length - 1];
    }
    removerUltimo() {
        if (this.eVazio())
            throw new Error('Pilha vazia.');
        return this.pilha.pop();
    }
    converterValor(tipo, valor) {
        switch (tipo) {
            case 'inteiro':
                return parseInt(valor);
            case 'logico':
            case 'lógico':
                return Boolean(valor);
            case 'numero':
            case 'número':
                return Number(valor);
            case 'texto':
                return String(valor);
            default:
                return valor;
        }
    }
    definirConstante(nomeConstante, valor, tipo) {
        const constante = this.pilha[this.pilha.length - 1].espacoMemoria.valores[nomeConstante];
        let tipoConstante;
        if (constante && constante.hasOwnProperty('tipo')) {
            tipoConstante = constante.tipo;
        }
        else if (tipo) {
            tipoConstante = tipo;
        }
        else {
            tipoConstante = (0, inferenciador_1.inferirTipoVariavel)(valor);
        }
        let elementoAlvo = {
            valor: this.converterValor(tipo, valor),
            tipo: tipoConstante,
            subtipo: undefined,
            imutavel: true,
        };
        if ([delegua_1.default.VETOR, delegua_1.default.TUPLA].includes(tipoConstante)) {
            let subtipo = '';
            if (valor instanceof Array) {
                // TODO: verificar tipo lógico e outros possíveis subtipos
                let numeros = valor.some((v) => typeof v === 'number');
                let textos = valor.some((v) => typeof v === 'string');
                if (numeros && textos)
                    subtipo = delegua_1.default.QUALQUER;
                else if (numeros)
                    subtipo = delegua_1.default.NUMERO;
                else
                    subtipo = delegua_1.default.TEXTO;
            }
            elementoAlvo.subtipo = subtipo;
        }
        this.pilha[this.pilha.length - 1].espacoMemoria.valores[nomeConstante] = elementoAlvo;
    }
    definirVariavel(nomeVariavel, valor, tipo) {
        const variavel = this.pilha[this.pilha.length - 1].espacoMemoria.valores[nomeVariavel];
        let tipoVariavel;
        let subtipo = undefined;
        if (variavel && variavel.hasOwnProperty('tipo')) {
            tipoVariavel = variavel.tipo;
        }
        else if (valor && valor.constructor === estruturas_1.DeleguaFuncao) {
            tipoVariavel = 'função';
            if (tipo !== undefined) {
                tipoVariavel = `função<${tipo}>`;
                subtipo = tipo;
            }
        }
        else if (tipo) {
            tipoVariavel = tipo;
        }
        else {
            tipoVariavel = (0, inferenciador_1.inferirTipoVariavel)(valor);
        }
        let elementoAlvo = {
            valor: this.converterValor(tipoVariavel, valor),
            tipo: tipoVariavel,
            subtipo: subtipo,
            imutavel: false,
        };
        if ([delegua_1.default.VETOR, delegua_1.default.TUPLA].includes(tipoVariavel)) {
            let subtipo = '';
            if (valor instanceof Array) {
                // TODO: verificar tipo lógico e outros possíveis subtipos
                let numeros = valor.some((v) => typeof v === 'number');
                let textos = valor.some((v) => typeof v === 'string');
                if (numeros && textos)
                    subtipo = delegua_1.default.QUALQUER;
                else if (numeros)
                    subtipo = delegua_1.default.NUMERO;
                else
                    subtipo = delegua_1.default.TEXTO;
            }
            elementoAlvo.subtipo = subtipo;
        }
        this.pilha[this.pilha.length - 1].espacoMemoria.valores[nomeVariavel] = elementoAlvo;
    }
    atribuirVariavelEm(distancia, simbolo, valor) {
        const espacoMemoriaAncestral = this.pilha[this.pilha.length - distancia].espacoMemoria;
        if (espacoMemoriaAncestral.valores[simbolo.lexema].imutavel) {
            throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Constante '${simbolo.lexema}' não pode receber novos valores.`);
        }
        espacoMemoriaAncestral.valores[simbolo.lexema] = {
            valor,
            tipo: (0, inferenciador_1.inferirTipoVariavel)(valor),
            imutavel: false,
        };
    }
    atribuirVariavel(simbolo, valor, indice) {
        for (let i = 1; i <= this.pilha.length; i++) {
            const espacoMemoria = this.pilha[this.pilha.length - i].espacoMemoria;
            if (espacoMemoria.valores[simbolo.lexema] !== undefined) {
                const variavel = espacoMemoria.valores[simbolo.lexema];
                if (variavel.imutavel) {
                    throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Constante '${simbolo.lexema}' não pode receber novos valores.`);
                }
                const tipoInferido = variavel && variavel.hasOwnProperty('tipo') && variavel.tipo
                    ? variavel.tipo
                    : (0, inferenciador_1.inferirTipoVariavel)(valor);
                const tipo = (tipoInferido || 'objeto').toLowerCase();
                const valorResolvido = this.converterValor(tipo, valor);
                if (indice !== undefined && indice !== null) {
                    let variavelValor = variavel.valor;
                    if (variavelValor instanceof Array || variavelValor instanceof Object) {
                        variavelValor[indice] = valorResolvido;
                    }
                    else {
                        throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, 'Variável não é um vetor ou dicionário.');
                    }
                }
                else {
                    espacoMemoria.valores[simbolo.lexema] = {
                        valor: valorResolvido,
                        tipo,
                        imutavel: false,
                    };
                }
                return;
            }
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, "Variável não definida '" + simbolo.lexema + "'.");
    }
    obterEscopoPorTipo(tipo) {
        for (let i = 1; i <= this.pilha.length; i++) {
            const escopoAtual = this.pilha[this.pilha.length - i];
            if (escopoAtual.tipo === tipo) {
                return escopoAtual;
            }
        }
        return undefined;
    }
    obterVariavelEm(distancia, nome) {
        const ambienteAncestral = this.pilha[this.pilha.length - distancia].espacoMemoria;
        return ambienteAncestral.valores[nome];
    }
    obterValorVariavel(simbolo) {
        for (let i = 1; i <= this.pilha.length; i++) {
            const ambiente = this.pilha[this.pilha.length - i].espacoMemoria;
            if (ambiente.valores[simbolo.lexema] !== undefined) {
                return ambiente.valores[simbolo.lexema];
            }
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, "Variável não definida: '" + simbolo.lexema + "'.");
    }
    obterVariavelPorNome(nome) {
        for (let i = 1; i <= this.pilha.length; i++) {
            const ambiente = this.pilha[this.pilha.length - i].espacoMemoria;
            if (ambiente.valores[nome] !== undefined) {
                return ambiente.valores[nome];
            }
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(new lexador_1.Simbolo('especial', nome, nome, -1, -1), "Variável não definida: '" + nome + "'.");
    }
    /**
     * Método usado pelo depurador para obter todas as variáveis definidas.
     */
    obterTodasVariaveis(todasVariaveis = []) {
        for (let i = 1; i <= this.pilha.length - 1; i++) {
            const valoresEspacoMemoria = this.pilha[this.pilha.length - i].espacoMemoria.valores;
            const vetorObjeto = Object.entries(valoresEspacoMemoria).map((chaveEValor, indice) => ({
                nome: chaveEValor[0],
                valor: chaveEValor[1].valor,
                tipo: chaveEValor[1].tipo,
                imutavel: chaveEValor[1].imutavel,
            }));
            todasVariaveis = todasVariaveis.concat(vetorObjeto);
        }
        return todasVariaveis;
    }
    /**
     * Obtém todas as funções declaradas ou por código-fonte, ou pelo desenvolvedor
     * em console, do último escopo.
     */
    obterTodasDeleguaFuncao() {
        const retorno = {};
        const espacoMemoria = this.pilha[this.pilha.length - 1].espacoMemoria;
        for (const [nome, corpo] of Object.entries(espacoMemoria.valores)) {
            const corpoValor = corpo.hasOwnProperty('valor') ? corpo.valor : corpo;
            if (corpoValor instanceof estruturas_1.DeleguaFuncao) {
                retorno[nome] = corpoValor;
            }
        }
        return retorno;
    }
    /**
     * Obtém todas as declarações de classe do último escopo.
     * @returns
     */
    obterTodasDeclaracoesClasse() {
        const retorno = {};
        const ambiente = this.pilha[this.pilha.length - 1].espacoMemoria;
        for (const [nome, corpo] of Object.entries(ambiente.valores)) {
            const corpoValor = corpo.hasOwnProperty('valor') ? corpo.valor : corpo;
            if (corpoValor instanceof estruturas_1.DescritorTipoClasse) {
                retorno[nome] = corpoValor;
            }
        }
        return retorno;
    }
    registrarReferenciaFuncao(idFuncao, funcao) {
        const espacoMemoriaAtual = this.pilha[this.pilha.length - 1].espacoMemoria;
        espacoMemoriaAtual.referenciasFuncoes[idFuncao] = funcao;
    }
    obterReferenciaFuncao(idFuncao) {
        for (let i = 1; i <= this.pilha.length; i++) {
            const espacoMemoria = this.pilha[this.pilha.length - i].espacoMemoria;
            if (espacoMemoria.referenciasFuncoes[idFuncao] !== undefined) {
                return espacoMemoria.referenciasFuncoes[idFuncao];
            }
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(new lexador_1.Simbolo('especial', idFuncao, idFuncao, -1, -1), "Referência para função não encontrada: '" + idFuncao + "'.");
    }
    registrarReferenciaMontao(endereco) {
        const espacoMemoria = this.pilha[this.pilha.length - 1].espacoMemoria;
        espacoMemoria.enderecosMontao.add(endereco);
    }
    migrarReferenciaMontaoParaEscopoDeVariavel(nomeVariavel, enderecoMontao) {
        // TODO: Normalmente uma referência a ser migrada está sempre no último escopo.
        // Conferir se é sempre este o caso.
        const ultimoEspacoMemoria = this.pilha[this.pilha.length - 1].espacoMemoria;
        ultimoEspacoMemoria.enderecosMontao.delete(enderecoMontao);
        for (let i = 1; i <= this.pilha.length; i++) {
            const espacoMemoria = this.pilha[this.pilha.length - i].espacoMemoria;
            if (espacoMemoria.valores[nomeVariavel] !== undefined) {
                espacoMemoria.enderecosMontao.add(enderecoMontao);
                break;
            }
        }
        // TODO: Devemos emitir erro em algum momento?
    }
}
exports.PilhaEscoposExecucao = PilhaEscoposExecucao;
//# sourceMappingURL=pilha-escopos-execucao.js.map