#include <config.h>

#include <gdkmm/pixbuf.h>

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <dirent.h>
#include <string.h>
#include <time.h>

#include <glib/gi18n.h>

#include <glibtop.h>
#include <glibtop/cpu.h>
#include <glibtop/mem.h>
#include <glibtop/swap.h>
#include <glibtop/netload.h>
#include <glibtop/netlist.h>
#include <math.h>

#include <algorithm>

#include "procman.h"
#include "load-graph.h"
#include "util.h"
#include "gsm_color_button.h"

void LoadGraph::clear_background()
{
    if (background) {
        cairo_pattern_destroy (background);
        this->background = NULL;
    }
}


unsigned LoadGraph::num_bars() const
{
    unsigned n;

    // keep 100 % num_bars == 0
    switch (static_cast<int>(this->draw_height / (fontsize + 14)))
    {
    case 0:
    case 1:
        n = 1;
        break;
    case 2:
    case 3:
        n = 2;
        break;
    case 4:
        n = 4;
        break;
    default:
        n = 5;
    }

    return n;
}



#define FRAME_WIDTH 4
static void draw_background(LoadGraph *graph) {
    GtkAllocation allocation;
    cairo_t *cr;
    guint i;
    unsigned num_bars;
    g_autofree gchar *caption;
    PangoLayout* layout;
    PangoFontDescription* font_desc;
    PangoRectangle extents;
    cairo_surface_t *surface;
    GdkRGBA fg, bg;

    num_bars = graph->num_bars();
    graph->graph_dely = (graph->draw_height - 15) / num_bars; /* round to int to avoid AA blur */
    graph->real_draw_height = graph->graph_dely * num_bars;
    graph->graph_delx = (graph->draw_width - 2.0 - graph->rmargin - graph->indent) / (LoadGraph::NUM_POINTS - 3);
    graph->graph_buffer_offset = (int) (1.5 * graph->graph_delx) + FRAME_WIDTH ;

    gtk_widget_get_allocation (graph->disp, &allocation);
    surface = gdk_window_create_similar_surface (gtk_widget_get_window (graph->disp), CAIRO_CONTENT_COLOR_ALPHA, allocation.width, allocation.height);
    cr = cairo_create (surface);

    GtkStyleContext *context = gtk_widget_get_style_context (ProcData::get_instance()->notebook);
    gtk_style_context_save (context);
    gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
    gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg);
    gtk_style_context_get_color (context, gtk_style_context_get_state (context), &fg);
    gtk_style_context_restore (context);

    // set the background color
    gdk_cairo_set_source_rgba (cr, &bg);
    cairo_paint (cr);

    layout = pango_cairo_create_layout (cr);
    gtk_style_context_save (context);
    gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL);
    gtk_style_context_get (context,
                           gtk_style_context_get_state (context),
                           GTK_STYLE_PROPERTY_FONT,
                           &font_desc, NULL);
    gtk_style_context_restore (context);
    pango_font_description_set_size (font_desc, 0.8 * graph->fontsize * PANGO_SCALE);
    pango_layout_set_font_description (layout, font_desc);
    pango_font_description_free (font_desc);

    /* draw frame */
    cairo_translate (cr, FRAME_WIDTH, FRAME_WIDTH);

    /* Draw background rectangle */
    cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
    cairo_rectangle (cr, graph->rmargin + graph->indent, 0,
                     graph->draw_width - graph->rmargin - graph->indent, graph->real_draw_height);
    cairo_fill(cr);

    cairo_set_line_width (cr, 1.0);
    cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);

    bool network_in_bits = ProcData::get_instance()->config.network_in_bits;
    for (i = 0; i <= num_bars; ++i) {
        double y;

        if (i == 0)
            y = 0.5 + graph->fontsize / 2.0;
        else if (i == num_bars)
            y = i * graph->graph_dely + 0.5;
        else
            y = i * graph->graph_dely + graph->fontsize / 2.0;

        gdk_cairo_set_source_rgba (cr, &fg);
        if (graph->type == LOAD_GRAPH_NET) {
            // operation orders matters so it's 0 if i == num_bars
            guint64 rate = graph->net.max - (i * graph->net.max / num_bars);
            caption = g_format_size_full (network_in_bits ? rate*8 : rate, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
            pango_layout_set_text (layout, caption, -1);
            pango_layout_get_extents (layout, NULL, &extents);
            cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2);
            pango_cairo_show_layout (cr, layout);
        } else {
            // operation orders matters so it's 0 if i == num_bars
            caption = g_strdup_printf("%d%%", 100 - i * (100 / num_bars));
            pango_layout_set_text (layout, caption, -1);
            pango_layout_get_extents (layout, NULL, &extents);
            cairo_move_to (cr, graph->indent - 1.0 * extents.width / PANGO_SCALE + 20, y - 1.0 * extents.height / PANGO_SCALE / 2);
            pango_cairo_show_layout (cr, layout);
        }

        if (i==0 || i==num_bars)
          cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0);
        else
          cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);
        cairo_move_to (cr, graph->rmargin + graph->indent - 3, i * graph->graph_dely + 0.5);
        cairo_line_to (cr, graph->draw_width - 0.5, i * graph->graph_dely + 0.5);
        cairo_stroke (cr);
    }

    const unsigned total_seconds = graph->speed * (LoadGraph::NUM_POINTS - 2) / 1000;

    for (unsigned int i = 0; i < 7; i++) {
        double x = (i) * (graph->draw_width - graph->rmargin - graph->indent) / 6;
        if (i==0 || i==6)
          cairo_set_source_rgba (cr, 0.70, 0.71, 0.70, 1.0);
        else
          cairo_set_source_rgba (cr, 0.89, 0.89, 0.89, 1.0);
        cairo_move_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, 0.5);
        cairo_line_to (cr, (ceil(x) + 0.5) + graph->rmargin + graph->indent, graph->real_draw_height + 4.5);
        cairo_stroke(cr);
        unsigned seconds = total_seconds - i * total_seconds / 6;
        const char* format;
        if (i == 0)
            format = dngettext(GETTEXT_PACKAGE, "%u second", "%u seconds", seconds);
        else
            format = "%u";
        caption = g_strdup_printf(format, seconds);
        pango_layout_set_text (layout, caption, -1);
        pango_layout_get_extents (layout, NULL, &extents);
        cairo_move_to (cr, ((ceil(x) + 0.5) + graph->rmargin + graph->indent) - (1.0 * extents.width / PANGO_SCALE/2), graph->draw_height - 1.0 * extents.height / PANGO_SCALE);
        gdk_cairo_set_source_rgba (cr, &fg);
        pango_cairo_show_layout (cr, layout);
    }
    g_object_unref(layout);
    cairo_stroke (cr);
    cairo_destroy (cr);
    graph->background = cairo_pattern_create_for_surface (surface);
    cairo_surface_destroy (surface);
}

