/* DrawState.cpp */

#include "DrawState.hpp"
#include "AnimateGcCopyCb.hpp"
#include "GcStatistics.hpp"
#include "xo/imgui/ImScale.hpp"
#include "xo/indentlog/scope.hpp"

using xo::gc::GcStatisticsHistory;
using xo::gc::generation;
using xo::scope;
using xo::xtag;

xo::up<xo::gc::GcCopyCallback>
DrawState::make_gc_copy_animation(AppState * app_state)
{
    return std::make_unique<AnimateGcCopyCb>(app_state, this);
}

/**
 *  @p polarity  0 -> draw from-space above to-space; 1 -> draw from-space below to-space
 *  @p p_x1    On exit *p_x1 contains x-coord of right-hand edge of rectangle
 *             depicting potential memory range
 **/
void
DrawState::draw_generation(const GenerationLayout & layout,
                           ImDrawList * draw_list)
{
    //scope log(XO_DEBUG(with_labels));

    using xo::gc::generation;

    /* next GC trigges when G0_to_size reaches this threshold */
    std::size_t G_to_gc_threshold = 0;

    G_to_gc_threshold = layout.gendescr_.gc_threshold_;

    std::size_t G_to_scale = layout.gendescr_.scale();

    /*
     * committed: G_to_committed
     * G1:  G1_to_size
     * G0:  G0_to_size
     * ckp: G1_to_size
     * ngc: G_to_gc_threshold
     *
     *   <----------------------------- committed --------------------------->
     *   <------------------ used ------------------> <-------- free -------->
     *   <------- G1 --------> <-------- G0 -------->
     *  |NNNNNNNNNNNNNNNNNNNNN|nnnnnnnnnnnnnnnnnnnnnn|________________________|
     *                        ^                                      ^
     *                        ckp                                    ngc
     *
     *  in screen coords:
     *
     *  horizontally:
     *
     *         rect.x_lo                                                                   rect.x_hi
     *         v                                                                           v
     *
     *          <--------------------------- display_w -----------------------------> <-+->
     *          <------------------------ ngc_w ---------------------->                 \- rh_text_dx
     *          <------- G1_w ------> <-------- G0_w ------>
     *         ^                     ^                      ^          ^             ^
     *        x0                    G0_x0                  G0_x1     ngc_w          x1
     *     *p_x0                                        *p_g0_x1                 *p_x1
     *
     *  vertically:
     *
     *      <- rect.y_lo
     *     ^
     *
     *     v
     *      <- rect.y_hi
     */

    /* e.g. N1: 34511 bytes */
    char g1_buf[255];

    ImU32 label_color = IM_COL32(255, 255, 192, 255); /*super pale yellow*/

    if (layout.with_labels_) {
        snprintf(g1_buf, sizeof(g1_buf),
                 "reserved: %lu bytes; committed: %lu bytes; %s\u2081: %lu bytes; %s\u2080: %lu bytes",
                 layout.gendescr_.reserved_,
                 layout.gendescr_.committed_,
                 layout.mnemonic(),
                 layout.gendescr_.before_checkpoint_,
                 layout.mnemonic(),
                 layout.gendescr_.after_checkpoint_);

        draw_list->AddText(layout.bounding_rect_.top_left(),
                           label_color,
                           g1_buf);
    }

    if (layout.with_labels_) {
        auto textz = ImGui::CalcTextSize(layout.rh_text_.c_str());

        draw_list->AddText(ImVec2(layout.chart_withlabel_rect_.x_hi() - textz.x,
                                  layout.chart_withlabel_rect_.y_mid() - 0.5 * textz.y),
                           label_color,
                           layout.rh_text_.c_str());
    }

    ImU32 outline_color = IM_COL32(255, 255, 255, 255); /*white*/

    /* chart rectangle */
    draw_list->AddRect(layout.mem_rect_from_.top_left(),
                       layout.mem_rect_from_.bottom_right(),
                       outline_color);

    draw_list->AddRect(layout.mem_rect_to_.top_left(),
                       layout.mem_rect_to_.bottom_right(),
                       outline_color);

    float display_w = layout.mem_rect_from_.width();
    float G1_w = display_w * layout.to_G1_size() / layout.to_scale();
    float G1_x1 = layout.mem_rect_from_.x_lo() + G1_w;
    ImRect G1_rect = layout.to_g1_rect();
    //ImRect G1_rect = layout.mem_rect_to_.left_fraction(layout.to_G1_size() / layout.to_scale());

    /* G1 (i.e. N1 or T1) */
    {
        ImU32   G1_color = IM_COL32(  0, 128,   0, 255);
        ImU32 text_color = IM_COL32(255, 255, 255, 255);

        char buf[255];

        if (layout.with_labels_)
            snprintf(buf, sizeof(buf), "%s\u2081: %luk",
                     layout.mnemonic(), layout.to_G1_size() / 1024); /* N1 / T1 */

        char tooltip[255];
        snprintf(tooltip, sizeof(tooltip),
                 "%s\u2081: %lu - %s survivor size in bytes",
                 layout.mnemonic(),
                 layout.to_G1_size(),
                 layout.name());

        ImRect::draw_filled_rect_with_label
            (layout.with_labels_ ? buf : nullptr,
             tooltip,
             G1_rect,
             G1_color,
             text_color,
             draw_list);
    }

    ImRect G0_rect = layout.to_g0_rect();

    /* G0 (i.e. N0 or T0) */
    {
        ImU32   G0_color = IM_COL32( 32, 192,  32, 255);
        ImU32 text_color = IM_COL32(  0,   0,   0, 255);

        char buf[255];

        if (layout.with_labels_)
            snprintf(buf, sizeof(buf), "%s\u2080: %luk",
                     layout.mnemonic(),
                     layout.to_G0_size() / 1024); /* N(0) */

        char tooltip[255];
        snprintf(tooltip, sizeof(tooltip),
                 "%s\u2080: %lu - %s new alloc size in bytes",
                 layout.mnemonic(), layout.to_G0_size(),
                 layout.name());

        ImRect::draw_filled_rect_with_label
            (layout.with_labels_ ? buf : nullptr,
             tooltip,
             G0_rect,
             G0_color,
             text_color,
             draw_list);
    }

    /* mark where next gc will trigger */
    if (layout.with_labels_) {
        const char * uparrow = reinterpret_cast<const char *>(u8"\u25b3");

        float ngc_w = (display_w * layout.gendescr_.gc_threshold_) / G_to_scale;

        auto tmp = ImGui::CalcTextSize(uparrow);
        std::size_t uparrow_w = tmp.x;
        double ngc_x = layout.chart_withlabel_rect_.x_lo() + ngc_w - uparrow_w/2.0;

        ImVec2 marker_pos(ngc_x, layout.chart_withlabel_rect_.y_hi());

        draw_list->AddText(marker_pos,
                           IM_COL32(255, 128, 128, 255) /*red*/,
                           uparrow);

        ImGui::SetCursorScreenPos(marker_pos);
        ImGui::InvisibleButton("mkbutton", tmp);
        if (ImGui::IsItemHovered()) {
            char marker_tt_buf[255];
            snprintf(marker_tt_buf, sizeof(marker_tt_buf),
                     "Next %s GC when size(%s) >= %lu bytes",
                     layout.gc_type(),
                     layout.name(),
                     layout.gendescr_.gc_threshold_);

            ImGui::SetTooltip("%s", marker_tt_buf);
        }
    }
} /*draw_generation*/

