#ifndef CODEGEN_H
#define CODEGEN_H

#include "codegen_aux.h"
#include "lexer.h"
#include "parser.h"
#include "semantico.h"
#include <map>
#include <sstream>
#include <string>
#include <string_view>

using tabla_dir = std::map<const sentencia_declaracion*, int>;

void emite_asignacion(expresion* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   os << "PUSH R1\n";
   tipo_evaluado t = traduce(ex, analisis, dir, os);
   if (t.modificador == ARREGLO) {
      tipo_evaluado e = tipo_evaluado(t.base, ESCALAR);
      os << "MOV R4, R1\n";
      os << "POP R1\n";
      os << "MOV R3, " << tam_memoria(e) << "\n";
      for (int i = 0; i < t.tamanyo; ++i) {
         os << "GET R2, " << ancho_acceso(e) << " [R4]\n";
         os << "PUT R2, " << ancho_acceso(e) << " [R1]\n";
         os << "ADD R1, R1, R3\n";
         os << "ADD R4, R4, R3\n";
      }
   } else {
      os << "POP R1\n";
      os << "PUT R2, " << ancho_acceso(t) << " [R1]\n";
   }
}

tipo_evaluado traduce(expresion_termino* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   if (ex->t.tipo == IDENTIFICADOR) {
      const sentencia_declaracion* s = analisis.variable_referida.find(ex)->second;
      tipo_evaluado t = analisis.variables.find(s)->second;
      os << "MOV R1, " << dir.find(s)->second << "\n";
      os << "ADD R1, R1, R0\n";
      if (t.modificador != ARREGLO) {
         os << "GET R2, " << ancho_acceso(t) << " [R1]\n";
      }
   } else if (ex->t.tipo == LITERAL_ENTERA) {
      os << "MOV R2, " << analisis.enteros.find(ex)->second << "\n";
   } else if (ex->t.tipo == LITERAL_CADENA) {
      std::string_view cad = analisis.cadenas.find(ex)->second;
      if (cad.size( ) == 1) {
         os << "MOV R2, " << int(cad[0]) << "\n";
      } else if (cad.size( ) > 1) {
         int inicio = (1 << 24) - cad.size( );
         for (int i = 0; i < cad.size( ); ++i) {
            os << "MOV R2, " << int(cad[i]) << "\n";
            os << "PUT R2, BYTE [" << inicio + i << "]\n";
         }
         os << "MOV R1, " << inicio << "\n";
      }
   }
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_binaria* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   if (ex->operador.tipo == ASIGNACION) {
      traduce(ex->izq, analisis, dir, os);
      emite_asignacion(ex->der, analisis, dir, os);
   } else if (ex->operador.tipo == MAS_ASIGNACION || ex->operador.tipo == MENOS_ASIGNACION || ex->operador.tipo == POR_ASIGNACION || ex->operador.tipo == ENTRE_ASIGNACION || ex->operador.tipo == MODULO_ASIGNACION) {
      std::string_view instr = (ex->operador.tipo == MAS_ASIGNACION ? "ADD" : (ex->operador.tipo == MENOS_ASIGNACION ? "SUB" :
                               (ex->operador.tipo == POR_ASIGNACION ? "MUL" : (ex->operador.tipo == ENTRE_ASIGNACION ? "DIV" : "MOD"))));
      tipo_evaluado t = traduce(ex->izq, analisis, dir, os);
      os << "PUSH R1\n";
      os << "PUSH R2\n";
      traduce(ex->der, analisis, dir, os);
      os << "POP R4\n";
      os << "POP R1\n";
      os << instr << " R2, R4, R2\n";
      os << "PUT R2, " << ancho_acceso(t) << " [R1]\n";
   } else if (ex->operador.tipo == OR) {
      traduce(ex->izq, analisis, dir, os);
      os << "JIF R2, " << eti(ex) << "_fin\n";
      traduce(ex->der, analisis, dir, os);
      os << eti(ex) << "_fin: NOP\n";
   } else if (ex->operador.tipo == AND) {
      traduce(ex->izq, analisis, dir, os);
      os << "JIF R2, " << eti(ex) << "_der\n";
      os << "JMP " << eti(ex) << "_fin\n";
      os << eti(ex) << "_der: NOP\n";
      traduce(ex->der, analisis, dir, os);
      os << eti(ex) << "_fin: NOP\n";
   } else {
      std::string_view instr = (ex->operador.tipo == MENOR ? "LST" : (ex->operador.tipo == MENOR_IGUAL ? "LEQ" :
                               (ex->operador.tipo == MAYOR ? "GRT" : (ex->operador.tipo == MAYOR_IGUAL ? "GEQ" :
                               (ex->operador.tipo == IGUAL ? "EQU" : (ex->operador.tipo == DIFERENTE ? "NEQ" :
                               (ex->operador.tipo == MAS ? "ADD" : (ex->operador.tipo == MENOS ? "SUB" :
                               (ex->operador.tipo == POR ? "MUL" : (ex->operador.tipo == ENTRE ? "DIV" : "MOD"))))))))));
      traduce(ex->izq, analisis, dir, os);
      os << "PUSH R2\n";
      traduce(ex->der, analisis, dir, os);
      os << "POP R4\n";
      os << instr << " R2, R4, R2\n";
   }
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_prefija* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   // tarea 7
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_posfija* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   tipo_evaluado t = traduce(ex->ex, analisis, dir, os);
   os << "MOV R1, R2\n";
   os << "GET R2, " << ancho_acceso(tipo_evaluado(t.base, ESCALAR)) << " [R1]\n";
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_llamada* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   auto p = dynamic_cast<expresion_termino*>(ex->izq);
   if (p->t.tipo == PRINT) {
      tipo_evaluado t = traduce(ex->dentro[0], analisis, dir, os);
      if (t.modificador == ESCALAR) {
         os << "SHOW " << ancho_acceso(t) << " R2\n";
      } else {
         os << "MOV R3, 1\n";
         for (int i = 0; i < t.tamanyo; ++i) {
            os << "GET R2, BYTE [R1]\n";
            os << "SHOW BYTE R2\n";
            os << "ADD R1, R1, R3\n";
         }
      }
   } else if (p->t.tipo == SCAN) {
      tipo_evaluado t = traduce(ex->dentro[0], analisis, dir, os);
      os << "ASK " << ancho_acceso(t) << " R2\n";
      os << "PUT R2, " << ancho_acceso(t) << " [R1]\n";
   } else if (p->t.tipo == IDENTIFICADOR) {
      int offset = 0;
      for (int i = 0; i < ex->dentro.size( ); ++i) {
         tipo_evaluado t = traduce(ex->dentro[i], analisis, dir, os);
         os << "MOV R3, " << offset << "\n";
         os << "ADD R1, R0, R3\n";
         os << "PUT R2, " << ancho_acceso(t) << " [R1]\n";
         offset += tam_memoria(t);
      }
      os << "CALL " << eti(analisis.funcion_referida.find(ex)->second->declaracion->nombre.vista) << "\n";
   }
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_indexacion* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   tipo_evaluado t = traduce(ex->izq, analisis, dir, os), e = tipo_evaluado(t.base, ESCALAR);
   os << "PUSH " << (t.modificador == ARREGLO ? "R1" : "R2") << "\n";
   traduce(ex->dentro, analisis, dir, os);
   os << "POP R1\n";
   os << "MOV R3, " << tam_memoria(e) << "\n";
   os << "MUL R2, R2, R3\n";
   os << "ADD R1, R1, R2\n";
   os << "GET R2, " << ancho_acceso(e) << " [R1]\n";
   return analisis.tipos.find(ex)->second;
}

