#include "basic/basic.h"
#include "basic/basic.cpp"
#include "profiler.h"

#include "SDL3/SDL.h"
#include "external/glad/glad.c"
#include "external/glad/glad.h"
#include "external/stb_truetype.h"
#include "external/stb_truetype.c"
#define MINICORO_IMPL
#include "external/minicoro.h"
#define LUA_USE_LONGJMP
#include "external/luaunity.c"

#include "render/generated_font.cpp"
#include "render/font.cpp"
#include "render/opengl.cpp"

#include "buffer.h"
#include "view.h"
#include "window.h"
#include "text_editor.h"
#include "lua.h"

#include "globals.cpp"
#include "lua.cpp"
#include "buffer.cpp"
#include "view.cpp"
#include "window.cpp"
#include "management.cpp"
#include "process.cpp"
#include "event.cpp"

#include "lua_api.cpp"
#include "commands.cpp"
#include "commands_clipboard.cpp"
#include "commands_bindings.cpp"
#include "title_bar.cpp"

#include "lua_api_generated.cpp"
#include "generated_config.cpp"

#include "draw.cpp"
#include "coroutines.cpp"
#include "test/tests.cpp"


#if OS_WASM
EM_JS(void, JS_SetMouseCursor, (const char *cursor_str), {
    document.getElementById("canvas").style.cursor = UTF8ToString(cursor_str);
})

void SetMouseCursor(SDL_SystemCursor id) {
    if (id == SDL_SYSTEM_CURSOR_EW_RESIZE) {
        JS_SetMouseCursor("ew-resize");
    } else if (id == SDL_SYSTEM_CURSOR_NS_RESIZE) {
        JS_SetMouseCursor("ns-resize");
    } else if (id == SDL_SYSTEM_CURSOR_DEFAULT) {
        JS_SetMouseCursor("default");
    } else if (id == SDL_SYSTEM_CURSOR_TEXT) {
        JS_SetMouseCursor("text");
    } else {
        InvalidCodepath();
    }
}
#else
void SetMouseCursor(SDL_SystemCursor id) {
    static SDL_Cursor *SDL_MouseCursor;
    static SDL_SystemCursor last_id;

    if (SDL_MouseCursor == NULL || last_id != id) {
        if (SDL_MouseCursor != NULL) {
            SDL_DestroyCursor(SDL_MouseCursor);
        }
        SDL_MouseCursor = SDL_CreateSystemCursor(id);
        SDL_SetCursor(SDL_MouseCursor);
        last_id = id;
    }
}
#endif

void SetMouseCursor(Event event) {
    Scratch scratch;
    Array<Window *> order = GetWindowZOrder(scratch);
    Vec2I mouse = MouseVec2I();

    if (ResizerSelected.id != -1) {
        Window *window = GetWindow(ResizerSelected);
        if (window->layout) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
        } else {
            SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
        }
        return;
    }

    if (ResizerHover.id != -1) {
        Window *window = GetWindow(ResizerHover);
        if (window->layout) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_EW_RESIZE);
        } else {
            SetMouseCursor(SDL_SYSTEM_CURSOR_NS_RESIZE);
        }
        return;
    }

    For(order) {
        if (!it->visible) continue;
        bool mouse_in_total     = AreOverlapping(mouse, it->total_rect);
        bool mouse_in_scrollbar = AreOverlapping(mouse, it->scrollbar_rect);

        if (!IsDocumentSelectionValid() && (mouse_in_scrollbar || IsScrollbarSelectionValid())) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
            return;
        } else if (mouse_in_total || IsDocumentSelectionValid()) {
            SetMouseCursor(SDL_SYSTEM_CURSOR_TEXT);
            return;
        }
    }



    SetMouseCursor(SDL_SYSTEM_CURSOR_DEFAULT);
}