void
DrawState::draw_nursery(const GcStateDescription & gcstate,
                        bool with_labels,
                        const ImRect & rect,
                        ImDrawList * draw_list,
                        GenerationLayout * p_layout)
{
    using xo::gc::generation;

    const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::nursery);

    GenerationLayout layout(gendescr, rect, with_labels);

    draw_generation(layout, draw_list);

    if (p_layout)
        *p_layout = layout;
}

void
DrawState::draw_tenured(const GcStateDescription & gcstate,
                        bool with_labels,
                        const ImRect & rect,
                        ImDrawList * draw_list,
                        GenerationLayout * p_layout)
{
    using xo::gc::generation;

    const GcGenerationDescription & gendescr = gcstate.get_gendescr(generation::tenured);

    GenerationLayout layout(gendescr, rect, with_labels);

    draw_generation(layout, draw_list);

    if (p_layout)
        *p_layout = layout;
}

#ifdef nope
                ImRect::draw_filled_rect
                    (tt.c_str(),
                     ImRect::from_xy_span(x_span, ImVec2(ypsz_lo, y_zero)),
                     persist_color,
                     draw_list);
#endif

ImU32 DrawState::headline_color(gc_history_headline headline)
{
    constexpr ImU32 sentinel_color = IM_COL32(255, 255, 255, 255);
    constexpr ImU32  persist_color = IM_COL32(  0,  64, 192, 255); /*darker blue*/
    ImU32  promote_color = IM_COL32(  0, 128,   0, 255); /*darker green*/
    ImU32  survive_color = IM_COL32( 32, 192,  32, 255); /*lighter green*/
    ImU32 garbageN_color = IM_COL32(255, 128,  64, 255); /*darker orange*/
    ImU32 garbage1_color = IM_COL32(255, 192, 128, 255); /*medium orange*/
    ImU32 garbage0_color = IM_COL32(255, 255, 192, 255); /*pale yellow*/

    switch(headline) {
    case gc_history_headline::survive: return survive_color;
    case gc_history_headline::promote: return promote_color;
    case gc_history_headline::persist: return persist_color;
    case gc_history_headline::garbage0: return garbage0_color;
    case gc_history_headline::garbage1: return garbage1_color;
    case gc_history_headline::garbageN: return garbageN_color;
    case gc_history_headline::N:
        assert(false);
        break;
    }

    return sentinel_color;
}

void
DrawState::draw_text_bullet(ImU32 fill_color,
                            ImDrawList * draw_list)
{
    float tx_h = ImGui::GetTextLineHeight();
    ImVec2 cursor_pos = ImGui::GetCursorScreenPos();
    ImVec2 circle_pos = cursor_pos;
    float circle_radius = 0.2 * tx_h;

    draw_list->AddCircleFilled(ImVec2(circle_pos.x,
                                      circle_pos.y + 0.5 * tx_h),
                               circle_radius, fill_color);

    ImGui::SetCursorScreenPos(ImVec2(cursor_pos.x + circle_radius, cursor_pos.y));
}