tipo_evaluado traduce(expresion_secuencia* ex, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   tipo_evaluado t = analisis.tipos.find(ex)->second;
   tipo_evaluado e = tipo_evaluado(t.base, ESCALAR);
   for (int i = 0; i < ex->ex.size( ); ++i) {
      traduce(ex->ex[i], analisis, dir, os);
      os << "PUSH R2\n";
   }
   int inicio = (1 << 24) - tam_memoria(t);
   for (int i = ex->ex.size( ) - 1; i >= 0; --i) {
      os << "POP R2\n";
      os << "PUT R2, " << ancho_acceso(e) << " [" << inicio + (i * tam_memoria(e)) << "]\n";
   }
   os << "MOV R1, " << inicio << "\n";
   return analisis.tipos.find(ex)->second;
}

void traduce(sentencia_expresion* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   traduce(s->ex, analisis, dir, os);
}

void traduce(sentencia_declaracion* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   if (s->inicializador != nullptr) {
      os << "MOV R1, " << dir.find(s)->second << "\n";
      os << "ADD R1, R1, R0\n";
      emite_asignacion(s->inicializador, analisis, dir, os);
   }
}

void traduce(sentencia_if* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   // tarea 7
}

void traduce(sentencia_while* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   // tarea 7
}

void traduce(sentencia_do* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   os << eti(s) << "_inicio: NOP\n";
   for (sentencia* actual : s->cuerpo) {
      traduce(actual, analisis, dir, os);
   }
   os << eti(s) << "_continue: NOP\n";
   os << eti(s) << "_condicion: NOP\n";
   traduce(s->condicion, analisis, dir, os);
   os << "JIF R2, " << eti(s) << "_inicio\n";
   os << eti(s) << "_fin: NOP\n";
}

