#include <fstream>
#include <ncurses.h>
#include <sstream>
#include "../src/Parser/Lexer/Lexer.hpp"
#include "../src/Parser/Parser.hpp"
#include "../src/Parser/Statements/ParsedStatementStringifier.hpp"
#include "InputWindow.hpp"

void ParseAndUpdate(const std::vector<std::u8string>& lines, WINDOW* diagnosticsWindow, WINDOW* parsedWindow,
                    MalachScriptRepl::InputWindow& inputWindow) {
    std::u8string script;
    for (size_t i = 0; i < lines.size(); i++) {
        script += lines[i];
        if (i != lines.size() - 1) {
            script += '\n';
        }
    }

    std::ofstream outfile;
    outfile.open("script.ms", std::ios::out | std::ios::trunc);
    outfile << (char*)script.data();
    outfile.close();

    auto logger = MalachScript::Diagnostics::Logger();
    auto lexer = MalachScript::Parser::Lexer(u8"diag", script, &logger);
    const auto* firstToken = lexer.Lex();
    const auto* parsedResult = MalachScript::Parser::Parser::Parse(firstToken, u8"diag", &logger);

    const MalachScript::Diagnostics::Diagnostic* diag = nullptr;
    wclear(diagnosticsWindow);

    if (!logger.GetMessages().empty()) {
        diag = &logger.GetMessages()[0];

        wattron(diagnosticsWindow, COLOR_PAIR(1));
        waddch(diagnosticsWindow, '[');
        waddstr(diagnosticsWindow, "Error");
        waddch(diagnosticsWindow, ']');
        wattroff(diagnosticsWindow, COLOR_PAIR(1));
        waddch(diagnosticsWindow, ' ');
        waddstr(diagnosticsWindow, std::to_string(diag->GetSpan().GetStart() + 1).c_str());
        waddch(diagnosticsWindow, '-');
        waddstr(diagnosticsWindow, std::to_string(diag->GetSpan().GetEnd() + 1).c_str());
        waddch(diagnosticsWindow, ' ');

        waddstr(diagnosticsWindow,
                MalachScript::Diagnostics::DiagnosticTypeHelper::ToEnglishString(diag->GetType()).c_str());

        waddch(diagnosticsWindow, '\n');
    }
    wrefresh(diagnosticsWindow);

    wclear(parsedWindow);
    std::stringstream ss;
    MalachScript::Parser::ParsedStatementStringifier::Stringify(parsedResult, ss, "", true);

    waddstr(parsedWindow, ss.str().c_str());
    wrefresh(parsedWindow);

    inputWindow.SetScriptWithDiagnostics(script, diag);
    delete parsedResult;
}

int main([[maybe_unused]] int argc, [[maybe_unused]] const char* argv[]) {
    setlocale(LC_ALL, "");
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, true);
    set_tabsize(4);

    start_color();
    init_pair(1, COLOR_RED, COLOR_BLACK);
    clear();

    int maxX;
    int maxY;
    getmaxyx(stdscr, maxY, maxX);
    wresize(stdscr, maxY, maxX);
    wrefresh(stdscr);

    const int inputFieldSize = 30;

    wborder(stdscr, 0, 0, 0, 0, 0, 0, 0, 0);
    wmove(stdscr, 31, 1);
    whline(stdscr, ACS_HLINE, maxX - 2);
    wmove(stdscr, 32, maxX / 2);
    wvline(stdscr, ACS_VLINE, 25);

    wrefresh(stdscr);

    [[maybe_unused]] auto* diagnosticsWindow = newwin(25, (maxX - 4) / 2, 32, 2);

    auto* parsedResultWindow = newwin(25, (maxX - 4) / 2 - 2, 32, (maxX - 4) / 2 + 4);
    wrefresh(parsedResultWindow);

    auto inputWindow = MalachScriptRepl::InputWindow(inputFieldSize, maxX - 3, 1, 2);
    inputWindow.RegisterOnChange(
        [diagnosticsWindow, parsedResultWindow, &inputWindow](const std::vector<std::u8string>& lines) {
            ParseAndUpdate(lines, diagnosticsWindow, parsedResultWindow, inputWindow);
        });

    int c;
    bool running = true;
    while (running) {
        c = inputWindow.GetInput();
        switch (c) {
            case 27: running = false; break;

            case KEY_LEFT: inputWindow.MoveCursorLeft(); break;
            case KEY_RIGHT: inputWindow.MoveCursorRight(); break;
            case KEY_UP: inputWindow.MoveCursorUp(); break;
            case KEY_DOWN: inputWindow.MoveCursorDown(); break;
            case KEY_BACKSPACE:
            case 127: inputWindow.Backspace(); break;
            case 10:
            case KEY_ENTER: inputWindow.Return(); break;
            case 9:
            case KEY_STAB: inputWindow.Tab(); break;
            case KEY_END: inputWindow.GoToEndOfLine(); break;
            case KEY_HOME: inputWindow.GoToStartOfLine(); break;
            default: inputWindow.Input(c); break;
        }
        inputWindow.Refresh();
    }
    endwin();

    return EXIT_SUCCESS;
}