#ifndef SEMANTICO_H
#define SEMANTICO_H

#include "lexer.h"
#include "parser.h"
#include "semantico_aux.h"
#include <map>
#include <vector>

struct analisis_funcion {
   const declaracion_funcion* declaracion;

   tipo_evaluado retorno;
   std::map<const sentencia_declaracion*, tipo_evaluado> variables;
   std::map<const expresion*, tipo_evaluado> tipos;
   std::map<const sentencia*, const sentencia*> ciclo_afectado;      // break o continue => ciclo afectado
   std::map<const expresion_termino*, int> enteros;
   std::map<const expresion_termino*, std::string> cadenas;
   std::map<const expresion_termino*, const sentencia_declaracion*> variable_referida;
   std::map<const expresion_llamada*, const analisis_funcion*> funcion_referida;
};

struct tabla_simbolos {
   std::map<std::string_view, analisis_funcion> funciones;
};

struct pila_simbolos {
   const tabla_simbolos* tabla;
   std::vector<std::map<std::string_view, const sentencia_declaracion*>> bloques{1};
   std::vector<sentencia*> ciclos_anidados;

   pila_simbolos(const tabla_simbolos* t)
   : tabla(t) {
   }

   const analisis_funcion* busca_funcion(std::string_view nombre) {
      if (tabla->funciones.contains(nombre)) {
         return &tabla->funciones.find(nombre)->second;
      }
      return nullptr;
   }

   const sentencia_declaracion* busca_variable(std::string_view nombre) {
      for (int i = bloques.size( ) - 1; i >= 0; --i) {
         if (bloques[i].contains(nombre)) {
            return bloques[i][nombre];
         }
      }
      return nullptr;
   }

   bool declara_variable(std::string_view nombre, const sentencia_declaracion* s) {
      if (bloques.back( ).contains(nombre)) {
         return false;
      } else {
         bloques.back( )[nombre] = s;
         return true;
      }
   }

   void crea_bloque( ) {
      bloques.emplace_back( );
   }

   void quita_bloque( ) {
      bloques.pop_back( );
   }

   void anida_ciclo(sentencia* s) {
      ciclos_anidados.push_back(s);
   }

   void quita_ciclo( ) {
      ciclos_anidados.pop_back( );
   }
};

tipo_evaluado analiza_semantica(expresion_termino* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   if (ex->t.tipo == LITERAL_ENTERA) {
      int v = evalua_literal_entera(ex->t);
      analisis.enteros[ex] = v;
      return analisis.tipos[ex] = tipo_evaluado(INT, ESCALAR, TEMPORAL);
   } else if (ex->t.tipo == LITERAL_CADENA) {
      std::string s = evalua_literal_cadena(ex->t);
      analisis.cadenas[ex] = s;
      return analisis.tipos[ex] = (s.size( ) == 1 ?
         tipo_evaluado(CHAR, ESCALAR, TEMPORAL) :
         tipo_evaluado(CHAR, ARREGLO, TEMPORAL, s.size( ))
      );
   } else if (ex->t.tipo == IDENTIFICADOR) {
      const sentencia_declaracion* s = pila.busca_variable(ex->t.vista);
      if (s == nullptr) {
         throw error("Variable no declarada", ex->vista);
      }
      
      analisis.variable_referida[ex] = s;
      return analisis.tipos[ex] = analisis.variables[s];
   } else {
      throw error("Termino no evaluable", ex->vista);
   }
}

tipo_evaluado analiza_semantica(expresion_binaria* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   // tarea 6
}

