bool PLOT_Initialized;

struct PLOT_Point {
    Vec2 render_pos;
    Vec2F64 pos;
    Vec4 color;
    bool was_selected;
    uint32_t name;
};

struct PLOT_Series {
    PLOT_Series *next;
    VIS_Node event;
    PLOT_Point *points;
    int64_t point_count;

    Vec4 color;
    int color_index;

    Rect2F64 range;
    int *bins;
};

// We make a full copy of the data structures, afterwards during a frame
// we don't access the shared memory stuff. The reason for that is let's
// for example say user decides to add some new data after we have computed
// value ranges or even better after we have assigned colors to series.
// Then we could access for example a data point that is not accounted for etc.
struct PLOT_Plot {
    PLOT_Plot *next;
    VIS_Node event;

    Vec2F64 pos;

    int64_t series_count;
    PLOT_Series *first;
    PLOT_Series *last;

    int64_t point_count;
    Rect2F64 range;
};
MA_Arena PLOT_Arena;
PLOT_Plot *PLOT_First;
PLOT_Plot *PLOT_Last;
RandomSeed PLOT_FrameSeriesColorSeed;

void PLOT_DrawBackgroundAndTitle(Rect2 rect, S8_String title) {
    R2_DrawRect(rect, COLOR_background_plot);

    // Draw label
    // We also need to handle here the behaviour of centering
    // in zoomed out view.
    {
        float label_scale = G_Camera.zoom.y * 1.0f;
        float label_t = 0;
        if (G_Camera.zoom.y > 0.5f) {
            label_t = 0.0f;
        }
        else {
            float t = ((1.f / G_Camera.zoom.y) - 2.0f) / 4.0f;
            label_t = Clamp01(Lerp(0, 1, t));
            if (t <= 1.0f) {
                label_scale = Lerp(label_scale, 1.0f, PingPong(t));
            }
            else {
                label_scale = Lerp(0.5f, 1.0f, PingPong(t));
            }
        }

        Vec2 plot_midtop = rect.max;
        plot_midtop.x -= GetXSize(rect) / 2;
        plot_midtop.y += 2;

        Vec2 plot_mid = rect.min + (rect.max - rect.min) / 2.f;

        // We want T that is 0 when zoom is 4 or less

        Vec2 label_pos = Lerp(plot_midtop, plot_mid, label_t);

        S8_String label_string = title;
        Vec2 top_label_size = R2_GetStringSize(label_string, label_scale);
        label_pos.x -= top_label_size.x / 2;
        R2.vertex_list = &R2.ui_vertex_list;
        R2_DrawString(label_pos, label_string, COLOR_text, label_scale);
        R2.vertex_list = &R2.base_vertex_list;
    }
}

