/*
@todo: popup when error
@todo: pdf files
@todo: help menu
@todo: check for number of cores before creating threads
*/

#define BASIC_IMPL
#include "../basic/basic.h"
#include "../basic/filesystem.h"
#include "../basic/thread_queue.h"

#include <mutex>
#include <stdio.h>

#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_opengl3.h"
#include "SDL3/SDL.h"
#include "glad.h"

struct FileLoadResult {
    String filename;
    String error;
};

Arena                 Perm;
Array<FileLoadResult> XFileLoadResults;

#include "config.cpp"
#include <pdfio.h>
#include "read_pdf.cpp"
#include "read_srt.cpp"

WorkQueue MainWorkQueue;

#include "loading_thread.cpp"
#include "searching_thread.cpp"

void UISearchResults(Array<String> filenames) {
    Scratch       scratch;
    Array<String> matches = LockSearchResults();
    defer { UnlockSearchResults(); };

    float                font_size     = ImGui::GetFontSize();
    ImFont              *font          = ImGui::GetFont();
    const ImGuiViewport *main_viewport = ImGui::GetMainViewport();

    int64_t chars_per_line = (int64_t)(main_viewport->WorkSize.x / 2) / (int64_t)font->GetCharAdvance('_') - strlen(Prompt) * 2;

    ImGuiListClipper clipper;
    clipper.Begin((int)matches.len);
    while (clipper.Step()) {
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
            ImGui::PushID(i);
            defer { ImGui::PopID(); };
            auto     &it           = matches[i];
            uintptr_t begin_region = (uintptr_t)XArena.data;
            uintptr_t end_region   = (uintptr_t)XArena.data + XArena.len;

            uint64_t begin = (uintptr_t)(it.data - chars_per_line / 2);
            uint64_t end   = (uintptr_t)(it.data + it.len + chars_per_line / 2);

            uint64_t a    = Clamp(begin, begin_region, end_region);
            uint64_t b    = Clamp((uintptr_t)it.data, begin_region, end_region);
            String   left = {(char *)a, (int64_t)(b - a)};

            uint64_t c     = Clamp((uintptr_t)(it.data + it.len), begin_region, end_region);
            uint64_t d     = Clamp(end, begin_region, end_region);
            String   right = {(char *)c, (int64_t)(d - c)};

            String middle = it;
            String string = Format(scratch, "%.*s**%.*s**%.*s",
                                   FmtString(left), FmtString(middle), FmtString(right));

            XToSource *item = XFindSource(middle);
            if (ImGui::Button(string.data)) {
                String exe_folder              = GetExeDir(scratch);
                exe_folder                     = Format(scratch, "\"%.*s\"", FmtString(exe_folder));
                Array<ReplaceVar> replace_vars = {scratch};
                replace_vars.add({"exe_folder", GetExeDir(scratch)});

                if (item->kind == SourceKind_SRT) {
                    String it = FindVideoForSRT(filenames, item->filepath);
                    if (it.len) {
                        int    seconds = item->srt.hour * 60 * 60 + item->srt.minute * 60 + item->srt.second;
                        String copy    = Copy(scratch, it);
                        for (int i = 0; i < copy.len; i += 1)
                            if (copy.data[i] == '/') copy.data[i] = '\\';

                        replace_vars.add({"video", Format(scratch, "\"%.*s\"", FmtString(copy))});
                        replace_vars.add({"time_in_seconds", Format(scratch, "%d", seconds)});
                        String  args    = ReplaceVars(scratch, replace_vars, SRTCommand);
                        Process process = RunEx(args);
                        if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message});
                    }
                } else if (item->kind == SourceKind_TXT) {
                    replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))});
                    replace_vars.add({"line", Format(scratch, "%d", item->txt.row + 1)});
                    String  args    = ReplaceVars(scratch, replace_vars, TXTCommand);
                    Process process = RunEx(args);
                    if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message});
                } else if (item->kind == SourceKind_PDF) {
                    replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))});
                    replace_vars.add({"page", Format(scratch, "%d", item->pdf.page)});
                    String  args    = ReplaceVars(scratch, replace_vars, PDFCommand);
                    Process process = RunEx(args);
                    if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message});
                } else {
                    Assert(!"Invalid codepath");
                }
            }
            ImGui::SameLine();
            ImGui::Text(SkipToLastSlash(item->filepath).data);
        }
    }
}