tipo_evaluado analiza_semantica(expresion_prefija* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   tipo_evaluado t = analiza_semantica(ex->ex, analisis, pila);
   if (ex->operador.tipo == MAS || ex->operador.tipo == MENOS || ex->operador.tipo == NOT) {
      if (tipo_compatible(t, tipo_evaluado(INT, ESCALAR))) {
         return analisis.tipos[ex] = tipo_evaluado(INT, ESCALAR, TEMPORAL);
      }
   } else if (ex->operador.tipo == TAMANYO) {
      if (t.modificador == ARREGLO) {
         return analisis.tipos[ex] = tipo_evaluado(INT, ESCALAR, TEMPORAL);
      }
   } else if (ex->operador.tipo == DIRECCION) {
      if (t.vida == VARIABLE && (tipo_compatible(t, tipo_evaluado(INT, ESCALAR)) || tipo_compatible(t, tipo_evaluado(CHAR, ESCALAR)))) {
         return analisis.tipos[ex] = tipo_evaluado(t.base, APUNTADOR, TEMPORAL);
      }
   }

   throw error("Operador prefijo no es compatible con la expresion", ex->vista);
}

tipo_evaluado analiza_semantica(expresion_posfija* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   // tarea 6
}

tipo_evaluado analiza_semantica(expresion_llamada* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   auto p = dynamic_cast<expresion_termino*>(ex->izq);
   if (p == nullptr) {
      throw error("Lado izquierdo invalido en una llamada a funcion", ex->vista);
   }

   if (p->t.tipo == INT || p->t.tipo == CHAR) {
      if (ex->dentro.size( ) == 1) {
         auto t = analiza_semantica(ex->dentro[0], analisis, pila);
         if (tipo_compatible(t, tipo_evaluado(INT, ESCALAR)) || tipo_compatible(t, tipo_evaluado(CHAR, ESCALAR))) {
            return analisis.tipos[ex] = tipo_evaluado(p->t.tipo, ESCALAR, TEMPORAL);
         }
      }
      throw error("Moldeado invalido", ex->vista);
   } else if (p->t.tipo == PRINT || p->t.tipo == SCAN) {
      if (ex->dentro.size( ) == 1) {
         auto t = analiza_semantica(ex->dentro[0], analisis, pila);
         if (p->t.tipo == PRINT && (tipo_compatible(t, tipo_evaluado(INT, ESCALAR)) || tipo_compatible(t, tipo_evaluado(CHAR, ESCALAR)) || tipo_compatible(t, tipo_evaluado(CHAR, ARREGLO)))) {
            return analisis.tipos[ex] = tipo_evaluado(VOID, ESCALAR, TEMPORAL);
         } else if (p->t.tipo == SCAN && (tipo_compatible(t, tipo_evaluado(INT, ESCALAR)) || tipo_compatible(t, tipo_evaluado(CHAR, ESCALAR))) && t.vida == VARIABLE) {
            return analisis.tipos[ex] = tipo_evaluado(VOID, ESCALAR, TEMPORAL);
         }
      }
      throw error("Llamada invalida a funcion incorporada", ex->vista);
   } else {
      if (pila.busca_variable(p->t.vista) != nullptr) {
         throw error("Variable usada como funcion", ex->vista);
      }

      const analisis_funcion* pa = pila.busca_funcion(p->t.vista);
      if (pa == nullptr) {
         throw error("Funcion no declarada", ex->vista);
      } else if (pa->declaracion->parametros.size( ) != ex->dentro.size( )) {
         throw error("Llamada con numero incorrecto de parametros", ex->vista);
      }
      for (int i = 0; i < ex->dentro.size( ); ++i) {
         if (!tipo_compatible(pa->variables.find(&pa->declaracion->parametros[i])->second, analiza_semantica(ex->dentro[i], analisis, pila))) {
            throw error("Tipo de argumento incompatible con parametro", ex->vista);
         }
      }

      analisis.funcion_referida[ex] = pa;
      return analisis.tipos[ex] = pa->retorno;
   }
}

tipo_evaluado analiza_semantica(expresion_indexacion* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   tipo_evaluado izq = analiza_semantica(ex->izq, analisis, pila), dentro = analiza_semantica(ex->dentro, analisis, pila);
   if (izq.modificador == ESCALAR) {
      throw error("Solo se permite indexar arreglos y apuntadores", ex->vista);
   } else if (!tipo_compatible(dentro, tipo_evaluado(INT, ESCALAR))) {
      throw error("Solo se pueden usar enteros como indices", ex->vista);
   }
   return analisis.tipos[ex] = tipo_evaluado(izq.base, ESCALAR, (izq.modificador == APUNTADOR ? VARIABLE : izq.vida));
}