void
DrawState::write_gc_history_bar(const char * idname,
                                gc_history_headline headline,
                                const GcStatisticsHistoryItem & stats,
                                const ImRect & bar_rect,
                                ImDrawList * draw_list)
{
    ImU32 fill_color = headline_color(headline);

    draw_list->AddRectFilled(bar_rect.top_left(), bar_rect.bottom_right(), fill_color);

    if (bar_rect.is_nonempty()) {
        ImGui::SetCursorScreenPos(bar_rect.top_left());
        ImGui::InvisibleButton(idname, ImVec2(bar_rect.width(), bar_rect.height()));
        if (ImGui::IsItemHovered()) {
            ImGui::BeginTooltip();

            ImDrawList * tt_draw_list = ImGui::GetWindowDrawList();

            draw_text_bullet(fill_color, tt_draw_list);

            /* choose text here */
            switch(headline) {
            case gc_history_headline::survive:
                ImGui::Text("survive: %lu: bytes surviving 1st GC after allocation",
                            stats.survive_z_);
                break;
            case gc_history_headline::promote:
                ImGui::Text("promote: %lu: bytes surviving 2nd GC; if nursery promote to tenured",
                            stats.promote_z_);
                break;
            case gc_history_headline::persist:
                ImGui::Text("persist: %lu: bytes surviving 3+ GCs. Only non-zero for full collections",
                            stats.persist_z_);
                break;
            case gc_history_headline::garbage0:
                ImGui::Text("garbage\u2080: %lu: bytes collected on 1st GC after allocation",
                            stats.garbage0_z_);
                break;
            case gc_history_headline::garbage1:
                ImGui::Text("garbage\u2081: %lu: bytes collected on 2nd GC after allocation",
                            stats.garbage1_z_);
                break;
            case gc_history_headline::garbageN:
                ImGui::Text("garbage\u2099: %lu: bytes collected on 3rd or later GC after allocation",
                            stats.garbageN_z_);
                break;
            case gc_history_headline::N:
                assert(false);
                break;
            }

            ImGui::Separator();

            ImGui::Text(" gcseq: %lu\n"
                        " type: %s\n"
                        " alloc: %lu\n",
                        stats.gc_seq_,
                        (stats.upto_ == generation::nursery) ? "incremental" : "FULL",
                        stats.new_alloc_z_);

            {
                draw_text_bullet(headline_color(gc_history_headline::survive), tt_draw_list);
                ImGui::Text(" survive: %lu - survived 1 gc cycle", stats.survive_z_);
            }

            {
                draw_text_bullet(headline_color(gc_history_headline::promote), tt_draw_list);
                ImGui::Text(" promote: %lu - survived 2 gc cycles", stats.promote_z_);
            }

            {
                draw_text_bullet(headline_color(gc_history_headline::persist), tt_draw_list);
                ImGui::Text(" persist: %lu - survived 3+ gc cycles\n", stats.persist_z_);
            }

            {
                draw_text_bullet(headline_color(gc_history_headline::garbage0), tt_draw_list);
                /* "garbage0", but with "0" subscripted */
                ImGui::Text(" garbage\u2080: %lu - survived 0 gc cycles", stats.garbage0_z_);
            }

            {
                draw_text_bullet(headline_color(gc_history_headline::garbage1), tt_draw_list);
                /* "garbage1" but with "1" subscripted */
                ImGui::Text(" garbage\u2081: %lu - survived 1 gc cycles", stats.garbage1_z_);
            }

            {
                draw_text_bullet(headline_color(gc_history_headline::garbageN), tt_draw_list);
                /* "garbageN" but with "N" subscripted */
                ImGui::Text(" garbage\u2099: %lu - survived 2+ gc cycles", stats.garbageN_z_);
            }

            ImGui::Text(" effort: %lu dt: %.1lfus\n"
                        " copy efficiency: %.1lf%% collection rate: %.0lf bytes/sec",
                        stats.effort_z_,
                        1e-3 * stats.dt_.scale(),
                        100.0 * stats.efficiency(),
                        stats.collection_rate()
                );

            ImGui::EndTooltip();
        }
    }
} /*write_gc_history_bar*/

/** stacked bar chart
 *
 *  @param gen  if @ref generation::nursery, only display nursery collections.
 *              otherwise display both
 **/
