#include <language/PugsParser.hpp>

#include <language/PEGGrammar.hpp>
#include <language/ast/ASTBacktrace.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/ASTExecutionInfo.hpp>
#include <language/utils/ASTPrinter.hpp>
#include <language/utils/Exit.hpp>
#include <language/utils/OperatorRepository.hpp>
#include <language/utils/SymbolTable.hpp>
#include <utils/ConsoleManager.hpp>
#include <utils/ExecutionStatManager.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 <memory>
#include <sstream>
#include <unordered_map>
#include <variant>

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

  if (grammar_issues != 0) {
    std::ostringstream os;
    os << "invalid grammar: " << rang::fgB::yellow << grammar_issues << rang::fg::reset << " were detected!";
    throw UnexpectedError(os.str());
  }

  if (ConsoleManager::showPreamble()) {
    std::cout << rang::style::bold << "Parsing file " << rang::style::reset << rang::style::underline << filename
              << rang::style::reset << " ...\n";
  }

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

    ASTModulesImporter module_importer{*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};

    ASTNodeEmptyBlockCleaner{*root_node};

    ASTNodeExpressionBuilder{*root_node};

    if (ConsoleManager::showPreamble()) {
      std::cout << "-------------------------------------------------------\n";
      std::cout << rang::style::bold << "Executing AST..." << rang::style::reset << '\n';
    }

    ASTExecutionInfo execution_info{*root_node, module_importer.moduleRepository()};

    ExecutionPolicy exec_all;
    try {
      root_node->execute(exec_all);
    }
    catch (language::Exit& e) {
      ExecutionStatManager::getInstance().setExitCode(e.code());
    }
    root_node->m_symbol_table->clearValues();

    OperatorRepository::destroy();
  };

  auto input = std::make_shared<TAO_PEGTL_NAMESPACE::file_input<>>(filename);
  if (not SignalManager::pauseOnError()) {
    try {
      parse_and_execute(input);
    }
    catch (const ParseError& e) {
      const auto p = e.positions().front();

      std::string error_specifier = [] {
        std::ostringstream os;
        os << rang::style::bold << "error:" << rang::style::reset << ' ';
        return os.str();
      }();

      std::string message = e.what();

      // This is a cosmetic hack to avoid repetition of "error: " in
      // output
      if (message.substr(0, error_specifier.size()) == error_specifier) {
        message = message.substr(error_specifier.size());
      }

      std::cerr << rang::style::bold << p.source << ':' << p.line << ':' << p.column << ": " << rang::style::reset
                << rang::fgB::red << "error: " << rang::fg::reset << rang::style::bold << message << rang::style::reset
                << '\n'
                << input->line_at(p) << '\n'
                << std::string(p.column - 1, ' ') << rang::fgB::yellow << '^' << rang::fg::reset << '\n';
      finalize();
      std::exit(1);
    }
    catch (const IExitError& e) {
      // Each failing process must write
      std::cerr.clear();
      std::cerr << ASTBacktrace::getInstance().errorMessageAt(e.what()) << '\n';
      std::exit(1);
    }
  } else {
    parse_and_execute(input);
  }
}