void Update(Event event) {
    LayoutWindows(event.xwindow, event.ywindow);

    Scratch    scratch;
    Array<Window *> order = GetWindowZOrder(scratch);
    For(order) {
        if (!it->visible) continue;
        View *view                      = GetView(it->active_view);
        view->main_caret_on_begin_frame = view->carets[0];
        view->update_scroll             = true;
    }

    OnCommand(event);
    UpdateProcesses();
    CoUpdate(&event);
    ReloadLuaConfigs();
    StatusBarUpdate();
    UpdateDebugBuffer();
    CallLuaOnUpdate(&event);
    GarbageCollect();

    For(IterateInReverse(&order)) {
        if (!it->visible) continue;

        View *view = GetView(it->active_view);
        UpdateScroll(it, !AreEqual(view->main_caret_on_begin_frame, view->carets[0]) && view->update_scroll);
    }
}

void Windows_SetupVCVarsall(mco_coro *co) {
    View *view = NULL;
    {
        Scratch scratch;
        String working_dir = WorkDir;
        String buffer_name = GetUniqueBufferName(working_dir, "vcvarsall-");
        String cmd = Format(scratch, "\"%S\" && set", StyleVCVarsall);
        view = ExecHidden(buffer_name, cmd, working_dir);
    }
    for (;;) {
        if (!ProcessIsActive(view->id)) {
            break;
        }
        CoYield(co);
    }

    Scratch scratch;
    Buffer *buffer = GetBuffer(view->active_buffer);
    String16 string16 = GetString(buffer);
    String string = ToString(scratch, string16);
    Array<String> lines = Split(scratch, string, "\n");
    For (lines) {
        String s = Trim(it);
        Array<String> values = Split(scratch, s, "=");
        if (values.len == 1) continue;
        Add(&Enviroment, Copy(GetSystemAllocator(), s));
    }
}

void MainLoop() {
    FrameID += 1;

    Scratch      scratch;
    Array<Event> frame_events = GetEventsForFrame(scratch);
    Serializer ser = {EventBuffer};
    For(frame_events) {
        if (it.kind != 1) {
            if (!Testing) Serialize(&ser, &it);
        }
        if (it.kind == EVENT_QUIT) {
            AppIsRunning = false;
            return;
        }

        if (it.xwindow == 0 || it.ywindow == 0) {
            int xwindow, ywindow;
            SDL_GetWindowSize(SDLWindow, &xwindow, &ywindow);
            it.xwindow = xwindow;
            it.ywindow = ywindow;
        }

        Update(it);
    }

    WaitForEvents = StyleWaitForEvents;
    if (IsDocumentSelectionValid() || IsScrollbarSelectionValid() || ActiveProcesses.len) {
        WaitForEvents = false;
    }

    // This shouldn't matter to the state of the program, only appearance for
    // the user
    {
        Window     *window = GetActiveWind();
        View       *view   = GetView(window->active_view);
        Buffer     *buffer = GetBuffer(view->active_buffer);
        const char *dirty  = buffer->dirty ? " !" : "";
        String      string = Format(scratch, "%S%s", buffer->name, dirty);
        SDL_SetWindowTitle(SDLWindow, string.data);
    }

    Event *event = GetLast(frame_events);
    SetMouseCursor(*event);
    LayoutWindows(event->xwindow, event->ywindow); // This is here to render changes in title bar size without a frame of delay
    BeginFrameRender(event->xwindow, event->ywindow);

    Array<Window *> order = GetWindowZOrder(scratch);
    For(IterateInReverse(&order)) {
        if (!it->visible) continue;
        DrawWindow(it, *GetLast(frame_events));
    }
    EndFrameRender(event->xwindow, event->ywindow, ColorBackground);
    SDL_GL_SwapWindow(SDLWindow);
}


