/*
 * parser.cpp --- Seq AST parser.
 *
 * (c) Seq project. All rights reserved.
 * This file is subject to the terms and conditions defined in
 * file 'LICENSE', which is part of this source code package.
 */

#include <chrono>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

#include "parser/cache.h"
#include "parser/parser.h"
#include "parser/peg/peg.h"
#include "parser/visitors/doc/doc.h"
#include "parser/visitors/format/format.h"
#include "parser/visitors/simplify/simplify.h"
#include "parser/visitors/translate/translate.h"
#include "parser/visitors/typecheck/typecheck.h"
#include "sir/sir.h"
#include "util/fmt/format.h"

#include "sir/util/format.h"
#include <fstream>

int _ocaml_time = 0;
int _ll_time = 0;
int _level = 0;
int _dbg_level = 0;
bool _isTest = false;

namespace seq {

ir::Module *parse(const string &argv0, const string &file, const string &code,
                  bool isCode, int isTest, int startLine,
                  const std::unordered_map<std::string, std::string> &defines) {
  try {
    auto d = getenv("CODON_DEBUG");
    if (d) {
      auto s = string(d);
      _dbg_level |= s.find('t') != string::npos ? (1 << 0) : 0; // time
      _dbg_level |= s.find('r') != string::npos ? (1 << 2) : 0; // realize
      _dbg_level |= s.find('T') != string::npos ? (1 << 4) : 0; // type-check
      _dbg_level |= s.find('L') != string::npos ? (1 << 5) : 0; // lexer
      _dbg_level |= s.find('i') != string::npos ? (1 << 6) : 0; // IR
      _dbg_level |= s.find('l') != string::npos ? (1 << 7) : 0; // User-level debugging
    }

    char abs[PATH_MAX + 1] = {'-', 0};
    if (file != "-")
      realpath(file.c_str(), abs);

    auto cache = make_shared<ast::Cache>(argv0);
    ast::StmtPtr codeStmt = isCode ? ast::parseCode(cache, abs, code, startLine)
                                   : ast::parseFile(cache, abs);
    if (_dbg_level) {
      auto fo = fopen("_dump.sexp", "w");
      fmt::print(fo, "{}\n", codeStmt->toString(0));
      fclose(fo);
    }

    using namespace std::chrono;
    cache->module0 = file;
    if (isTest)
      cache->testFlags = isTest;

    auto t = high_resolution_clock::now();

    auto transformed =
        ast::SimplifyVisitor::apply(cache, move(codeStmt), abs, defines, (isTest > 1));
    if (!isTest) {
      LOG_TIME("[T] ocaml = {:.1f}", _ocaml_time / 1000.0);
      LOG_TIME("[T] simplify = {:.1f}",
               (duration_cast<milliseconds>(high_resolution_clock::now() - t).count() -
                _ocaml_time) /
                   1000.0);
      if (_dbg_level) {
        auto fo = fopen("_dump_simplify.sexp", "w");
        fmt::print(fo, "{}\n", transformed->toString(0));
        fclose(fo);
        fo = fopen("_dump_simplify.seq", "w");
        fmt::print(fo, "{}", ast::FormatVisitor::apply(transformed, cache));
        fclose(fo);
      }
    }

    t = high_resolution_clock::now();
    auto typechecked = ast::TypecheckVisitor::apply(cache, move(transformed));
    if (!isTest) {
      LOG_TIME("[T] typecheck = {:.1f}",
               duration_cast<milliseconds>(high_resolution_clock::now() - t).count() /
                   1000.0);
      if (_dbg_level) {
        auto fo = fopen("_dump_typecheck.seq", "w");
        fmt::print(fo, "{}", ast::FormatVisitor::apply(typechecked, cache));
        fclose(fo);
        fo = fopen("_dump_typecheck.sexp", "w");
        fmt::print(fo, "{}\n", typechecked->toString(0));
        for (auto &f : cache->functions)
          for (auto &r : f.second.realizations)
            fmt::print(fo, "{}\n", r.second->ast->toString(0));
        fclose(fo);
      }
    }

    t = high_resolution_clock::now();
    auto *module = ast::TranslateVisitor::apply(cache, move(typechecked));
    module->setSrcInfo({abs, 0, 0, 0});

    if (!isTest)
      LOG_TIME("[T] translate   = {:.1f}",
               duration_cast<milliseconds>(high_resolution_clock::now() - t).count() /
                   1000.0);
    if (_dbg_level) {
      auto out = seq::ir::util::format(module);
      std::ofstream os("_dump_sir.lisp");
      os << out;
      os.close();
      os.close();
    }

    _isTest = isTest;
    return module;
  } catch (exc::ParserException &e) {
    for (int i = 0; i < e.messages.size(); i++)
      if (!e.messages[i].empty()) {
        if (isTest) {
          _level = 0;
          LOG("{}", e.messages[i]);
        } else {
          compilationError(e.messages[i], e.locations[i].file, e.locations[i].line,
                           e.locations[i].col, /*terminate=*/false);
        }
      }
    return nullptr;
  }
}

void generateDocstr(const string &argv0) {
  vector<string> files;
  string s;
  while (std::getline(std::cin, s))
    files.push_back(s);
  try {
    auto j = ast::DocVisitor::apply(argv0, files);
    fmt::print("{}\n", j->toString());
  } catch (exc::ParserException &e) {
    for (int i = 0; i < e.messages.size(); i++)
      if (!e.messages[i].empty()) {
        compilationError(e.messages[i], e.locations[i].file, e.locations[i].line,
                         e.locations[i].col, /*terminate=*/false);
      }
  }
}

} // namespace seq