void UILoadedFiles() {
    Scratch               scratch;
    Array<FileLoadResult> file_load_results = XLockFileLoadResults();
    defer { XUnlockFileResults(); };

    ImGuiListClipper clipper;
    clipper.Begin((int)file_load_results.len);
    while (clipper.Step()) {
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
            FileLoadResult file = file_load_results[file_load_results.len - 1 - i];
            if (file.error.len) {
                ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)ImColor::HSV(7.0f, 0.6f, 0.6f));
                String string = Format(scratch, "%.*s error: %.*s", FmtString(file.filename), FmtString(file.error));
                ImGui::Text(string.data);
                ImGui::PopStyleColor();
            } else {
                String string = Format(scratch, "%.*s loaded", FmtString(file.filename));
                ImGui::Text(string.data);
            }
        }
    }
}

int EntryPoint() {
    InitOS();
    InitScratch();
    InitArena(&Perm);
    XInitLoading();
    InitSearch();
    LoadConfig();
    ThreadStartupInfo infos[16] = {};
    InitWorkQueue(&MainWorkQueue, Lengthof(infos), infos);

    Array<String> filenames = {};

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) != 0) {
        printf("Error: %s\n", SDL_GetError());
        return -1;
    }

    const char *glsl_version = "#version 150";
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
    SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
    SDL_Window     *window       = SDL_CreateWindow("Transcript browser", 960, 600, window_flags);
    if (window == nullptr) {
        printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError());
        return -1;
    }

    SDL_GLContext gl_context = SDL_GL_CreateContext(window);
    SDL_GL_MakeCurrent(window, gl_context);

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
        return -1;
    }

    SDL_GL_SetSwapInterval(1); // Enable vsync

    // Setup Dear ImGui context
    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO &io    = ImGui::GetIO();
    io.IniFilename = NULL;
    io.LogFilename = NULL;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;

    // Setup Dear ImGui style
    // ImGui::StyleColorsDark();
    ImGui::StyleColorsLight();

    // Setup Platform/Renderer backends
    ImGui_ImplSDL3_InitForOpenGL(window, gl_context);
    ImGui_ImplOpenGL3_Init(glsl_version);
    io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);

    bool deffered_read_on_start = false;
    {
        String read_on_start   = ReadOnStart;
        deffered_read_on_start = (read_on_start != "-");
    }

    enum MenuOpen {
        MenuOpen_Query,
        MenuOpen_LoadedFiles,
        MenuOpen_Config,
        MenuOpen_Count,
    };
    MenuOpen menu_open = MenuOpen_Query;

    bool    set_focus_to_input = true;
    int64_t frame              = -1;

    // Main loop
    bool done = false;
    while (!done) {
        Scratch frame_arena;
        frame += 1;
        if (frame > 4) set_focus_to_input = false;

        bool      set_focus_to_list = false;
        bool      tab_press         = false;
        bool      enter_press       = false;
        bool      f1_press          = false;
        bool      f2_press          = false;
        SDL_Event event;
        SDL_Event input_event_saved;
#if 1
        while (SDL_PollEvent(&event)) {
#else
        if (SDL_WaitEvent(&event)) {
#endif
            ImGui_ImplSDL3_ProcessEvent(&event);
            if (event.type == SDL_EVENT_KEY_DOWN) {
                switch (event.key.key) {
                case SDLK_UP:
                case SDLK_DOWN:
                case SDLK_PAGEUP:
                case SDLK_PAGEDOWN:
                    set_focus_to_list = true;
                    break;
                case SDLK_BACKSPACE:
                case SDLK_DELETE:
                    set_focus_to_input = true;
                    input_event_saved  = event;
                    break;
                case SDLK_RETURN:
                    enter_press = true;
                    break;
                case SDLK_TAB:
                    tab_press = true;
                    break;
                case SDLK_F1:
                    f1_press = true;
                    break;
                case SDLK_F2:
                    f2_press = true;
                    break;
                }
            } else if (event.type == SDL_EVENT_TEXT_INPUT) {
                set_focus_to_input = true;
                input_event_saved  = event;
            } else if (event.type == SDL_EVENT_QUIT) {
                done = true;
            } else if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) {
                done = true;
            }
        }

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplSDL3_NewFrame();
        ImGui::NewFrame();

#if 0
        ImGui::ShowDemoWindow();
#else

        // Query input window
        {

            ImGuiWindowFlags     window_flags  = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
            const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
            ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
            ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, 40), ImGuiCond_Always);

            ImGui::Begin("input", NULL, window_flags);

            if (menu_open == MenuOpen_LoadedFiles) {
                if (ImGui::Button("Hide files (F1)") || f1_press) {
                    menu_open = MenuOpen_Query;
                }
            } else {
                if (ImGui::Button("Show files (F1)") || f1_press) {
                    menu_open = MenuOpen_LoadedFiles;
                }
            }
            ImGui::SameLine();

            if (menu_open == MenuOpen_Config) {
                if (ImGui::Button("Hide config (F2)") || f2_press) {
                    menu_open = MenuOpen_Query;
                }
            } else {
                if (ImGui::Button("Show config (F2)") || f2_press) {
                    menu_open = MenuOpen_Config;
                }
            }

            ImGui::SameLine();
            if (menu_open == MenuOpen_Query && set_focus_to_input) {
                ImGui::SetKeyboardFocusHere(0);
            }
            if (tab_press && ImGui::IsWindowFocused()) set_focus_to_list = true;
            if (ImGui::InputText("Input your query", Prompt, sizeof(Prompt))) {
                StartSearchingForMatches();
            }

            ImGui::End();

            if (enter_press) {
                String prompt = Prompt;
                if (StartsWith(prompt, "read=")) {
                    XAddFolder(prompt.skip(5), &filenames);
                    memset(Prompt, 0, sizeof(Prompt));
                }
            }
            if (frame > 2 && deffered_read_on_start) {
                deffered_read_on_start = false;
                Scratch       scratch;
                String        read_on_start = ReadOnStart;
                Array<String> strings       = Split(scratch, read_on_start, ";");
                For(strings) XAddFolder(it, &filenames);
            }
        }

        //
        {
            ImGuiWindowFlags     window_flags  = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse;
            const ImGuiViewport *main_viewport = ImGui::GetMainViewport();
            ImGui::SetNextWindowPos(ImVec2(0, 35), ImGuiCond_Always);
            ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, main_viewport->Size.y - 35), ImGuiCond_Always);

            ImGui::Begin("result", NULL, window_flags);
            if (!ImGui::IsWindowFocused() && set_focus_to_list) ImGui::SetKeyboardFocusHere(0);

            if (menu_open == MenuOpen_Config) {
                ImGui::InputText(".srt", SRTCommand, sizeof(SRTCommand));
                ImGui::InputText(".pdf", PDFCommand, sizeof(PDFCommand));
                ImGui::InputText(".txt", TXTCommand, sizeof(TXTCommand));
                ImGui::InputText("Folders to read during startup", ReadOnStart, sizeof(ReadOnStart));
                if (ImGui::Button("Save config")) {
                    SaveConfig();
                    LoadConfig();
                }
            } else if (menu_open == MenuOpen_LoadedFiles) {
                UILoadedFiles();
            } else {
                UISearchResults(filenames);
            }

            ImGui::End();
        }
#endif

        ImGui::Render();
        glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
        glClearColor(1, 1, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
        SDL_GL_SwapWindow(window);
        SDL_Delay(16);
    }

    // Cleanup
    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplSDL3_Shutdown();
    ImGui::DestroyContext();

    SDL_GL_DestroyContext(gl_context);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

#if _WIN32
int WinMain(void *hInstance, void *hPrevInstance, const char *cmd, int show_cmd) {
    return EntryPoint();
}
#else
int main() {
    return EntryPoint();
}
#endif