/* Redraws the backing buffer for the load graph and updates the window */
void
load_graph_queue_draw (LoadGraph *graph)
{
    /* repaint */
    gtk_widget_queue_draw (graph->disp);
}

static int load_graph_update (gpointer user_data); // predeclare load_graph_update so we can compile ;)

static gboolean
load_graph_configure (GtkWidget *widget,
                      GdkEventConfigure *event,
                      gpointer data_ptr)
{
    GtkAllocation allocation;
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);

    gtk_widget_get_allocation (widget, &allocation);
    graph->draw_width = allocation.width - 2 * FRAME_WIDTH;
    graph->draw_height = allocation.height - 2 * FRAME_WIDTH;

    graph->clear_background();

    load_graph_queue_draw (graph);

    return TRUE;
}

static gboolean load_graph_draw (GtkWidget *widget, cairo_t *context, gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);
    GdkWindow *window;

    guint i, j;
    gdouble sample_width, x_offset;

    window = gtk_widget_get_window (graph->disp);

    /* Number of pixels wide for one graph point */
    sample_width = (float)(graph->draw_width - graph->rmargin - graph->indent) / (float)LoadGraph::NUM_POINTS;
    /* General offset */
    x_offset = graph->draw_width - graph->rmargin + (sample_width*2);

    /* Subframe offset */
    x_offset += graph->rmargin - ((sample_width / graph->frames_per_unit) * graph->render_counter);

    /* draw the graph */
    cairo_t* cr;

    cr = gdk_cairo_create (window);

    if (graph->background == NULL) {
        draw_background(graph);
    }
    cairo_set_source (cr, graph->background);
    cairo_paint (cr);

    cairo_set_line_width (cr, 1);
    cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
    cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
    cairo_rectangle (cr, graph->rmargin + graph->indent + FRAME_WIDTH + 1, FRAME_WIDTH - 1,
             graph->draw_width - graph->rmargin - graph->indent - 1, graph->real_draw_height + FRAME_WIDTH - 1);
    cairo_clip(cr);

    for (j = 0; j < graph->n; ++j) {
        cairo_move_to (cr, x_offset, (1.0f - graph->data[0][j]) * graph->real_draw_height);
        gdk_cairo_set_source_rgba (cr, &(graph->colors [j]));

        for (i = 1; i < LoadGraph::NUM_POINTS; ++i) {
            if (graph->data[i][j] == -1.0f)
                continue;
            cairo_curve_to (cr,
                       x_offset - ((i - 0.5f) * graph->graph_delx),
                       (1.0f - graph->data[i-1][j]) * graph->real_draw_height + 3.5f,
                       x_offset - ((i - 0.5f) * graph->graph_delx),
                       (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f,
                       x_offset - (i * graph->graph_delx),
                       (1.0f - graph->data[i][j]) * graph->real_draw_height + 3.5f);
        }
        cairo_stroke (cr);

    }

    cairo_destroy (cr);

    return TRUE;
}