#if _WIN32
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
#else
extern char **environ;
int main(int argc, char **argv)
#endif
{
#if _WIN32
    int    argc = __argc;
    char **argv = __argv;
    AttachConsole(ATTACH_PARENT_PROCESS);
#endif
    BeginProfiler();

    if (1) {
        RunArenaTest();
        RunBufferTest();
        // ReportErrorf("Testing DONE\n");
        // return 0;
    }

#if !OS_WINDOWS
    for (int i = 0; environ[i]; i += 1) {
        Add(&Enviroment, Copy(GetSystemAllocator(), environ[i]));
    }
#endif

    WorkDir = GetWorkingDir(SysAllocator);
    {
        String sdl_config_path = SDL_GetPrefPath("krzosa", "text_editor");
        if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '\\') {
            sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
        }
        if (sdl_config_path.len && sdl_config_path.data[sdl_config_path.len - 1] == '/') {
            sdl_config_path = Chop(sdl_config_path, 1); // chop '/'
        }
        ConfigDir = NormalizePath(SysAllocator, sdl_config_path);
        SDL_free(sdl_config_path.data);
    }

    if (!SDL_Init(SDL_INIT_VIDEO)) {
        ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError());
        return 1;
    }

    SDL_EnableScreenSaver();
    SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
#if OS_WASM
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#else
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 5);
#endif

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    SDL_DisplayID primary_display_id = SDL_GetPrimaryDisplay();
    const SDL_DisplayMode *display_mode = SDL_GetCurrentDisplayMode(primary_display_id);

    // int w8 = (int)(display_mode->w * 0.8);
    // int h8 = (int)(display_mode->h * 0.8);

    int whalf = (int)(display_mode->w * 0.5) - 10;
    int hhalf = (int)(display_mode->h) - 120;
    int xhalf = whalf;
    int yhalf = 30;

    Uint32 window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
    SDLWindow           = SDL_CreateWindow("Text editor", whalf, hhalf, window_flags);
    if (SDLWindow == NULL) {
        ReportErrorf("Couldn't create window! %s", SDL_GetError());
        return 1;
    }
    SDL_SetWindowPosition(SDLWindow, xhalf, yhalf);

    SDL_GLContext gl_context = SDL_GL_CreateContext(SDLWindow);
    SDL_GL_MakeCurrent(SDLWindow, gl_context);
    SDL_ShowWindow(SDLWindow);

    // Set icon
    {
        uint32_t     data    = 0xddddddff;
        SDL_Surface *surface = SDL_CreateSurfaceFrom(1, 1, SDL_PIXELFORMAT_RGBA8888, &data, sizeof(uint32_t));
        SDL_SetWindowIcon(SDLWindow, surface);
        SDL_DestroySurface(surface);
    }

    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
        ReportErrorf("Couldn't load OpenGL! %s", SDL_GetError());
        return 1;
    }

    SDL_StartTextInput(SDLWindow);
    SDL_SetEventEnabled(SDL_EVENT_DROP_FILE, true);
    SDL_GL_SetSwapInterval(1); // vsync
    {
        float scale = SDL_GetWindowDisplayScale(SDLWindow);
        if (scale != 1.0f) DPIScale = scale;
    }

    InitBuffers();
    InitRender();
    ReloadFont(StyleFont, (U32)StyleFontSize);
    InitWindows();
    InitOS(ReportWarningf);


    for (int i = 1; i < argc; i += 1) {
        String it = argv[i];
        if (!FileExists(it)) continue;

        if (it == "--testing") {
            Testing = true;
        } else if (EndsWith(it, ".project.lua")) {
            SetProjectFile(BufferOpenFile(it));
        } else {
            Window *window = GetWindow({0});
            WindowOpenBufferView(window, it);
        }
    }

    ReportConsolef("WorkDir = %S", WorkDir);
    InitLuaConfig();
    if (Testing) InitTests();
#if OS_WINDOWS
    CoAdd(Windows_SetupVCVarsall);
#endif
#if OS_WASM
    emscripten_set_main_loop(MainLoop, 0, 1);
#else
    while (AppIsRunning) {
        MainLoop();
    }
#endif

    SDL_DestroyWindow(SDLWindow);
    SDL_Quit();

    EndProfiler();

    return 0;
}
