#include <language/PugsParser.hpp>

#include <language/PEGGrammar.hpp>
#include <language/ast/ASTBuilder.hpp>
#include <language/ast/ASTModulesImporter.hpp>
#include <language/ast/ASTNode.hpp>
#include <language/ast/ASTNodeDataTypeBuilder.hpp>
#include <language/ast/ASTNodeDataTypeChecker.hpp>
#include <language/ast/ASTNodeDeclarationToAffectationConverter.hpp>
#include <language/ast/ASTNodeEmptyBlockCleaner.hpp>
#include <language/ast/ASTNodeExpressionBuilder.hpp>
#include <language/ast/ASTNodeJumpPlacementChecker.hpp>
#include <language/ast/ASTNodeTypeCleaner.hpp>
#include <language/ast/ASTSymbolInitializationChecker.hpp>
#include <language/ast/ASTSymbolTableBuilder.hpp>
#include <language/utils/ASTDotPrinter.hpp>
#include <language/utils/ASTPrinter.hpp>
#include <language/utils/SymbolTable.hpp>
#include <utils/PugsAssert.hpp>
#include <utils/PugsUtils.hpp>
#include <utils/SignalManager.hpp>

#include <pegtl/contrib/analyze.hpp>
#include <pegtl/contrib/parse_tree.hpp>
#include <pegtl/contrib/parse_tree_to_dot.hpp>

#include <rang.hpp>

#include <fstream>
#include <iostream>
#include <sstream>
#include <unordered_map>
#include <variant>

void
parser(const std::string& filename)
{
  const size_t grammar_issues = analyze<language::grammar>();

  std::cout << rang::fgB::yellow << "grammar_issues=" << rang::fg::reset << grammar_issues << '\n';

  std::cout << rang::style::bold << "Parsing file " << rang::style::reset << rang::style::underline << filename
            << rang::style::reset << " ...\n";

  auto parse_and_execute = [](auto& input) {
    std::unique_ptr<ASTNode> root_node = ASTBuilder::build(input);

    ASTModulesImporter{*root_node};
    ASTNodeTypeCleaner<language::import_instruction>{*root_node};

    ASTSymbolTableBuilder{*root_node};

    ASTSymbolInitializationChecker{*root_node};

    ASTNodeDataTypeBuilder{*root_node};

    ASTNodeDataTypeChecker{*root_node};

    ASTNodeJumpPlacementChecker{*root_node};

    // optimizations
    ASTNodeDeclarationToAffectationConverter{*root_node};

    ASTNodeTypeCleaner<language::var_declaration>{*root_node};
    ASTNodeTypeCleaner<language::fct_declaration>{*root_node};

    {
      std::string dot_filename{"parse_tree.dot"};
      std::ofstream fout(dot_filename);
      ASTDotPrinter dot_printer{*root_node};
      fout << dot_printer;
      std::cout << "   AST dot file: " << dot_filename << '\n';
    }

    ASTNodeEmptyBlockCleaner{*root_node};

    ASTNodeExpressionBuilder{*root_node};

    std::cout << ASTPrinter{*root_node} << '\n';

    auto& function_table = root_node->m_symbol_table->functionTable();

    for (size_t i_function = 0; i_function < function_table.size(); ++i_function) {
      const auto& function_descriptor = function_table[i_function];
      std::cout << "function " << rang::fgB::magenta << function_descriptor.name() << rang::style::reset << '\n';
      std::cout << ASTPrinter(function_descriptor.domainMappingNode());
      std::cout << ASTPrinter(function_descriptor.definitionNode());
      std::cout << "--------\n";
    }

    ExecutionPolicy exec_all;
    root_node->execute(exec_all);
    std::cout << *(root_node->m_symbol_table) << '\n';

    root_node->m_symbol_table->clearValues();
  };

  if (not SignalManager::pauseOnError()) {
    read_input input(filename);
    try {
      parse_and_execute(input);
    }
    catch (const ParseError& e) {
      const auto p = e.positions().front();

      std::cerr << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ": " << rang::style::reset
                << rang::fgB::red << "error: " << rang::fg::reset << rang::style::bold << e.what() << rang::style::reset
                << '\n'
                << input.line_at(p) << '\n'
                << std::string(p.column, ' ') << rang::fgB::yellow << '^' << rang::fg::reset << '\n';
      finalize();
      std::exit(1);
    }
  } else {
    read_input input(filename);
    parse_and_execute(input);
  }
  std::cout << "Executed successfuly: " << filename << '\n';
}