static void
get_load (LoadGraph *graph)
{
    guint i;
    glibtop_cpu cpu;

    glibtop_get_cpu (&cpu);

#undef NOW
#undef LAST
#define NOW  (graph->cpu.times[graph->cpu.now])
#define LAST (graph->cpu.times[graph->cpu.now ^ 1])

    if (graph->n == 1) {
        NOW[0][CPU_TOTAL] = cpu.total;
        NOW[0][CPU_USED] = cpu.user + cpu.nice + cpu.sys;
    } else {
        for (i = 0; i < graph->n; i++) {
            NOW[i][CPU_TOTAL] = cpu.xcpu_total[i];
            NOW[i][CPU_USED] = cpu.xcpu_user[i] + cpu.xcpu_nice[i]
                + cpu.xcpu_sys[i];
        }
    }

    // on the first call, LAST is 0
    // which means data is set to the average load since boot
    // that value has no meaning, we just want all the
    // graphs to be aligned, so the CPU graph needs to start
    // immediately

    for (i = 0; i < graph->n; i++) {
        float load;
        float total, used;
        gchar *text;

        total = NOW[i][CPU_TOTAL] - LAST[i][CPU_TOTAL];
        used  = NOW[i][CPU_USED]  - LAST[i][CPU_USED];

        load = used / MAX(total, 1.0f);
        graph->data[0][i] = load;

        /* Update label */
        text = g_strdup_printf("%.1f%%", load * 100.0f);
        gtk_label_set_text(GTK_LABEL(graph->labels.cpu[i]), text);
        g_free(text);
    }

    graph->cpu.now ^= 1;

#undef NOW
#undef LAST
}


namespace
{

    void set_memory_label_and_picker(GtkLabel* label, GSMColorButton* picker,
                                   guint64 used, guint64 total, double percent)
    {
        char* used_text;
        char* total_text;
        char* text;

        used_text = g_format_size_full(used, G_FORMAT_SIZE_IEC_UNITS);
        total_text = g_format_size_full(total, G_FORMAT_SIZE_IEC_UNITS);
        if (total == 0) {
            text = g_strdup(_("not available"));
        } else {
            // xgettext: 540MiB (53 %) of 1.0 GiB
            text = g_strdup_printf(_("%s (%.1f%%) of %s"), used_text, 100.0 * percent, total_text);
        }
        gtk_label_set_text(label, text);
        g_free(used_text);
        g_free(total_text);
        g_free(text);

        if (picker)
            gsm_color_button_set_fraction(picker, percent);
    }
}