void
DrawState::draw_gc_history(const GcStateDescription & gcstate,
                           generation gen,
                           const GcStatisticsHistory & gc_history,
                           const ImRect & bounding_rect,
                           bool debug_flag,
                           ImDrawList * draw_list)
{
    scope log(XO_DEBUG(debug_flag));

    if (gc_history.empty())
        return;

    float lm = 10;
    float tm = 25;
    float rm = 0;
    float bm = 0;

    ImRect chart_rect = bounding_rect.within_margin(ImRect(lm, tm, rm, bm));

    /* we're going to make a bar chart */

    /* x_scale,y_scale in GC units (i.e. bytes) */
    size_t x_scale = gc_history.capacity();
    /* in bytes. +ve values for bytes survivingK */
    size_t yplus_range = 0;
    /* in bytes. -ve values for bytes collected */
    size_t yminus_range = 0;

    float display_w = chart_rect.width();
    float display_h = chart_rect.height();

    /* 1st loop: figure out max y scale */
    for (const GcStatisticsHistoryItem & stats : gc_history) {
        if ((gen == stats.upto_) || (gen == generation::tenured))
        {
            //size_t  na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/
            size_t  sz = stats.survive_z_; /*survive 1st gc */
            size_t  pz = stats.promote_z_; /*survive 2nd gc */
            size_t psz = stats.persist_z_; /*survive 3+ gc */
            size_t g0z = stats.garbage0_z_;
            size_t g1z = stats.garbage1_z_;
            size_t gNz = stats.garbageN_z_;

            if (yplus_range < sz + pz + psz)
                yplus_range = sz + pz + psz;

            if (yminus_range < g0z + g1z + gNz)
                yminus_range = g0z + g1z + gNz;
        } else {
            ;
        }
    }

    float y_range = yplus_range + yminus_range;

    /* native->screen conversion in y direction */
    ImScale y_scale{ImVec2(0.0 - yminus_range, chart_rect.y_hi()), ImVec2(yplus_range, chart_rect.y_lo())};

    /* screen y-coord of x-axis */
    float y_zero = y_scale(0);

    if ((y_zero < chart_rect.y_lo()) || (y_zero > chart_rect.y_hi())) {
        throw std::runtime_error(tostr("expected y_zero within chart range [y_lo,y_hi]",
                                       xtag("y_lo", chart_rect.y_lo()),
                                       xtag("y_zero", y_zero),
                                       xtag("y_hi", chart_rect.y_hi())));

    }

    assert(y_zero >= chart_rect.y_lo());
    assert(y_zero <= chart_rect.y_hi());

    /* width of 1 bar in screen coords */
    constexpr float c_min_bar_w = 5.0;
    float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity());

    /* 2nd loop: draw bars */
    std::size_t i = 0;
    for (const GcStatisticsHistoryItem & stats : gc_history)
    {
        ImGui::PushID(i);

        if ((gen == stats.upto_) || (gen == generation::tenured))
        {
            /*
             *    screen              native
             *    coords              coords
             *
             *     ys_span.y   +--+   ypsz_nat.y
             *                 |  |                survive_z  (survived 1st GC)
             *                 |  |
             *     yp_span.y   +--+
             *                 |  |                promote_z  (sruvived 2nd GC)
             *                 |  |
             *   ypsz_span.y   +--+
             *                 |  |                persist_z  (survived 3+ GCs)
             *                 |  |
             *        y_zero   +--+ ---------------------------------------------- x-axis
             *                 |  |                gN  (killed on 3+ GC)
             *                 |  |
             *    ygN_span.x   +--+
             *                 |  |                g1  (killed on 2nd GC)
             *                 |  |
             *    yg1_span.x   +--+
             *                 |  |                g0  (killed on 1st GC)
             *                 |  |
             *    yg0_span.x   +--+
             */

            /* x-coordinates of bar */
            float x_lo = bounding_rect.x_lo() + lm + i * bar_w;
            float x_hi = x_lo + bar_w - 1;
            ImSpan x_span{x_lo, x_hi};

            /* native y-coordinates of persist bar */
            ImSpan ypsz_nat(0, stats.persist_z_);
            {
                /* screen y-coordinates of persist bar (survived 3+ GCs) */
                ImSpan ypsz_span = y_scale.map_span(ypsz_nat);

                write_gc_history_bar("##persist",
                                     gc_history_headline::persist,
                                     stats,
                                     ImRect::from_xy_span(x_span, ypsz_span.normalize()),
                                     draw_list);
            }

            /* native y-coordinates of promote bar */
            ImSpan yp_nat(ypsz_nat.hi(), ypsz_nat.hi() + stats.promote_z_);

            /* y-coordinates of promote bar (survived 2nd GC) */
            {
                /* screen y-coordinates of promote bar (survived 2nd GC) */
                ImSpan yp_span = y_scale.map_span(yp_nat);

                write_gc_history_bar("##promote",
                                     gc_history_headline::promote,
                                     stats,
                                     ImRect::from_xy_span(x_span, yp_span.normalize()),
                                     draw_list);
            }

            /* native y-coordinates of survive bar */
            ImSpan ys_nat(yp_nat.hi(), yp_nat.hi() + stats.survive_z_);

            /* y-coordinates of survivor bar (survived 1st GC) */
            {
                /* screen y-coordinates of surive bar (survived 1st GC) */
                ImSpan ys_span = y_scale.map_span(ys_nat);

                write_gc_history_bar("##survivor",
                                     gc_history_headline::survive,
                                     stats,
                                     ImRect::from_xy_span(x_span, ys_span.normalize()),
                                     draw_list);
            }

            // -----------------------------------------------------------

            /* native y-coordinates of garbageN bar */
            ImSpan ygN_nat(0.0 - stats.garbageN_z_, 0.0);

            /* y-coordinates of garbageN bar (killed on 3+ GC) */
            {
                /* screen y-coordinates of garbageN bar (killed on 3+ GC) */
                ImSpan ygN_span = y_scale.map_span(ygN_nat);

                write_gc_history_bar("##garbageN",
                                     gc_history_headline::garbageN,
                                     stats,
                                     ImRect::from_xy_span(x_span, ygN_span.normalize()),
                                     draw_list);
            }

            /* native y-coordinates of garbage1 bar */
            ImSpan yg1_nat(ygN_nat.lo() - stats.garbage1_z_, ygN_nat.lo());

            /* y-coordinates of garbage1 bar (killed on 2nd GC) */
            {
                /* y-coordinates of garbage1 bar (killed on 2nd GC) */
                ImSpan yg1_span = y_scale.map_span(yg1_nat);

                write_gc_history_bar("##garbage1",
                                     gc_history_headline::garbage1,
                                     stats,
                                     ImRect::from_xy_span(x_span, yg1_span.normalize()),
                                     draw_list);
            }

            /* native y-coordinates of garbage0 bar */
            ImSpan yg0_nat(yg1_nat.lo() - stats.garbage0_z_, yg1_nat.lo());

            /* y-coordinates of garbage0 bar (killed on 1st GC) */
            {
                /* y-coordinates of garbage0 bar (killed on 1st GC) */
                ImSpan yg0_span = y_scale.map_span(yg0_nat);

                write_gc_history_bar("##garbage0",
                                     gc_history_headline::garbage0,
                                     stats,
                                     ImRect::from_xy_span(x_span, yg0_span.normalize()),
                                     draw_list);
            }
        } else {
            /* draw nothing */
            ;
        }

        ++i;

        ImGui::PopID();
    }

    log && log(xtag("i", i));
}