tipo_evaluado analiza_semantica(expresion_secuencia* ex, analisis_funcion& analisis, pila_simbolos& pila) {
   if (ex->ex.empty( )) {
      throw error("No se permiten literales de secuencia vacias", ex->vista);
   }
   tipo_evaluado primero = analiza_semantica(ex->ex[0], analisis, pila);
   if (!tipo_compatible(primero, tipo_evaluado(INT, ESCALAR)) && !tipo_compatible(primero, tipo_evaluado(CHAR, ESCALAR))) {
      throw error("Solo se permiten arreglos de enteros y caracteres", ex->vista);
   }
   for (int i = 1; i < ex->ex.size( ); ++i) {
      if (!tipo_compatible(primero, analiza_semantica(ex->ex[i], analisis, pila))) {
         throw error("Todos los elementos de la secuencia deben ser del mismo tipo", ex->vista);
      }
   }
   return analisis.tipos[ex] = tipo_evaluado(primero.base, ARREGLO, TEMPORAL, ex->ex.size( ));
}

void analiza_semantica(sentencia_expresion* s, analisis_funcion& analisis, pila_simbolos& pila) {
   analiza_semantica(s->ex, analisis, pila);
}

void analiza_semantica(sentencia_declaracion* s, analisis_funcion& analisis, pila_simbolos& pila) {
   tipo_evaluado t = analiza_como_tipo(s->tipo, LOCAL);
   if (!pila.declara_variable(s->nombre.vista, s)) {
      throw error("Nombre de variable repetido", s->vista);
   } else if (s->inicializador != nullptr && !tipo_compatible(t, analiza_semantica(s->inicializador, analisis, pila))) {
      throw error("Inicializador de variable es de tipo incorrecto", s->vista);
   }
   analisis.variables[s] = t;
}

void analiza_semantica(sentencia_if* s, analisis_funcion& analisis, pila_simbolos& pila) {
   tipo_evaluado t = analiza_semantica(s->condicion, analisis, pila);
   if (!tipo_compatible(t, tipo_evaluado(INT, ESCALAR))) {
      throw error("Tipo invalido en condicion", s->vista);
   }

   pila.crea_bloque( );
   for (sentencia* actual : s->cuerpo_si) {
      analiza_semantica(actual, analisis, pila);
   }
   pila.quita_bloque( );
   pila.crea_bloque( );
   for (sentencia* actual : s->cuerpo_no) {
      analiza_semantica(actual, analisis, pila);
   }
   pila.quita_bloque( );
}

void analiza_semantica(sentencia_while* s, analisis_funcion& analisis, pila_simbolos& pila) {
   tipo_evaluado t = analiza_semantica(s->condicion, analisis, pila);
   if (!tipo_compatible(t, tipo_evaluado(INT, ESCALAR))) {
      throw error("Tipo invalido en condicion", s->vista);
   }

   pila.crea_bloque( );
   pila.anida_ciclo(s);
   for (sentencia* actual : s->cuerpo) {
      analiza_semantica(actual, analisis, pila);
   }
   pila.quita_ciclo( );
   pila.quita_bloque( );
}

void analiza_semantica(sentencia_do* s, analisis_funcion& analisis, pila_simbolos& pila) {
   pila.crea_bloque( );
   pila.anida_ciclo(s);
   for (sentencia* actual : s->cuerpo) {
      analiza_semantica(actual, analisis, pila);
   }
   pila.quita_ciclo( );
   pila.quita_bloque( );

   tipo_evaluado t = analiza_semantica(s->condicion, analisis, pila);
   if (!tipo_compatible(t, tipo_evaluado(INT, ESCALAR))) {
      throw error("Tipo invalido en condicion", s->vista);
   }
}

