#ifndef DEBUG_H
#define DEBUG_H

#include "lexer.h"
#include "parser.h"
#include <iostream>

std::ostream& operator<<(std::ostream& os, const token& t) {
   return os << t.tipo << " " << t.vista;
}

std::ostream& operator<<(std::ostream& os, expresion* ex);

std::ostream& operator<<(std::ostream& os, const std::vector<expresion*>& lista) {
   for (int i = 0; i < lista.size( ); ++i) {
      os << lista[i] << (i + 1 < lista.size( ) ? "," : "");
   }
   return os;
}

std::ostream& operator<<(std::ostream& os, expresion* ex) {
   if (auto p = dynamic_cast<expresion_termino*>(ex); p != nullptr) {
      return os << p->t.vista;
   } else if (auto p = dynamic_cast<expresion_binaria*>(ex); p != nullptr) {
      return os << "(" << p->izq << p->operador.vista << p->der << ")";
   } else if (auto p = dynamic_cast<expresion_prefija*>(ex); p != nullptr) {
      return os << p->operador.vista << "(" << p->ex << ")";
   } else if (auto p = dynamic_cast<expresion_posfija*>(ex); p != nullptr) {
      return os << p->ex << p->operador.vista;
   } else if (auto p = dynamic_cast<expresion_llamada*>(ex); p != nullptr) {
      return os << p->izq << "(" << p->dentro << ")";
   } else if (auto p = dynamic_cast<expresion_indexacion*>(ex); p != nullptr) {
      return os << p->izq << "[" << p->dentro << "]";
   } else if (auto p = dynamic_cast<expresion_secuencia*>(ex); p != nullptr) {
      return os << "[" << p->ex << "]";
   } else {
      return os;
   }
}

std::ostream& operator<<(std::ostream& os, sentencia* ex);

std::ostream& operator<<(std::ostream& os, const std::vector<sentencia*>& lista) {
   for (int i = 0; i < lista.size( ); ++i) {
      os << lista[i] << "\n";
   }
   return os;
}

std::ostream& operator<<(std::ostream& os, sentencia_declaracion* s) {
   if (s != nullptr) {
      os << s->tipo << " " << s->nombre.vista << (s->inicializador != nullptr ? " = " : "") << s->inicializador;
   }
   return os;
};
std::ostream& operator<<(std::ostream& os, sentencia* s) {
   if (auto p = dynamic_cast<sentencia_expresion*>(s); p != nullptr) {
      return os << p->ex << ";";
   } else if (auto p = dynamic_cast<sentencia_declaracion*>(s); p != nullptr) {
      return os << p << ";";
   } else if (auto p = dynamic_cast<sentencia_if*>(s); p != nullptr) {
      return os << "if " << p->condicion << "{\n" << p->cuerpo_si << "} else {\n" << p->cuerpo_no << "}";
   } else if (auto p = dynamic_cast<sentencia_while*>(s); p != nullptr) {
      return os << "while " << p->condicion << "{\n" << p->cuerpo << "}";
   } else if (auto p = dynamic_cast<sentencia_do*>(s); p != nullptr) {
      return os << "do " << "{\n" << p->cuerpo << "} while " << p->condicion << ";";
   } else if (auto p = dynamic_cast<sentencia_for*>(s); p != nullptr) {
      return os << "for " << p->inicializacion << ";" << p->condicion << ";" << p->actualizacion << "{\n" << p->cuerpo << "}";
   } else if (auto p = dynamic_cast<sentencia_return*>(s); p != nullptr) {
      return os << "return " << p->ex << ";";
   } else if (auto p = dynamic_cast<sentencia_break*>(s); p != nullptr) {
      return os << "break;";
   } else if (auto p = dynamic_cast<sentencia_continue*>(s); p != nullptr) {
      return os << "continue;";
   } else if (auto p = dynamic_cast<sentencia_exit*>(s); p != nullptr) {
      return os << "exit;";
   } else {
      return os;
   }
}

std::ostream& operator<<(std::ostream& os, const declaracion_funcion& f) {
   os << f.tipo << " " << f.nombre.vista << "(";
   for (int i = 0; i < f.parametros.size( ); ++i) {
      os << f.parametros[i].tipo << " " << f.parametros[i].nombre.vista << (i + 1 < f.parametros.size( ) ? "," : "");
   }
   os << ") {\n";
   for (const auto& s : f.cuerpo) {
      os << s << "\n";
   }
   os << "}";
   return os;
}

std::ostream& operator<<(std::ostream& os, const arbol_sintactico& arbol) {
   for (const declaracion_funcion& actual : arbol.funciones) {
      os << actual << "\n";
   }
   return os;
}

#endif