void
DrawState::draw_gc_efficiency(const GcStateDescription & gcstate,
                              //generation gen,
                              const GcStatisticsHistory & gc_history,
                              const ImRect & bounding_rect,
                              bool debug_flag,
                              ImDrawList * draw_list)
{
    scope log(XO_DEBUG(debug_flag));

    float lm = 10;
    float tm = 25;

    /* we're going to make a level chart */

    /* x_scale,y_scale in GC units (i.e. bytes) */
    size_t x_scale = gc_history.capacity();
    size_t yplus_scale = 1;
    size_t yminus_scale = 0;

    float display_w = bounding_rect.width()  - lm;
    float display_h = bounding_rect.height() - tm;

#ifdef NOPE // don't need this.  y-scale is [0.0, 1.0]
    /* 1st loop: figure out max y scale */
    for (const GcStatisticsHistoryItem & stats : gc_history) {
        if ((gen == stats.upto_) || (gen == generation::tenured))
        {
            //size_t  na = stats.new_alloc_z_ - stats.survive_z_; /*new allocs, but dont' double-count survive_z*/
            size_t  sz = stats.survive_z_; /*survive 1st gc */
            size_t  pz = stats.promote_z_; /*survive 2nd gc */
            size_t psz = stats.persist_z_; /*survive 3+ gc */
            size_t g0z = stats.garbage0_z_;
            size_t g1z = stats.garbage1_z_;
            size_t gNz = stats.garbageN_z_;

            if (yplus_scale < sz + pz + psz)
                yplus_scale = sz + pz + psz;

            if (yminus_scale < g0z + g1z + gNz)
                yminus_scale = g0z + g1z + gNz;
        } else {
            ;
        }
    }
#endif

    /* y-coord of x-axis */
    float y_zero = bounding_rect.y_lo() + tm + display_h;
    //float y_zero = bounding_rect.y_lo() + tm + (display_h * yplus_scale) / (yplus_scale + yminus_scale);

    float y_scale = 1.0;

    /* width of 1 bar in screen coords */
    constexpr float c_min_bar_w = 5.0;
    float bar_w = std::max(c_min_bar_w, display_w / gc_history.capacity());

    /* TODO: use temporary arena */
    std::vector<ImVec2> line_points;
    line_points.reserve(gc_history.size());

    ImU32 average_color = IM_COL32(255, 255,  64, 255); /*solid yellow*/
    ImU32  sample_color = IM_COL32(255, 255, 255, 255); /*white*/

    /* 2nd loop: draw levels */
    std::size_t i = 0;
    for (const GcStatisticsHistoryItem & stats : gc_history)
    {
        //std::vector<ImVec2> line_points = { /* your points */ };
        //draw_list->AddPolyline(line_points.data(), line_points.size(),
        //                       IM_COL32(255, 255, 0, 255), false, 2.0f);

        float y = y_zero - display_h * stats.efficiency();
        float y_mean = y_zero - display_h * stats.average_efficiency();

        /* x-coordinates of point */
        float x = bounding_rect.x_lo() + lm + i * bar_w + 0.5 * bar_w;

        line_points.push_back(ImVec2(x, y_mean));

        draw_list->AddCircleFilled(ImVec2(x, y), 2.0f, sample_color);

        ++i;
    }

    draw_list->AddPolyline(line_points.data(),
                           line_points.size(),
                           average_color,
                           false,
                           1.0f /*line width?*/);

    log && log(xtag("i", i));
} /*draw_gc_efficiency*/