void analiza_semantica(sentencia_for* s, analisis_funcion& analisis, pila_simbolos& pila) {
   pila.crea_bloque( );
   pila.anida_ciclo(s);
   if (s->inicializacion != nullptr) {
      analiza_semantica(s->inicializacion, analisis, pila);
   }
   if (s->condicion != nullptr) {
      tipo_evaluado t = analiza_semantica(s->condicion, analisis, pila);
      if (!tipo_compatible(t, tipo_evaluado(INT, ESCALAR))) {
         throw error("Tipo invalido en condicion", s->vista);
      }
   }
   if (s->actualizacion != nullptr) {
      analiza_semantica(s->actualizacion, analisis, pila);
   }
   for (sentencia* actual : s->cuerpo) {
      analiza_semantica(actual, analisis, pila);
   }
   pila.quita_ciclo( );
   pila.quita_bloque( );
}

void analiza_semantica(sentencia_return* s, analisis_funcion& analisis, pila_simbolos& pila) {
   if (s->ex == nullptr) {
      if (!tipo_compatible(analisis.retorno, tipo_evaluado(VOID, ESCALAR))) {
         throw error("Sin valor de retorno en funcion que no devuelve void", s->vista);
      }
   } else if (!tipo_compatible(analisis.retorno, analiza_semantica(s->ex, analisis, pila))) {
      throw error("Tipo incorrecto en el valor de retorno", s->vista);
   }
}

void analiza_semantica(sentencia_break* s, analisis_funcion& analisis, pila_simbolos& pila) {
   if (pila.ciclos_anidados.empty( )) {
      throw error("Sentencia break fuera de ciclos", s->vista);
   }
   analisis.ciclo_afectado[s] = pila.ciclos_anidados.back( );
}

void analiza_semantica(sentencia_continue* s, analisis_funcion& analisis, pila_simbolos& pila) {
   if (pila.ciclos_anidados.empty( )) {
      throw error("Sentencia continue fuera de ciclos", s->vista);
   }
   analisis.ciclo_afectado[s] = pila.ciclos_anidados.back( );
}

void analiza_semantica(sentencia_exit* s, analisis_funcion& analisis, pila_simbolos& pila) {
   return;
}

tabla_simbolos semantico(const std::vector<token>& tokens, const arbol_sintactico& arbol) {
   tabla_simbolos tabla;
   for (const declaracion_funcion& decl : arbol.funciones) {
      if (tabla.funciones.contains(decl.nombre.vista)) {
         throw error("Redeclaracion de funcion", decl.nombre.vista);
      }
      analisis_funcion analisis;
      analisis.declaracion = &decl;
      analisis.retorno = analiza_como_tipo(decl.tipo, RETORNO);
      pila_simbolos pila(&tabla);
      for (const sentencia_declaracion& param : decl.parametros) {
         if (!pila.declara_variable(param.nombre.vista, &param)) {
            throw error("Redeclaracion de parametro", param.nombre.vista);
         }
         analisis.variables[&param] = analiza_como_tipo(param.tipo, PARAMETRO);
      }
      tabla.funciones[decl.nombre.vista] = analisis;
   }

   for (const declaracion_funcion& decl : arbol.funciones) {
      analisis_funcion& analisis = tabla.funciones[decl.nombre.vista];
      pila_simbolos pila(&tabla);
      for (const sentencia_declaracion& param : decl.parametros) {
         pila.declara_variable(param.nombre.vista, &param);
      }
      for (sentencia* s : decl.cuerpo) {
         analiza_semantica(s, analisis, pila);
      }
   }

   if (!tabla.funciones.contains("main") || !tipo_compatible(tabla.funciones["main"].retorno, tipo_evaluado(VOID, ESCALAR)) || !tabla.funciones["main"].declaracion->parametros.empty( )) {
      throw error("Funcion main no existe o no tiene la declaracion correcta", tokens.back( ).vista);
   }

   return tabla;
}

#endif