void PLOT_DrawMetrics(Rect2 rect, S8_String xlabel, S8_String ylabel, Rect2F64 range, Vec4 text_color) {
    float axis_number_scale = G_Camera.zoom.y * 0.5f;
    Vec2 plot_size = CalcSize(rect);

    // X and Y at 0,0
    {
        {
            S8_String x_value_string = S8_Format(G_Frame, "%.02f", range.min.x);
            Vec2 string_size = R2_GetStringSize(x_value_string, axis_number_scale);
            Vec2 xpos = {rect.min.x - string_size.x / 2, rect.min.y - string_size.y};
            R2_DrawString(xpos, x_value_string, text_color, axis_number_scale);
        }

        {
            S8_String y_value_string = S8_Format(G_Frame, "%.02f", range.min.y);
            Vec2 string_size = R2_GetStringSize(y_value_string, axis_number_scale);
            Vec2 ypos = {rect.min.x - string_size.x + R2.font->descent * G_Camera.zoom.y, rect.min.y};
            R2_DrawString(ypos, y_value_string, text_color, axis_number_scale);
        }
    }

    Vec2 rect_size = plot_size;
    float subdivisions = 10.f;
    float start_value = 1.f / subdivisions;
    float step_value = 1.f / subdivisions;
    float line_half_size = rect_size.y * 0.001f;

    // X Numbers
    for (double it = start_value; it < 1.1f; it += step_value) {
        double x_value = it * GetXSize(range);
        Rect2 line = {rect.min.x, rect.min.y, rect.min.x, rect.max.y};
        line.min.x += (float)it * rect_size.x;
        line.max.x += (float)it * rect_size.x;
        line.min.x -= line_half_size;
        line.max.x += line_half_size;
        R2_DrawRect(line, COLOR_plot_lines);

        S8_String x_value_string = S8_Format(G_Frame, "%.02f", x_value);
        Vec2 string_size = R2_GetStringSize(x_value_string, axis_number_scale);
        R2_DrawString({line.min.x - string_size.x / 2, line.min.y - string_size.y}, x_value_string, text_color, axis_number_scale);
    }

    // Y Numbers
    float y_label_max_x_string_size = 0;
    for (float it = start_value; it < 1.1f; it += step_value) {
        double y_value = it * GetYSize(range);

        Rect2 line = {rect.min.x, rect.min.y, rect.max.x, rect.min.y};
        line.min.y += it * rect_size.y;
        line.max.y += it * rect_size.y;
        line.min.y -= line_half_size;
        line.max.y += line_half_size;
        R2_DrawRect(line, COLOR_plot_lines);

        S8_String y_value_string = S8_Format(G_Frame, "%.02f", y_value);
        Vec2 string_size = R2_GetStringSize(y_value_string, axis_number_scale);
        y_label_max_x_string_size = Maximum(y_label_max_x_string_size, string_size.x);
        R2_DrawString({line.min.x - string_size.x + R2.font->descent, line.min.y}, y_value_string, text_color, axis_number_scale);
    }

    // X label
    {
        S8_String s = xlabel;
        Vec2 pos = rect.min + Vec2{GetXSize(rect) / 2.f, 0};
        Vec2 ssize = R2_GetStringSize(s, axis_number_scale * 2.0f);

        pos.y -= ssize.y * 2.0f;
        pos.x -= ssize.x / 2.0f;
        R2_DrawString(pos, s, text_color, axis_number_scale * 2.0f);
    }

    // Y label
    {
        S8_String s = ylabel;
        Vec2 pos = rect.min + Vec2{0, GetYSize(rect) / 2.f};
        Vec2 ssize = R2_GetStringSize(s, axis_number_scale * 2.0f);

        pos.x -= y_label_max_x_string_size * 2;
        pos.y -= ssize.y / 2.0f;
        R2_DrawString(pos, s, text_color, axis_number_scale * 2.0f, PI32 * 0.5f);
    }
}