void
DrawState::draw_gc_alloc_state(const GcStateDescription & gcstate,
                               const ImRect & canvas_rect,
                               ImDrawList * draw_list,
                               GenerationLayout * p_nursery_layout,
                               GenerationLayout * p_tenured_layout)
{
    constexpr float c_est_chart_text_height = 14.0;
    constexpr float c_min_h =  7;  // chart bar height
    constexpr float c_max_h = 40;  // chart bar height

    /* bounding rectange for nursery display */
    ImRect N_space_rect = canvas_rect.top_fraction(0.5,
                                                   c_min_h + c_est_chart_text_height,
                                                   c_max_h + c_est_chart_text_height);

    //assert(N_space_rect.height() >= c_min_h + c_est_chart_text_height);
    //assert(N_space_rect.height() <= c_max_h + c_est_chart_text_height);

    /* rectangle representing allocated nursery range */
    //float N_x1 = 0.0;
    GenerationLayout N_layout;

    draw_nursery(gcstate,
                 true /*with_labels*/,
                 N_space_rect,
                 draw_list,
                 &N_layout);

    float N_x1 = N_layout.chart_nolabel_rect_.x_hi();

    if (p_nursery_layout)
        *p_nursery_layout = N_layout;

//    if (p_nursery_alloc_rect)
//        *p_nursery_alloc_rect = N_layout.to_alloc_rect();

    /* N0_to_size..N_to_scale: in bytes */
    std::size_t N_to_scale = gcstate.gen_state_v_[gen2int(generation::nursery)].tospace_scale_;

    /* display_w .. N0_h : viewportcoords */
    std::size_t display_w = canvas_rect.width();

    std::size_t x0 = canvas_rect.x_lo();
    std::size_t x1 = canvas_rect.x_hi();

    // now turn to Tenured space

    std::size_t T_to_scale = gcstate.gen_state_v_[gen2int(generation::tenured)].tospace_scale_;

    /* want to put to-scale image of nursery next to to-scale image of tenured;
     * but also want space between them.
     */
    float TplusN_to_scale = N_to_scale + T_to_scale;
    /* space between T, N images */
    float TplusN_spacer = 10;

    /* bounding rectange for tenured display */
    ImRect T_space_rect = (canvas_rect
                           .within_bottom_margin(c_est_chart_text_height)
                           .bottom_fraction(0.5,
                                            c_min_h + c_est_chart_text_height,
                                            c_max_h + c_est_chart_text_height));

    if (N_space_rect.y_hi() > T_space_rect.y_lo()) {
        T_space_rect = T_space_rect.translate(ImVec2(0, N_space_rect.y_hi() - T_space_rect.y_lo()));
    }

    assert(T_space_rect.y_lo() >= N_space_rect.y_hi());

    /* for smaller image of nursery */
    //std::size_t t_y0 = canvas_rect.y_lo() + 70 + alloc_height + 20;

    /* for side-by-side tenured + nursery, with both on same scale
     * 2nd term is horiz space used for N label like 'Mem: 28k'
     */
    std::size_t adj_display_w = display_w - (N_space_rect.x_hi() - N_x1);

    /* bounding rectangle for secondary nursery display */
    ImRect np_rect(ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)),
                          T_space_rect.y_lo() + c_est_chart_text_height),
                   ImVec2(x0 + adj_display_w,
                          T_space_rect.y_hi()));

    // redraw nursery to same scale as tenured
    {
        draw_list->AddLine(N_space_rect.bottom_left(), np_rect.top_left(),
                           IM_COL32(128, 128, 128, 255) /*grey*/);
        draw_list->AddLine(ImVec2(N_x1, N_space_rect.y_hi()), np_rect.top_right(),
                           IM_COL32(128, 128, 128, 255) /*grey*/);

        draw_nursery(gcstate,
                     false /*no labels*/,
                     np_rect,
                     draw_list,
                     nullptr);
    }

    /* rectangle representing allocated tenured range */
    GenerationLayout T_layout;

    draw_tenured(gcstate,
                 true /*with labels*/,
                 ImRect(ImVec2(x0, T_space_rect.y_lo()),
                        ImVec2(x0 + (adj_display_w * (T_to_scale/TplusN_to_scale)) - TplusN_spacer,
                               T_space_rect.y_hi())),
                 draw_list,
                 &T_layout);

    if (p_tenured_layout)
        *p_tenured_layout = T_layout;

//    if (p_tenured_alloc_rect)
//        *p_tenured_alloc_rect = T_layout.to_alloc_rect();

} /*draw_gc_alloc_state*/