void traduce(sentencia_for* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   if (s->inicializacion != nullptr) {
      traduce(s->inicializacion, analisis, dir, os);
   }
   os << "JMP " << eti(s) << "_condicion\n";
   os << eti(s) << "_continue: NOP\n";
   if (s->actualizacion != nullptr) {
      traduce(s->actualizacion, analisis, dir, os);
   }
   os << eti(s) << "_condicion: NOP\n";
   if (s->condicion != nullptr) {
      traduce(s->condicion, analisis, dir, os);
      os << "JIF R2, " << eti(s) << "_cuerpo\n";
      os << "JMP " << eti(s) << "_fin\n";
   }
   os << eti(s) << "_cuerpo: NOP\n";
   for (sentencia* actual : s->cuerpo) {
      traduce(actual, analisis, dir, os);
   }
   os << "JMP " << eti(s) << "_continue\n";
   os << eti(s) << "_fin: NOP\n";
}

void traduce(sentencia_return* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   if (s->ex != nullptr) {
      traduce(s->ex, analisis, dir, os);
   }
   os << "POP R0\n";
   os << "RET\n";
}

void traduce(sentencia_break* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   os << "JMP " << eti(analisis.ciclo_afectado.find(s)->second) << "_fin\n";
}

void traduce(sentencia_continue* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   os << "JMP " << eti(analisis.ciclo_afectado.find(s)->second) << "_continue\n";
}

void traduce(sentencia_exit* s, const analisis_funcion& analisis, tabla_dir& dir, std::ostream& os) {
   os << "END\n";
}

std::string codegen(const arbol_sintactico& arbol, const tabla_simbolos& tabla) {
   std::ostringstream oss;
   oss << "CALL " << eti("main") << "\n";
   oss << "END\n\n";

   for (const declaracion_funcion& decl : arbol.funciones) {
      const analisis_funcion& analisis = tabla.funciones.find(decl.nombre.vista)->second;
      tabla_dir dir;
      oss << eti(decl.nombre.vista) << ": PUSH R0\n";
      int total_memoria = 0;
      for (const sentencia_declaracion& param : decl.parametros) {
         dir[&param] = total_memoria;
         total_memoria += tam_memoria(analisis.variables.find(&param)->second);
      }
      for (auto [s, t] : analisis.variables) {
         if (!dir.contains(s)) {
            dir[s] = total_memoria;
            total_memoria += tam_memoria(t);
         }
      }
      for (auto [s, t] : analisis.variables) {
         dir[s] -= total_memoria;
      }
      oss << "MOV R3, " << total_memoria << "\n";
      oss << "ADD R0, R0, R3\n";
      for (sentencia* s : decl.cuerpo) {
         traduce(s, analisis, dir, (std::ostream&)(oss));
      }
      oss << "POP R0\n";
      oss << "RET\n";
   }

   return oss.str( );
}

#endif
