#include "nasal.h"
#include "nasal_type.h"
#include "nasal_gc.h"
#include "nasal_err.h"
#include "nasal_lexer.h"
#include "nasal_ast.h"
#include "nasal_parse.h"
#include "nasal_import.h"
#include "ast_visitor.h"
#include "ast_dumper.h"
#include "symbol_finder.h"
#include "optimizer.h"
#include "nasal_codegen.h"
#include "nasal_vm.h"
#include "nasal_dbg.h"

#include "util/util.h"
#include "repl/repl.h"
#include "cli/cli.h"

#include <thread>
#include <cstdlib>

std::ostream& logo(std::ostream& out) {
    out
    << "\n"
    << "       __                _\n"
    << "    /\\ \\ \\__ _ ___  __ _| |\n"
    << "   /  \\/ / _` / __|/ _` | |\n"
    << "  / /\\  / (_| \\__ \\ (_| | |\n"
    << "  \\_\\ \\/ \\__,_|___/\\__,_|_|\n"
    << "\n"
    << "ver  : " << __nasver__
    << " " << nasal::util::get_platform()
    << " " << nasal::util::get_arch()
    << " (" << __DATE__ << " " << __TIME__ << ")\n"
    << "std  : c++ " << __cplusplus << "\n"
    << "core : " << std::thread::hardware_concurrency() << " core(s)\n"
    << "repo : https://github.com/ValKmjolnir/Nasal-Interpreter\n"
    << "repo : https://gitee.com/valkmjolnir/Nasal-Interpreter\n"
    << "wiki : https://wiki.flightgear.org/Nasal_scripting_language\n"
    << "\n"
    << "presented by fgprc members:\n"
    << " - http://fgprc.org\n"
    << " - http://fgprc.org.cn\n"
    << "\n"
    << "input <nasal -h> to get help.\n\n";
    return out;
}

std::ostream& version(std::ostream& out) {
    std::srand(static_cast<u32>(std::time(nullptr)));

    f64 num = 0;
    for(u32 i = 0; i<5; ++i) {
        num = (num+rand())*(1.0/(RAND_MAX+1.0));
    }
    // give you 5% to see this easter egg
    if (num<0.05) {
        nasal::parse::easter_egg();
    }

    out << "nasal version " << __nasver__;
    out << " " << nasal::util::get_platform();
    out << " " << nasal::util::get_arch();
    out << " (" << __DATE__ << " " << __TIME__ << ")\n";
    return out;
}

[[noreturn]]
void err() {
    std::cerr << "invalid argument(s), use <nasal -h> to get help.\n";
    std::exit(1);
}

void execute(const nasal::cli::cli_config& config) {
    using option = nasal::cli::option;
    using clk = std::chrono::high_resolution_clock;
    const auto den = clk::duration::period::den;

    nasal::lexer   lex;
    nasal::parse   parse;
    nasal::linker  ld;
    nasal::codegen gen;

    // lexer scans file to get tokens
    lex.scan(config.input_file_path).chkerr();

    // parser gets lexer's token list to compile
    parse.compile(lex).chkerr();
    if (config.has(option::cli_view_raw_ast)) {
        nasal::ast_dumper().dump(parse.tree());
    }

    // linker gets parser's ast and load import files to this ast
    ld.link(parse, config.has(option::cli_detail_info)).chkerr();
    if (config.has(option::cli_show_referenced_file)) {
        if (ld.get_file_list().size()) {
            std::cout << "referenced file(s):\n";
        }
        for(const auto& file: ld.get_file_list()) {
            std::cout << "  " << file << "\n";
        }
    }
    
    // optimizer does simple optimization on ast
    auto opt = std::make_unique<nasal::optimizer>();
    opt->do_optimization(parse.tree());
    if (config.has(option::cli_view_ast)) {
        nasal::ast_dumper().dump(parse.tree());
    }

    // code generator gets parser's ast and import file list to generate code
    gen.compile(parse, ld, false, config.has(option::cli_limit_mode)).chkerr();
    if (config.has(option::cli_view_code)) {
        gen.print(std::cout);
    }
    if (config.has(option::cli_show_symbol)) {
        gen.symbol_dump(std::cout);
    }

    // run
    const auto start = clk::now();
    if (config.has(option::cli_debug_mode)) {
        auto debugger = std::make_unique<nasal::dbg>();
        debugger->run(
            gen,
            ld,
            config.nasal_vm_args,
            config.has(option::cli_profile),
            config.has(option::cli_profile_all)
        );
    } else if (config.has(option::cli_show_execute_time) ||
               config.has(option::cli_detail_info) ||
               config.has(option::cli_limit_mode) ||
               config.has(option::cli_execute)) {
        auto runtime = std::make_unique<nasal::vm>();
        runtime->set_detail_report_info(config.has(option::cli_detail_info));
        runtime->set_limit_mode_flag(config.has(option::cli_limit_mode));
        runtime->run(gen, ld, config.nasal_vm_args);
    }

    // get running time
    const auto end = clk::now();
    if (config.has(option::cli_show_execute_time)) {
        std::clog << "process exited after ";
        std::clog << static_cast<f64>((end-start).count())/den << "s.\n\n";
    }
}

i32 main(i32 argc, const char* argv[]) {
    // output version info
    if (argc<=1) {
        std::clog << logo;
        return 0;
    }

    // the first argument is the executable itself, ignore it
    const auto config = nasal::cli::parse({argv+1, argv+argc});

    // run directly or show help
    if (argc==2) {
        if (config.has(nasal::cli::option::cli_help)) {
            std::clog << nasal::cli::help;
        } else if (config.has(nasal::cli::option::cli_version)) {
            std::clog << version;
        } else if (config.has(nasal::cli::option::cli_repl_mode)) {
            auto repl = std::make_unique<nasal::repl::repl>();
            repl->execute();
        } else if (config.input_file_path.size()) {
            execute(config);
        } else {
            err();
        }
        return 0;
    }

    // execute with arguments
    execute(config);
    return 0;
}