static void
get_memory (LoadGraph *graph)
{
    float mempercent, swappercent;

    glibtop_mem mem;
    glibtop_swap swap;

    glibtop_get_mem (&mem);
    glibtop_get_swap (&swap);

    /* There's no swap on LiveCD : 0.0f is better than NaN :) */
    swappercent = (swap.total ? (float)swap.used / (float)swap.total : 0.0f);
    mempercent  = (float)mem.user  / (float)mem.total;

    set_memory_label_and_picker(GTK_LABEL(graph->labels.memory),
                                GSM_COLOR_BUTTON(graph->mem_color_picker),
                                mem.user, mem.total, mempercent);

    set_memory_label_and_picker(GTK_LABEL(graph->labels.swap),
                                GSM_COLOR_BUTTON(graph->swap_color_picker),
                                swap.used, swap.total, swappercent);

    graph->data[0][0] = mempercent;
    graph->data[0][1] = swappercent;
}

/* Nice Numbers for Graph Labels after Paul Heckbert
   nicenum: find a "nice" number approximately equal to x.
   Round the number if round=1, take ceiling if round=0    */

static double
nicenum (double x, int round)
{
    int expv;				/* exponent of x */
    double f;				/* fractional part of x */
    double nf;				/* nice, rounded fraction */

    expv = floor( log10(x) );
    f = x/pow( 10.0, expv );		/* between 1 and 10 */
    if (round) {
        if ( f < 1.5 ) nf = 1.0;
        else if ( f < 3.0 ) nf = 2.0;
        else if ( f < 7.0 ) nf = 5.0;
        else nf = 10.0;
    } else {
        if ( f <= 1.0 ) nf = 1.0;
        else if ( f <= 2.0 ) nf = 2.0;
        else if ( f <= 5.0 ) nf = 5.0;
        else nf = 10.0;
    }
    return nf * pow(10.0, expv);
}