static void PROTOTYPE_Plot() {
    PLOT_FrameSeriesColorSeed = {4};

    // Initialization, Initialized gets switched at end of frame
    if (!PLOT_Initialized) {
        VIS_CreateSharedMemory();
        U_Notification(60.0f * 60.0f * 48.0f, "No program connected ... ");
    }

    static bool generate_3plots_data = false;

    S8_String events_dump_name = S8_Lit("events.bin");

    // Generate data
    if (generate_3plots_data) {

        VIS_Plot(0);
        VIS_Title("Different application metrics  ");
        VIS_XLabel("Frame number");
        VIS_YLabel("Time in miliseconds");

        VIS_Series(0);
        VIS_SeriesName("Update without sleep");
        VIS_Point((double)Mu->frame, (double)Mu->time.update);

        VIS_Series(1);
        VIS_SeriesName("Update with sleep");
        VIS_Point((double)Mu->frame, (double)Mu->time.update_total);

        VIS_Series(2);
        VIS_SeriesName("Delta time");
        VIS_Point((double)Mu->frame, (double)Mu->time.deltaf);

        VIS_Plot(1);
        VIS_PlotKind(VIS_PlotKind_Bar);
        VIS_NamedValue("Allocated", (double)PLOT_Arena.len);
        VIS_NamedValue("Reserved", (double)PLOT_Arena.memory.reserve);
        VIS_NamedValue("Commited", (double)PLOT_Arena.memory.commit);

        double random = GetRandomNormal(&PLOT_RandomSeed);
        uint64_t hash = HashBytes(&random, sizeof(random));

        VIS_Plot(2);
        VIS_PlotKind(VIS_PlotKind_Histogram);
        VIS_Title("XOR Shift random distribution");
        VIS_Value((double)random);

        VIS_Plot(3);
        VIS_PlotKind(VIS_PlotKind_Histogram);
        VIS_Title("FNV Hash distribution");
        VIS_Value((double)hash);
    }

    {
        static bool menu_open;

        U_PushLayout({
            U_GrowthRule_Down | U_SizeRule_MatchText | U_AlignRule_Center,
            {UI_RectPadding.x, Mu->window->sizef.y - UI_RectPadding.y - 35},
            {              75,                                          35},
        });

        if (U_Checkbox(&menu_open, "Menu")) {
            U_PushLayoutIndent(0);

            if (U_Button("Close menu")) {
                menu_open = false;
            }

            U_PopLayout();
        }

        U_PopLayout();
    }

    // Context menu
    {

        static bool ui_context_menu_open;
        static Vec2 ui_context_menu_pos;
        if (Mu->window->mouse.right.press) {
            if (ui_context_menu_open == false) ui_context_menu_open = true;
            ui_context_menu_pos = Mu->window->mouse.posf;
        }

        if (ui_context_menu_open) {
            U_PushLayout({
                U_GrowthRule_Down | U_SizeRule_Exact | U_AlignRule_Left | U_AnimationRule_Off,
                ui_context_menu_pos,
                {400, 35},
            });

            if (U_ClickedOutsideUI || U_ClickedUnpress) ui_context_menu_open = false;

            if (U_Button("Post notification")) {
                U_Notification(4.0f, "Very cool text and stuff!");
            }

            if (U_Button("Reset camera")) {
                G_Camera.zoom = {1, 1};
                G_Camera.pos = {0, 0};
            }

            if (U_Button("Generate constant data")) {
                generate_3plots_data = !generate_3plots_data;
            }
            if (U_Button("Generate sine wave")) {
                double t = Mu->time.totalf * 8;
                double step = 32.0f / 48000.f;
                VIS_Plot(0);
                VIS_Title("Sine wave time");
                VIS_SeriesName("Sine wave");
                for (int i = 0; i < 48000; i += 1) {
                    VIS_Point((double)i, sin(t));
                    t += step;
                }
            }

            if (U_Button("Save events to file")) {
                S8_String shared_memory = S8_Make((char *)VIS_HeaderPointer, VIS_BUFFER_SIZE);
                OS_WriteFile(events_dump_name, shared_memory);
            }

            if (U_Button("Load events from file")) {
                S8_String events_dump = OS_ReadFile(G_Frame, events_dump_name);
                MA_MemoryCopy(VIS_HeaderPointer, events_dump.str, events_dump.len);
            }

            U_PopLayout();
        }
    }

    float alpha_for_zoom = EaseInCubic(Clamp01(G_Camera.zoom.y * 4.0f));

    MA_Reset(&PLOT_Arena);
    PLOT_First = 0;
    PLOT_Last = 0;

    // int MEASURE_ID = 2;
    // VIS_BeginMeasure(MEASURE_ID);
    // VIS_EndMeasure(MEASURE_ID);

    Vec2F64 PLOT_Pos = {Mu->window->sizef.x / 2, Mu->window->sizef.y / 2};
    for (VIS_Node *P = VIS_GetNode(VIS_HeaderPointer->first_plot); P; P = VIS_GetNode(P->next)) {
        PLOT_Plot *it_plot = MA_PushStruct(&PLOT_Arena, PLOT_Plot);
        {
            it_plot->event = *P;
            it_plot->range = {INFINITY, INFINITY, 0, 0};
            it_plot->pos = PLOT_Pos;
            PLOT_Pos.y -= 800;
        }

        for (VIS_Node *S = VIS_GetNode(P->plot.first_series); S; S = VIS_GetNode(S->next)) {
            PLOT_Series *it_series = MA_PushStruct(&PLOT_Arena, PLOT_Series);
            {
                it_plot->series_count += 1;
                it_series->event = *S;
                it_series->range = {INFINITY, INFINITY, 0, 0};
                it_series->color = R2_GetColor(&it_series->color_index);
                SLL_QUEUE_ADD(it_plot->first, it_plot->last, it_series);
                // R2.circle.colors[color_index].a = alpha_for_zoom;
            }

            if (P->plot.plot_kind == VIS_PlotKind_Histogram) {
                it_series->bins = MA_PushArray(&PLOT_Arena, int, P->plot.histogram_bin_count);
            }

            it_series->points = MA_BeginPackedArray(&PLOT_Arena, PLOT_Point);
            for (VIS_Node *E = VIS_GetNode(S->series.first_event); E; E = VIS_GetNode(E->next)) {
                double x = E->point[0];
                double y = E->point[1];
                it_series->range.min.x = Minimum(it_series->range.min.x, x);
                it_series->range.min.y = Minimum(it_series->range.min.y, y);
                it_series->range.max.x = Maximum(it_series->range.max.x, x);
                it_series->range.max.y = Maximum(it_series->range.max.y, y);

                PLOT_Point *point = MA_PushStruct(&PLOT_Arena, PLOT_Point);
                point->pos = {x, y};
                if (E->kind == VIS_Kind_NamedPoint) {
                    point->name = E->named_value.string;
                }
                it_series->point_count += 1;
                it_plot->point_count += 1;
            }
            int point_count = MA_EndPackedArray(&PLOT_Arena);
            IO_Assert(it_series->point_count == point_count);

            double offset = 0.0001f;
            if (it_series->range.min.x == it_series->range.max.x) {
                it_series->range.min.x -= offset;
                it_series->range.max.x += offset;
            }
            if (it_series->range.min.y == it_series->range.max.y) {
                it_series->range.min.y -= offset;
                it_series->range.max.y += offset;
            }

            it_plot->range.min.x = Minimum(it_series->range.min.x, it_plot->range.min.x);
            it_plot->range.min.y = Minimum(it_series->range.min.y, it_plot->range.min.y);
            it_plot->range.max.x = Maximum(it_series->range.max.x, it_plot->range.max.x);
            it_plot->range.max.y = Maximum(it_series->range.max.y, it_plot->range.max.y);
        }

        Vec4 background_plot_color = COLOR_background_plot;
        Vec4 text_color = COLOR_text;
        text_color.a = alpha_for_zoom;

        bool should_render = (alpha_for_zoom > 0.1f);

        Rect2F64 range = it_plot->range;
        Rect2 rect = Rect2_Center(Vec2F64ToVec2(it_plot->pos), {300, 300});
        {
            rect.min *= G_Camera.zoom;
            rect.max *= G_Camera.zoom;
            rect.min -= G_Camera.pos;
            rect.max -= G_Camera.pos;
        }

        Rect2 window_rect = {0, 0, Mu->window->sizef.x, Mu->window->sizef.y};
        if (AreColliding(rect, window_rect)) {

            Vec2 plot_size = CalcSize(rect);
            S8_String title = VIS_GetString(P->plot.title);
            PLOT_DrawBackgroundAndTitle(rect, title);

            Rect2F64 range = it_plot->range;
            if (P->plot.plot_kind == VIS_PlotKind_Histogram) {
                range = {it_plot->range.min.y, 0, it_plot->range.max.y, (double)it_plot->point_count};
            }

            S8_String xlabel = VIS_GetString(P->plot.xlabel);
            S8_String ylabel = VIS_GetString(P->plot.ylabel);
            PLOT_DrawMetrics(rect, xlabel, ylabel, range, text_color);

            if (should_render) {

                switch (P->plot.plot_kind) {

                    //
                    // HISTOGRAM
                    //
                    case VIS_PlotKind_Histogram: {

                        double range_step = 1.0 / (double)P->plot.histogram_bin_count;
                        double bar_x_size = range_step * plot_size.x;
                        float series_alpha = 1.0f;
                        float alpha_step = 1.0f / (float)it_plot->series_count;

                        for (PLOT_Series *it = it_plot->first; it; it = it->next) {
                            if (it->point_count == 0) continue;
                            VIS_Node *S = &it->event;
                            Vec4 color = it->color;
                            color.a = alpha_for_zoom * series_alpha;
                            series_alpha -= alpha_step;

                            for (int64_t point_i = 0; point_i < it->point_count; point_i += 1) {
                                PLOT_Point *point = it->points + point_i;
                                double y = point->pos.y;
                                Rect2F64 r = it->range;
                                double normy = (y - r.min.y) / (r.max.y - r.min.y);

                                double bin_indexf = normy / range_step;
                                int bin_index = (int)bin_indexf;

                                // Special case: Only one value in entire set
                                if (bin_index == P->plot.histogram_bin_count) {
                                    for (int i = 0; i < P->plot.histogram_bin_count; i += 1) {
                                        it->bins[i] += 1;
                                    }
                                }

                                if (bin_index < 0 || bin_index >= P->plot.histogram_bin_count) continue;
                                it->bins[bin_index] += 1;
                            }

                            bool already_selected = false;
                            for (int i = 0; i < P->plot.histogram_bin_count; i += 1) {
                                Vec4 bar_color = color;

                                int bin_it = it->bins[i];
                                double height_normalized = (double)bin_it / (double)it->point_count;
                                double height_in_plot_sizing = height_normalized * plot_size.y;

                                double i_normalized = (double)i / (double)P->plot.histogram_bin_count;
                                double bar_position_in_plot_sizing = i_normalized * plot_size.x;

                                Rect2F64 bar_rect_f64 = {
                                    bar_position_in_plot_sizing,
                                    0,
                                    bar_position_in_plot_sizing + bar_x_size,
                                    height_in_plot_sizing,
                                };
                                Rect2 bar_rect = Rect2F64ToRect2(bar_rect_f64);
                                bar_rect.min += rect.min;
                                bar_rect.max += rect.min;

                                if (already_selected == false && AreColliding(bar_rect, Mu->window->mouse.posf)) {
                                    bar_color = COLOR_highlighted_data_point;
                                }

                                R2_DrawRect(bar_rect, bar_color);

                                if (already_selected == false && AreColliding(bar_rect, Mu->window->mouse.posf)) {
                                    S8_String name = VIS_GetString(it->event.series.name);

                                    U_Popup(Mu->window->mouse.posf, "%d. %.*s\ndata points in range :: %d(%f%%)", it->event.id, S8_Expand(name), bin_it, height_normalized);
                                    already_selected = true;
                                }
                            }
                        }

                    } break;

                    //
                    // Bar graphs
                    //
                    case VIS_PlotKind_Bar: {
                        float range_step = 1.0f / it_plot->series_count;
                        float xrange_min = 0.0f;
                        for (PLOT_Series *it_series = it_plot->first; it_series; it_series = it_series->next, xrange_min += range_step) {
                            Rect2 bar_rect = {xrange_min, 0, xrange_min + range_step, 0};

                            // Sort points, smaller first
                            for (int64_t point_i = 0; point_i < it_series->point_count - 1; point_i += 1) {
                                for (int64_t point_j = 0; point_j < it_series->point_count - 1; point_j += 1) {
                                    PLOT_Point *it_point = it_series->points + point_j;
                                    PLOT_Point *next_point = it_series->points + point_j + 1;
                                    if (it_point->pos.y > next_point->pos.y) {
                                        Swap(*it_point, *next_point);
                                    }
                                }
                            }

                            float point_alpha = 1.0f;
                            float alpha_step = 1.0f / (float)it_series->point_count;

                            double y_cursor = 0;
                            for (int64_t point_i = 0; point_i < it_series->point_count; point_i += 1) {
                                Vec4 color = it_series->color;
                                color.a = alpha_for_zoom * point_alpha;
                                point_alpha -= alpha_step;

                                PLOT_Point *it_point = it_series->points + point_i;

                                Rect2 point_rect = bar_rect;
                                double height = (it_point->pos.y - it_plot->range.min.y) / (it_plot->range.max.y - it_plot->range.min.y);
                                {
                                    if (height == 0) height += 0.0001f;
                                    point_rect.min.y = (float)y_cursor;
                                    point_rect.max.y = (float)height;
                                    y_cursor = height;

                                    point_rect.min = point_rect.min * plot_size;
                                    point_rect.max = point_rect.max * plot_size;
                                    point_rect.min = point_rect.min + rect.min;
                                    point_rect.max = point_rect.max + rect.min;
                                }

                                if (AreColliding(point_rect, Mu->window->mouse.posf)) {
                                    S8_String series_name = VIS_GetString(it_series->event.series.name);
                                    S8_String value_name = VIS_GetString(it_point->name);
                                    U_Popup(Mu->window->mouse.posf, "%d%.*s :: %.*s %f(%f%%)", it_series->event.id, S8_Expand(series_name), S8_Expand(value_name), it_point->pos.y, height);
                                    color += 0.5f;
                                }

                                R2_DrawRect(point_rect, color);
                            }
                        }
                    } break;

                    //
                    // SCATTER
                    //
                    case VIS_PlotKind_Line:
                    case VIS_PlotKind_Scatter: {

                        float circle_size = 2.0f * G_Camera.zoom.y;
                        R2.circle.radius = 2.0f * G_Camera.zoom.y;
                        float line_thickness = 0.2f * G_Camera.zoom.y;
                        Vec4 highlighted_data_point_color = COLOR_highlighted_data_point;

                        for (PLOT_Series *it_series = it_plot->first; it_series; it_series = it_series->next) {
                            VIS_Node *S = &it_series->event;
                            for (int64_t point_i = 0; point_i < it_series->point_count; point_i += 1) {
                                PLOT_Point *pit = it_series->points + point_i;
                                Vec2 norm_pos = Vec2F64ToVec2((pit->pos - it_plot->range.min) / (it_plot->range.max - it_plot->range.min));
                                Vec2 point_scaled_to_plot_size = norm_pos * plot_size;               // <0, plot_size>
                                Vec2 point_moved_to_plot_pos = point_scaled_to_plot_size + rect.min; // Move to rect poit_seriesion

                                R2_DrawCircleFast(point_moved_to_plot_pos, it_series->color_index);

                                pit->color = it_series->color;
                                pit->render_pos = point_moved_to_plot_pos;
                                if (AreColliding(point_moved_to_plot_pos, circle_size, Mu->window->mouse.posf)) {
                                    pit->was_selected = true;
                                    pit->color = highlighted_data_point_color;
                                    S8_String xlabel = VIS_GetString(P->plot.xlabel);
                                    S8_String ylabel = VIS_GetString(P->plot.ylabel);
                                    S8_String name = VIS_GetString(S->series.name);
                                    U_Popup(Mu->window->mouse.posf, "%d.%.*s\n%.*s :: %f\n%.*s :: %f", (int)S->id, S8_Expand(name), S8_Expand(xlabel), pit->pos.x, S8_Expand(ylabel), pit->pos.y);

                                    R2.vertex_list = &R2.ui_vertex_list;
                                    R2_DrawCircle(point_moved_to_plot_pos, R2.circle.radius, pit->color);
                                    R2.vertex_list = &R2.base_vertex_list;
                                }
                            }
                        }
                        if (P->plot.plot_kind == VIS_PlotKind_Line) {
                            for (PLOT_Series *it_series = it_plot->first; it_series; it_series = it_series->next) {
                                for (int64_t plot_i = 0; plot_i < it_series->point_count; plot_i += 1) {
                                    PLOT_Point *pit = it_series->points + plot_i;
                                    if (plot_i < it_series->point_count - 1) {
                                        PLOT_Point *next_pit = it_series->points + plot_i + 1;
                                        Vec4 line_color = pit->color;
                                        if (next_pit->was_selected) line_color = next_pit->color;
                                        R2_DrawLine(pit->render_pos, next_pit->render_pos, line_thickness, line_color);
                                    }
                                }
                            }
                        }
                    } break;
                        IO_InvalidDefaultCase();
                }
            }
        }
    }

    if (!PLOT_Initialized) PLOT_Initialized = !PLOT_Initialized;
}