void
DrawState::draw_gc_state(const AppState & app_state,
                         const GcStateDescription & gcstate,
                         const ImRect & canvas_rect,
                         ImDrawList * draw_list,
                         GenerationLayout * p_nursery_layout,
                         GenerationLayout * p_tenured_layout,
                         ImRect * p_history_rect)
{
    // draw stuff
    draw_list->AddRect(canvas_rect.top_left(),
                       canvas_rect.bottom_right(),
                       IM_COL32(255, 255, 255, 255));

    /* TODO: does this reset coord space? */
    ImRect alloc_rect;
    {
        ImGui::BeginChild("top pane", ImVec2(0, 200), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeY);

        alloc_rect = ImRect(canvas_rect.top_left() + ImGui::GetWindowContentRegionMin(),
                            canvas_rect.top_left() + ImGui::GetWindowContentRegionMax());
        ImRect draw_rect = alloc_rect.within_margin(ImRect(50, 10, 70, 10));

        draw_list->PushClipRect(draw_rect.top_left(), draw_rect.bottom_right());

        draw_gc_alloc_state(gcstate,
                            draw_rect,
                            draw_list,
                            p_nursery_layout,
                            p_tenured_layout
                            //p_nursery_alloc_rect,
                            //p_tenured_alloc_rect
            );

        draw_list->PopClipRect();

        ImGui::EndChild();
    }

    ImRect history_rect;
    {
        ImGui::BeginChild("left pane", ImVec2(800, 0), ImGuiChildFlags_Border | ImGuiChildFlags_ResizeX);

        /* history below alloc area */
        history_rect = ImRect(alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMin(),
                              alloc_rect.bottom_left() + ImGui::GetWindowContentRegionMax());

         if (p_history_rect)
             *p_history_rect = history_rect;

         draw_list->PushClipRect(history_rect.top_left(), history_rect.bottom_right());

         float lm = 50;
         float rm = 70;
         float tm = 10;
         std::size_t x0 = history_rect.x_lo() + lm;
         std::size_t x1 = history_rect.x_hi() - rm;
         std::size_t h_y0 = history_rect.y_lo() + tm;

         /* just incremental (nursery) collections */
         ImRect incremental_rect = history_rect.top_fraction(0.33);

         draw_gc_history(gcstate,
                         generation::nursery,
                         app_state.gc_->gc_history(),
                         incremental_rect,
                         false /*debug_flag*/,
                         draw_list);

         /* just full (nursery+tenured) collections */
         ImRect full_rect = history_rect.bottom_fraction(0.67).top_fraction(0.5);

         /* both nursery + full collections */
         draw_gc_history(gcstate,
                         generation::tenured,
                         app_state.gc_->gc_history(),
                         full_rect,
                         false /*debug_flag*/,
                         draw_list);

         ImRect efficiency_rect = history_rect.bottom_fraction(0.67).bottom_fraction(0.5);

         draw_gc_efficiency(gcstate,
                            app_state.gc_->gc_history(),
                            efficiency_rect,
                            false /*debug_flag*/,
                            draw_list);

         draw_list->PopClipRect();

         ImGui::EndChild();
    }

    //ImGui::Text("placeholder text"); // appears below history_rect

    /* BeginChild() again ? */

#ifdef NOPE
    draw_list->AddCircleFilled(ImVec2(canvas_p0.x + 50, canvas_p0.y + 50),
                               30.0f, IM_COL32(255, 0, 0, 255));

    draw_list->AddText(ImVec2(canvas_p0.x + 10, canvas_p0.y + 10),
                       IM_COL32(255, 255, 255, 255), "Hello 2D!");
#endif
}

ImRect
DrawState::map_src_alloc_to_screen(const GcCopyDetail & copy_detail,
                                   const ImRect & space_rect)
{
    // TODO: methods on copy_detail / and/or ImPoint

    auto [x_coord_lo, x_coord_hi] = space_rect.x_span();

    double w0 = copy_detail.src_offset_ / static_cast<double>(copy_detail.src_space_z_);
    float src0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi);

    double w1 = ((copy_detail.src_offset_ + copy_detail.z_)
                 / static_cast<double>(copy_detail.src_space_z_));
    float src1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi);

    return space_rect.with_x_span(src0_x, src1_x);
}

ImRect
DrawState::map_dest_alloc_to_screen(const GcCopyDetail & copy_detail,
                                    const ImRect & space_rect)
{
    /* for from-space, want to use full width of memory space */

    auto [x_coord_lo, x_coord_hi] = space_rect.x_span();

    // dest_space_z_ ?

    double w0 = copy_detail.dest_offset_ / static_cast<double>(copy_detail.dest_z_);
    float dest0_x = ((1.0 - w0) * x_coord_lo) + (w0 * x_coord_hi);

    double w1 = ((copy_detail.dest_offset_ + copy_detail.z_)
                 / static_cast<double>(copy_detail.dest_z_));
    float dest1_x = ((1.0 - w1) * x_coord_lo) + (w1 * x_coord_hi);

    return space_rect.with_x_span(dest0_x, dest1_x);
}