static void
net_scale (LoadGraph *graph, guint64 din, guint64 dout)
{
    graph->data[0][0] = 1.0f * din / graph->net.max;
    graph->data[0][1] = 1.0f * dout / graph->net.max;

    guint64 dmax = std::max(din, dout);
    graph->net.values[graph->net.cur] = dmax;
    graph->net.cur = (graph->net.cur + 1) % LoadGraph::NUM_POINTS;

    guint64 new_max;
    // both way, new_max is the greatest value
    if (dmax >= graph->net.max)
        new_max = dmax;
    else
        new_max = *std::max_element(&graph->net.values[0],
                                    &graph->net.values[LoadGraph::NUM_POINTS]);

    //
    // Round network maximum
    //

    const guint64 bak_max(new_max);

    if (ProcData::get_instance()->config.network_in_bits) {
        // nice number is for the ticks
        unsigned ticks = graph->num_bars();

        // gets messy at low values due to division by 8
        guint64 bit_max = std::max( new_max*8, G_GUINT64_CONSTANT(10000) );

        // our tick size leads to max
        double d = nicenum(bit_max/ticks, 0);
        bit_max = ticks * d;
        new_max = bit_max / 8;

        procman_debug("bak*8 %" G_GUINT64_FORMAT ", ticks %d, d %f"
                      ", bit_max %" G_GUINT64_FORMAT ", new_max %" G_GUINT64_FORMAT,
                      bak_max*8, ticks, d, bit_max, new_max );
    } else {
        // round up to get some extra space
        // yes, it can overflow
        new_max = 1.1 * new_max;
        // make sure max is not 0 to avoid / 0
        // default to 1 KiB
        new_max = std::max(new_max, G_GUINT64_CONSTANT(1024));

        // decompose new_max = coef10 * 2**(base10 * 10)
        // where coef10 and base10 are integers and coef10 < 2**10
        //
        // e.g: ceil(100.5 KiB) = 101 KiB = 101 * 2**(1 * 10)
        //      where base10 = 1, coef10 = 101, pow2 = 16

        guint64 pow2 = std::floor(log2(new_max));
        guint64 base10 = pow2 / 10.0;
        guint64 coef10 = std::ceil(new_max / double(G_GUINT64_CONSTANT(1) << (base10 * 10)));
        g_assert(new_max <= (coef10 * (G_GUINT64_CONSTANT(1) << (base10 * 10))));

        // then decompose coef10 = x * 10**factor10
        // where factor10 is integer and x < 10
        // so we new_max has only 1 significant digit

        guint64 factor10 = std::pow(10.0, std::floor(std::log10(coef10)));
        coef10 = std::ceil(coef10 / double(factor10)) * factor10;

        // then make coef10 divisible by num_bars
        if (coef10 % graph->num_bars() != 0)
            coef10 = coef10 + (graph->num_bars() - coef10 % graph->num_bars());
        g_assert(coef10 % graph->num_bars() == 0);
        new_max = coef10 * (G_GUINT64_CONSTANT(1) << guint64(base10 * 10));
        procman_debug("bak %" G_GUINT64_FORMAT " new_max %" G_GUINT64_FORMAT
                      "pow2 %" G_GUINT64_FORMAT " coef10 %" G_GUINT64_FORMAT,
                      bak_max, new_max, pow2, coef10);
    }

    if (bak_max > new_max) {
        procman_debug("overflow detected: bak=%" G_GUINT64_FORMAT
                      " new=%" G_GUINT64_FORMAT,
                      bak_max, new_max);
        new_max = bak_max;
    }

    // if max is the same or has decreased but not so much, don't
    // do anything to avoid rescaling
    if ((0.8 * graph->net.max) < new_max && new_max <= graph->net.max)
        return;

    const double scale = 1.0f * graph->net.max / new_max;

    for (size_t i = 0; i < LoadGraph::NUM_POINTS; i++) {
        if (graph->data[i][0] >= 0.0f) {
            graph->data[i][0] *= scale;
            graph->data[i][1] *= scale;
        }
    }

    procman_debug("rescale dmax = %" G_GUINT64_FORMAT
                  " max = %" G_GUINT64_FORMAT
                  " new_max = %" G_GUINT64_FORMAT,
                  dmax, graph->net.max, new_max);

    graph->net.max = new_max;

    // force the graph background to be redrawn now that scale has changed
    graph->clear_background();
}

static void
get_net (LoadGraph *graph)
{
    glibtop_netlist netlist;
    char **ifnames;
    guint32 i;
    guint64 in = 0, out = 0;
#if GLIB_CHECK_VERSION(2,61,2)
    gint64 time;
#else
    GTimeVal time;
#endif
    guint64 din, dout;

    ifnames = glibtop_get_netlist(&netlist);

    for (i = 0; i < netlist.number; ++i)
    {
        glibtop_netload netload;
        glibtop_get_netload (&netload, ifnames[i]);

        if (netload.if_flags & (1 << GLIBTOP_IF_FLAGS_LOOPBACK))
            continue;

        /* Skip interfaces without any IPv4/IPv6 address (or
         those with only a LINK ipv6 addr) However we need to
         be able to exclude these while still keeping the
         value so when they get online (with NetworkManager
         for example) we don't get a suddent peak.  Once we're
         able to get this, ignoring down interfaces will be
         possible too.  */
        if (not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS6)
             and netload.scope6 != GLIBTOP_IF_IN6_SCOPE_LINK)
            and not (netload.flags & (1 << GLIBTOP_NETLOAD_ADDRESS)))
            continue;

        /* Don't skip interfaces that are down (GLIBTOP_IF_FLAGS_UP)
           to avoid spikes when they are brought up */

        in  += netload.bytes_in;
        out += netload.bytes_out;
    }

    g_strfreev(ifnames);

