
#include "../core/vmath.hpp"
#define MU_Float2 Vec2
#define MU_Int2 Vec2I

#include "../core/core.c"
#include "../vendor/stb_truetype.c"
#include "../vendor/glad/glad.c"

#define VIS_MemoryCopy MA_MemoryCopy
#define VIS_MemoryZero MA_MemoryZero
#define VIS_ASSERT IO_Assert
#define VIS_IMPLEMENTATION
#include "visualize.h"
#include "../core/vmath64.hpp"

#include "vis_types.h"
#include "render2d.cpp"

MA_Arena    G_PernamentArena;
MA_Arena    G_FrameArena;
MA_Arena   *G_Frame = &G_FrameArena;
MA_Arena   *G_Perm  = &G_PernamentArena;
MU_Context *Mu;

#include "globals.cpp"
#include "vis_ui.cpp"
#include "prototype_data_handling.cpp"
#include "vis_plot_canvas.cpp"
#include "prototype_text_editor.cpp"

/* TODO
- Add global hotkey to bring to forefront
- Output text to console
- Save event files and add history

*/

int main(int argc, char **argv) {
    DEV_SetWorkingDir();
    for (int i = 1; i < argc; i += 1) {
        S8_String arg = S8_MakeFromChar(argv[i]);
        if (S8_AreEqual(arg, S8_Lit("1"), S8_NO_FLAGS)) G_ProcessID = 1;
    }

    VIS_Prototypes prototype = VIS_Prototypes_Plot;
    MU_Params      params    = {};
    params.enable_opengl     = true;
    params.window.size.x     = 1280;
    params.window.size.y     = 720;
    params.delta_time        = 0.0166666;
    Mu                       = MU_Start(params);
    GL_Init(Mu);

    while (MU_Update(Mu)) {
        MA_Reset(G_Frame);
        if (Mu->window->key[MU_KEY_ESCAPE].down) {
            MU_Quit(Mu);
        }
        if (Mu->window->is_focused == false) {

            uint8_t cmd = VIS_ReadCommand();
            if (cmd) {
                switch (cmd) {
                case VIS_COMMAND_CONNECTED: {
                    U_Notification(4.0f, "Your program has successfully written to the shared memory!");
                } break;
                case VIS_COMMAND_CLEAR: {
                    VIS__Clear();
                } break;
                }
                VIS_IncrementReadCommand();
            }

            Mu->window->should_render = false;
            continue;
        }

        //
        // PROTOTYPE TRANSITIONS
        //
        static float t_transition;
        static int   transition_phase;

        if (Mu->window->key[MU_KEY_F1].press) {
            transition_phase = 1;
            t_transition     = 0;
        }
        if (transition_phase == 1) {
            t_transition += Mu->time.deltaf * 5.0f;
            if (t_transition >= 1) {
                prototype        = (prototype + 1) % VIS_Prototypes_Count;
                transition_phase = 2;
            }
        }
        if (transition_phase == 2) {
            t_transition -= Mu->time.deltaf * 5.0f;
            if (t_transition <= 0) {
                transition_phase = 0;
            }
        }

        //
        // CAMERA
        //
        {
            Mu->window->change_cursor_on_mouse_hold = true;
            const float zoom_penalty                = 0.4f;
            const Vec2  target_wheel                = {};

            bool one_zoom_to_far = (G_Camera.zoom.y > 1000.0f || G_Camera.zoom.x > 1000.0f);
            if (Mu->window->mouse.delta_wheel) {
                bool invalid = one_zoom_to_far && Mu->window->mouse.delta_wheel > 0.0f;
                if (!invalid) {
                    // We are adding the G_Camera.zoom so it scales nicely,
                    // When big zoom it zoomes faster and when small zoom it zooms slower
                    G_Camera.wheel = Mu->window->mouse.delta_wheel * G_Camera.zoom * zoom_penalty;
                }
            }

            if (Mu->window->key[MU_KEY_SPACE].down) {
                if (Mu->window->key[MU_KEY_CONTROL].down) {
                    G_Camera.wheel.x = -0.5f * G_Camera.zoom.x * zoom_penalty;
                } else {
                    G_Camera.wheel.x = 0.5f * G_Camera.zoom.x * zoom_penalty;
                }
            }

            Vec2 distance   = G_Camera.wheel - target_wheel;
            Vec2 wheel_step = distance * Mu->time.deltaf * 10.f;
            G_Camera.wheel -= wheel_step; //

            Vec2 zoom_to_point  = Mu->window->mouse.posf;
            Vec2 change_in_zoom = wheel_step;
            Vec2 new_zoom       = G_Camera.zoom + change_in_zoom;

            bool stepped_any          = Absolute(wheel_step.x) >= 0.0000001f && Absolute(wheel_step.y) >= 0.00000001f;
            bool zoom_is_big_enough   = new_zoom.y > 0.01f && new_zoom.x > 0.01f;
            bool zoom_is_small_enough = new_zoom.x < 1200.f && new_zoom.y < 1200.f;
            // if (stepped_any && zoom_is_small_enough && zoom_is_big_enough) {
            if (1) {
                Vec2 t = new_zoom / G_Camera.zoom;

                M4x4 matrix = M4_Identity();
                matrix      = M4_Translation({zoom_to_point.x, zoom_to_point.y, 0}) * matrix;
                matrix      = M4_Scale({t.x, t.y, 1}) * matrix;
                matrix      = M4_Translation({-zoom_to_point.x, -zoom_to_point.y, 0}) * matrix;

                Vec4 pos      = {G_Camera.pos.x, G_Camera.pos.y, 1, 1};
                pos           = matrix * pos;
                G_Camera.pos  = {pos.x, pos.y};
                G_Camera.zoom = new_zoom;
            }

            if (G_Camera.zoom.y > 4.0f) {
                R2.font = &R2.font_very_large;
            } else if (G_Camera.zoom.y > 0.25f)
                R2.font = &R2.font_medium;
            else {
                R2.font = &R2.font_small;
            }

            if (Mu->window->mouse.left.down) {
                Vec2 pan_delta = Mu->window->mouse.delta_pos_normalized * 1000;
                G_Camera.pos -= pan_delta;
            }

            float speed = 8.0f;
            if (Mu->window->key[MU_KEY_SHIFT].down) {
                speed *= 4;
            }

            Vec2 multiplier = G_Camera.zoom;
            multiplier.x    = Clamp(multiplier.x, 1.0f, 4.f);
            multiplier.y    = Clamp(multiplier.y, 1.0f, 4.f);
            if (Mu->window->key[MU_KEY_W].down) {
                G_Camera.pos.y += speed * multiplier.y;
            }
            if (Mu->window->key[MU_KEY_S].down) {
                G_Camera.pos.y -= speed * multiplier.y;
            }
            if (Mu->window->key[MU_KEY_D].down) {
                G_Camera.pos.x += speed * multiplier.x;
            }
            if (Mu->window->key[MU_KEY_A].down) {
                G_Camera.pos.x -= speed * multiplier.x;
            }
        }

        GL_Clear(Mu->window->size, {1, 1, 1, 1});

        switch (prototype) {
        case VIS_Prototypes_Plot: {
            PROTOTYPE_Plot();
        } break;
        case VIS_Prototypes_TextEditor: {
            PROTOTYPE_TextEditor();
        } break;
        case VIS_Prototypes_DataHandling: {
            PROTOTYPE_DataHandling();
        } break;
            IO_InvalidDefaultCase();
        }

        // UI_EndFrame();
        U_EndFrame();
#if 1
        R2_DebugString("Camera_Pos: %f %f", G_Camera.pos.x, G_Camera.pos.y);
        R2_DebugString("Zoom: %f %f", G_Camera.zoom.x, G_Camera.zoom.y);
        R2_DebugString("update: %f", Mu->time.update);
        R2_DebugString("points: %llu", G_PointCount);
        G_PointCount = 0;
        // R2_DebugString("loading_globals: %f", T_LoadingGlobals);
        // R2_DebugString("draw_routines: %f", T_PlotDrawRoutines);
        // R2_DebugString("series_processing: %f", T_PlotSeriesProcessing);
        // R2_DebugString("total_events: %f", T_PlotTotal);
#endif

        R2_DrawCircle(Mu->window->sizef / 2, Mu->window->size.x / 2.f * t_transition, HSLToRGB({0.7861f, 0.2f, 0.95f, 1.0f}));
        R2_EndFrame({(float)Mu->window->size.x, (float)Mu->window->size.y});
    }
}