/* editor bait: animate_copy() */
void
DrawState::animate_gc_copy(const AppState & app_state,
                           const DrawState & draw_state,
                           ImDrawList * draw_list)
{
    /* NOTE: this only runs during GC copy.
     *       draw_state.gcw_nursery_layout_ and gcw_tenured_layout_
     *       are taken from snapshots made before GC began,
     *       ergo before to/from spaces were swapped
     */
    ImRect nursery_src_rect = draw_state.gcw_nursery_layout_.to_alloc_rect();
    ImRect tenured_src_rect = draw_state.gcw_tenured_layout_.to_alloc_rect();

    ImRect nursery_dest_rect = draw_state.gcw_nursery_layout_.mem_rect_from_;
    ImRect tenured_dest_rect;

    if (app_state.upto_ == generation::nursery) {
        tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_to_;
    } else {
        tenured_dest_rect = draw_state.gcw_tenured_layout_.mem_rect_from_;
    }

    std::size_t n_copy = app_state.copy_detail_v_.size();
    /* grade from black-to-white between lo_copy and hi_copy.
     * Note we allow animate_copy_hi_pct_ > 100.0
     */
    float lo_copy = 0.01 * std::max(0.0, draw_state.animate_copy_hi_pct_ - 14.0) * n_copy;
    float hi_copy = 0.01 * draw_state.animate_copy_hi_pct_ * n_copy;

    /* remember max copy offset seen in {nursery, tenured} space respectively,
     * so we can label it
     */
    std::size_t last_nursery_dest_offset = 0;
    ImRect last_nursery_dest_rect;
    std::size_t first_tenured_dest_offset = 0;
    ImRect first_tenured_dest_rect;
    std::size_t last_tenured_dest_offset = 0;
    ImRect last_tenured_dest_rect;

    std::size_t i_copy = 0;
    for (const auto & copy_detail : app_state.copy_detail_v_) {
        /* cutout for each copied object */
        {
            float wt = (i_copy > lo_copy) ? static_cast<float>(i_copy) / hi_copy : 0.0;
            /* grey fading to black */
            //ImU32 color = IM_COL32(wt*128, 64+wt*64, wt*128, 255);
            ImU32 color = IM_COL32( 96, 224, 255, 255);

            ImRect src_rect;

            if (copy_detail.src_gen_ == generation::nursery) {
                src_rect = map_src_alloc_to_screen(copy_detail, nursery_src_rect);
            } else {
                src_rect = map_src_alloc_to_screen(copy_detail, tenured_src_rect);
            }

            draw_list->AddRectFilled(src_rect.top_left(),
                                     src_rect.bottom_right(),
                                     color);
        }

        if (copy_detail.dest_gen_ == generation::nursery) {
            last_nursery_dest_rect = map_dest_alloc_to_screen(copy_detail, nursery_dest_rect);
            last_nursery_dest_offset = copy_detail.dest_offset_;
        } else if (copy_detail.dest_gen_ == generation::tenured) {
            last_tenured_dest_rect = map_dest_alloc_to_screen(copy_detail, tenured_dest_rect);
            last_tenured_dest_offset = copy_detail.dest_offset_;

            if (first_tenured_dest_rect.width() == 0) {
                first_tenured_dest_rect = last_tenured_dest_rect;
                first_tenured_dest_offset = copy_detail.dest_offset_;
            }
        }

        if (++i_copy >= hi_copy) {
            break;
        }
    }

    if (last_nursery_dest_rect.width() > 0.0) {
        //ImU32 color = IM_COL32(64, 255, static_cast<int>(64 + (128 * wt)), 255);
        ImU32 color = IM_COL32(  0, 96, 192, 255);

        draw_list->AddRectFilled(nursery_dest_rect.top_left(),
                                 last_nursery_dest_rect.bottom_right(),
                                 color);

        char buf[255];
        snprintf(buf, sizeof(buf), "N\u2081: %luk", last_nursery_dest_offset / 1024);

        auto textz = ImGui::CalcTextSize(buf);

        ImU32  text_color = IM_COL32(255, 255, 255, 255); /*black*/
        ImVec2 text_pos   = ImVec2(0.5 * (nursery_dest_rect.x_lo()
                                          + last_nursery_dest_rect.x_hi()
                                          - textz.x),
                                   nursery_dest_rect.y_mid() - 0.5 * textz.y);

        if (text_pos.x < nursery_dest_rect.x_lo())
            text_pos.x = nursery_dest_rect.x_lo() + 2;
        else if (text_pos.x < nursery_dest_rect.x_lo() + 0.5 * textz.x)
            text_pos.x = nursery_dest_rect.x_lo() + 0.5 * textz.x;

        draw_list->AddText(text_pos, text_color, buf);

    }

    if (last_tenured_dest_rect.width() > 0.0) {
        ImU32 color = IM_COL32(  0,  96, 192, 255);

        draw_list->AddRectFilled(first_tenured_dest_rect.top_left(),
                                 last_tenured_dest_rect.bottom_right(),
                                 color);

        char buf[255];
        snprintf(buf, sizeof(buf), "+%luk", (last_tenured_dest_offset - first_tenured_dest_offset) / 1024);

        auto textz = ImGui::CalcTextSize(buf);

        ImU32  text_color = IM_COL32(255, 255, 255, 255); /*black*/
        float x0 = first_tenured_dest_rect.x_lo();
        float x1 = last_tenured_dest_rect.x_hi();
        ImVec2 text_pos   = ImVec2(0.5 * (x0 + x1 - textz.x),
                                   tenured_dest_rect.y_mid() - 0.5 * textz.y);

        if (text_pos.x < x0 + 2)
            text_pos.x = x0 + 2;
        else if (text_pos.x < x0 + 0.5 * textz.x)
            text_pos.x = x0 + 0.5 * textz.x;

        draw_list->AddText(text_pos, text_color, buf);
    }
}

/* end DrawState.cpp */