#if GLIB_CHECK_VERSION(2,61,2)
    time = g_get_real_time ();

    if (in >= graph->net.last_in && out >= graph->net.last_out &&
        graph->net.time != 0) {
        float dtime = (double) (time - graph->net.time) / G_USEC_PER_SEC;
#else
    g_get_current_time (&time);
    if (in >= graph->net.last_in && out >= graph->net.last_out &&
        graph->net.time.tv_sec != 0) {
        float dtime;
        dtime = time.tv_sec - graph->net.time.tv_sec +
            (double) (time.tv_usec - graph->net.time.tv_usec) / G_USEC_PER_SEC;
#endif
        din   = static_cast<guint64>((in  - graph->net.last_in)  / dtime);
        dout  = static_cast<guint64>((out - graph->net.last_out) / dtime);
    } else {
        /* Don't calc anything if new data is less than old (interface
           removed, counters reset, ...) or if it is the first time */
        din  = 0;
        dout = 0;
    }

    graph->net.last_in  = in;
    graph->net.last_out = out;
    graph->net.time     = time;

    net_scale(graph, din, dout);

    bool network_in_bits = ProcData::get_instance()->config.network_in_bits;
    g_autofree gchar *str=NULL, *formatted_str=NULL;

    str = g_format_size_full (network_in_bits ? din*8 : din, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
    formatted_str = g_strdup_printf(_("%s/s"), str);
    gtk_label_set_text (GTK_LABEL (graph->labels.net_in), formatted_str);

    str = g_format_size_full (network_in_bits ? in*8 : in, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
    gtk_label_set_text (GTK_LABEL (graph->labels.net_in_total), str);

    str = g_format_size_full (network_in_bits ? dout*8 : dout, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
    formatted_str = g_strdup_printf(_("%s/s"), str);
    gtk_label_set_text (GTK_LABEL (graph->labels.net_out), formatted_str);

    str = g_format_size_full (network_in_bits ? out*8 : out, network_in_bits ? G_FORMAT_SIZE_BITS : G_FORMAT_SIZE_DEFAULT);
    gtk_label_set_text (GTK_LABEL (graph->labels.net_out_total), str);
}


/* Updates the load graph when the timeout expires */
static gboolean
load_graph_update (gpointer user_data)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(user_data);

    if (graph->render_counter == graph->frames_per_unit - 1) {
        std::rotate(&graph->data[0], &graph->data[LoadGraph::NUM_POINTS - 1], &graph->data[LoadGraph::NUM_POINTS]);

        switch (graph->type) {
        case LOAD_GRAPH_CPU:
            get_load(graph);
            break;
        case LOAD_GRAPH_MEM:
            get_memory(graph);
            break;
        case LOAD_GRAPH_NET:
            get_net(graph);
            break;
        default:
            g_assert_not_reached();
        }
    }

    if (graph->draw)
        load_graph_queue_draw (graph);

    graph->render_counter++;

    if (graph->render_counter >= graph->frames_per_unit)
        graph->render_counter = 0;

    return TRUE;
}



LoadGraph::~LoadGraph()
{
  load_graph_stop(this);

  if (timer_index)
    g_source_remove(timer_index);

  clear_background();
}



static gboolean
load_graph_destroy (GtkWidget *widget, gpointer data_ptr)
{
    LoadGraph * const graph = static_cast<LoadGraph*>(data_ptr);

    delete graph;

    return FALSE;
}


LoadGraph::LoadGraph(guint type)
  : fontsize(8.0),
    rmargin(3.5 * fontsize),
    indent(24.0),
    n(0),
    type(type),
    speed(0),
    draw_width(0),
    draw_height(0),
    render_counter(0),
    frames_per_unit(10), // this will be changed but needs initialising
    graph_dely(0),
    real_draw_height(0),
    graph_delx(0.0),
    graph_buffer_offset(0),
    main_widget(NULL),
    disp(NULL),
    background(NULL),
    timer_index(0),
    draw(FALSE),
    mem_color_picker(NULL),
    swap_color_picker(NULL)
{
    LoadGraph * const graph = this;

    // FIXME:
    // on configure, graph->frames_per_unit = graph->draw_width/(LoadGraph::NUM_POINTS);
    // knock FRAMES down to 5 until cairo gets faster

    switch (type) {
    case LOAD_GRAPH_CPU:
        memset(&cpu, 0, sizeof cpu);
        n = ProcData::get_instance()->config.num_cpus;

        for(guint i = 0; i < G_N_ELEMENTS(labels.cpu); ++i)
            labels.cpu[i] = gtk_label_new(NULL);

        break;

    case LOAD_GRAPH_MEM:
        n = 2;
        labels.memory = gtk_label_new(NULL);
        labels.swap = gtk_label_new(NULL);
        break;

    case LOAD_GRAPH_NET:
        memset(&net, 0, sizeof net);
        n = 2;
        net.max = 1;
        labels.net_in = gtk_label_new(NULL);
        labels.net_in_total = gtk_label_new(NULL);
        labels.net_out = gtk_label_new(NULL);
        labels.net_out_total = gtk_label_new(NULL);
        break;
    }

    speed  = ProcData::get_instance()->config.graph_update_interval;

    colors.resize(n);

    switch (type) {
    case LOAD_GRAPH_CPU:
        memcpy(&colors[0], ProcData::get_instance()->config.cpu_color,
               n * sizeof colors[0]);
        break;
    case LOAD_GRAPH_MEM:
        colors[0] = ProcData::get_instance()->config.mem_color;
        colors[1] = ProcData::get_instance()->config.swap_color;
        mem_color_picker = gsm_color_button_new (&colors[0],
                                                 GSMCP_TYPE_PIE);
        swap_color_picker = gsm_color_button_new (&colors[1],
                                                  GSMCP_TYPE_PIE);
        break;
    case LOAD_GRAPH_NET:
        colors[0] = ProcData::get_instance()->config.net_in_color;
        colors[1] = ProcData::get_instance()->config.net_out_color;
        break;
    }

    timer_index = 0;
    render_counter = (frames_per_unit - 1);
    draw = FALSE;

    main_widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
    gtk_widget_set_size_request(main_widget, -1, LoadGraph::GRAPH_MIN_HEIGHT);
    gtk_widget_show (main_widget);

    disp = gtk_drawing_area_new ();
    gtk_widget_show (disp);
    g_signal_connect (G_OBJECT (disp), "draw", G_CALLBACK (load_graph_draw), graph);
    g_signal_connect (G_OBJECT(disp), "configure_event",
                      G_CALLBACK (load_graph_configure), graph);
    g_signal_connect (G_OBJECT(disp), "destroy",
                      G_CALLBACK (load_graph_destroy), graph);

    gtk_widget_set_events (disp, GDK_EXPOSURE_MASK);

    gtk_box_pack_start (GTK_BOX (main_widget), disp, TRUE, TRUE, 0);


    /* Allocate data in a contiguous block */
    data_block = std::vector<float>(n * LoadGraph::NUM_POINTS, -1.0f);

    for (guint i = 0; i < LoadGraph::NUM_POINTS; ++i)
        data[i] = &data_block[0] + i * n;

    gtk_widget_show_all (main_widget);
}

void
load_graph_start (LoadGraph *graph)
{
    if(!graph->timer_index) {

        load_graph_update(graph);

        graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
                                        load_graph_update,
                                        graph);
    }

    graph->draw = TRUE;
}

void
load_graph_stop (LoadGraph *graph)
{
    /* don't draw anymore, but continue to poll */
    graph->draw = FALSE;
}

void
load_graph_change_speed (LoadGraph *graph,
             guint new_speed)
{
    if (graph->speed == new_speed)
        return;

    graph->speed = new_speed;

    if(graph->timer_index) {
        g_source_remove (graph->timer_index);
        graph->timer_index = g_timeout_add (graph->speed / graph->frames_per_unit,
                                        load_graph_update,
                                        graph);
    }

    graph->clear_background();
}


LoadGraphLabels*
load_graph_get_labels (LoadGraph *graph)
{
    return &graph->labels;
}

GtkWidget*
load_graph_get_widget (LoadGraph *graph)
{
    return graph->main_widget;
}

GtkWidget*
load_graph_get_mem_color_picker(LoadGraph *graph)
{
    return graph->mem_color_picker;
}

GtkWidget*
load_graph_get_swap_color_picker(LoadGraph *graph)
{
    return graph->swap_color_picker;
}
