/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: hxbandwidthgraph.cpp,v 1.3.4.3 2004/07/09 01:48:55 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "hxbandwidthgraph.h"

#include <string.h>
#include <stdio.h>

/* 10 squares + 1 for head room */
#define VERTICAL_GRID_COUNT 11 /* Number of squares to display vertically */
#define HEAD_ROOM 1            /* Number of squares over 100% to display */
#define SECONDS_PER_TICK 0.5   /* Seconds per horizontal tick */

static void hx_bandwidth_graph_class_init (HXBandwidthGraphClass* klass);
static void hx_bandwidth_graph_init       (HXBandwidthGraph*      graph,
                                           HXBandwidthGraphClass* klass);

static void     hx_bandwidth_graph_realize      (GtkWidget*      widget);
static void     hx_bandwidth_graph_unrealize    (GtkWidget*      widget);
static gboolean hx_bandwidth_graph_expose       (GtkWidget*      widget,
                                                 GdkEventExpose* event);
static void     hx_bandwidth_graph_size_allocate(GtkWidget*      widget,
                                                 GtkAllocation*  allocation);
static void     hx_bandwidth_graph_size_request (GtkWidget*      widget,
                                                 GtkRequisition* requisition);

typedef struct _HXBandwidthData
{
    gdouble percent;
    gdouble time;
} HXBandwidthData;



static GtkWidgetClass *parent_class = NULL;

GType
hx_bandwidth_graph_get_type (void)
{
    static GType hx_bandwidth_graph_type = 0;

    if (!hx_bandwidth_graph_type)
    {
	static const GTypeInfo hx_bandwidth_graph_info =
	    {
		sizeof (HXBandwidthGraphClass),
		NULL,		/* base_init */
		NULL,		/* base_finalize */
		(GClassInitFunc) hx_bandwidth_graph_class_init,
		NULL,		/* class_finalize */
		NULL,		/* class_data */
		sizeof (HXBandwidthGraph),
		0,		/* n_preallocs */
		(GInstanceInitFunc) hx_bandwidth_graph_init,
		NULL,           /* value_table */
	    };

	hx_bandwidth_graph_type = g_type_register_static (GTK_TYPE_WIDGET, "HXBandwidthGraph",
                                                          &hx_bandwidth_graph_info, (GTypeFlags)0);
    }

    return hx_bandwidth_graph_type;
}

GtkWidget*
hx_bandwidth_graph_new(void)
{
    HXBandwidthGraph* graph = (HXBandwidthGraph*)g_object_new(HX_TYPE_BANDWIDTH_GRAPH, NULL);
    
    graph->y_tick_count = VERTICAL_GRID_COUNT;
    graph->head_room = HEAD_ROOM;
    graph->seconds_per_tick = SECONDS_PER_TICK;
    graph->ideal_bandwidth = 0;
    
    return GTK_WIDGET(graph);
}

static void
hx_bandwidth_graph_init(HXBandwidthGraph* graph,
                        HXBandwidthGraphClass* /* klass */)
{
    graph->y_tick_count = 0;
    graph->head_room = 0;
    graph->seconds_per_tick = 0.0;

    graph->points = NULL;
    graph->gc = NULL;
    graph->pixmap = NULL;
}

static void
hx_bandwidth_graph_class_init (HXBandwidthGraphClass* klass)
{
    GtkWidgetClass *widget_class;
    widget_class = GTK_WIDGET_CLASS (klass);
    parent_class = (GtkWidgetClass*) g_type_class_peek_parent (klass);

    widget_class->expose_event = hx_bandwidth_graph_expose;   
    widget_class->realize = hx_bandwidth_graph_realize;
    widget_class->unrealize = hx_bandwidth_graph_unrealize;
    widget_class->size_allocate = hx_bandwidth_graph_size_allocate;
    widget_class->size_request = hx_bandwidth_graph_size_request;
}

void
hx_bandwidth_graph_draw(HXBandwidthGraph* graph)
{
    GtkWidget* widget;
    gint width;
    gint height;
    gdouble y_pixels_per_tick;
    gdouble x_pixels_per_tick;
    
    g_return_if_fail(graph != NULL);
    
    widget = GTK_WIDGET(graph);

    if(!widget->window || !graph->gc)
    {
        return;
    }
    
    if(!graph->pixmap)
    {
        graph->pixmap = gdk_pixmap_new(widget->window,
                                       widget->allocation.width,
                                       widget->allocation.height,
                                       -1);
    }
        
    
    gdk_drawable_get_size(GDK_DRAWABLE(graph->pixmap),
                          &width,
                          &height);

    gdk_gc_set_foreground (graph->gc, &graph->background_color);

    gdk_draw_rectangle(graph->pixmap,
                       graph->gc,
                       TRUE,
                       0, 0,
                       width, height);

    y_pixels_per_tick = (gdouble)height / (gdouble)graph->y_tick_count;

    /* Draw the horizontal lines */
    gdouble y;
    guint rounded_y;

    gdk_gc_set_foreground (graph->gc, &graph->grid_color);
    
    for(y = y_pixels_per_tick; y < height; y += y_pixels_per_tick)
    {
        rounded_y = (guint)(y + 0.5);
        gdk_draw_line (graph->pixmap,
                       graph->gc,
                       0,
                       rounded_y,
                       width,
                       rounded_y);
    }

    /* No x lines */
    x_pixels_per_tick = y_pixels_per_tick;

    if(graph->ideal_bandwidth > 0)
    {
        /* Draw the bandwidth series */
        GList* iter;
        GList* first = NULL;
        GList* last = NULL;
        HXBandwidthData* point;
        HXBandwidthData* a;
        HXBandwidthData* b;

        /* Step 0: See if we have space for the whole series */
        first = g_list_first(graph->points);

        /* Step 1: Skip all points newer than our cut-off */
        iter = g_list_last(graph->points);
        while(iter)
        {
            point = (HXBandwidthData*) iter->data;
        
            if(point->time <= graph->cutoff_time)
            {
                last = iter;
                break;
            }

            iter = g_list_previous(iter);
        }
    
        if(first && last)
        {
            gdouble delta;
            gdouble graph_time;
            gboolean draw_left_to_right;
            gint x1 = -1;
            gint x2 = -1;
            gint y1 = -1;
            gint y2 = -1;

            gdk_gc_set_foreground (graph->gc, &graph->line_color);
            
            a = (HXBandwidthData*)first->data;
            b = (HXBandwidthData*)last->data;

            delta = b->time - a->time;
        
            graph_time = (graph->seconds_per_tick * (width / x_pixels_per_tick));

            if(delta < graph_time)
            {
                /* We can draw the whole shebang -- draw left to right */
                draw_left_to_right = TRUE;
            }
            else
            {
                /* More data than graph -- draw right to left */
                draw_left_to_right = FALSE;
            }
        
            /* Step 2: Draw until we get to the end of the graph */
            gdouble base_time; 
            gdouble graph_percent = 0.0;
            gboolean done = FALSE;
            
            graph_percent = (gdouble)(graph->y_tick_count + graph->head_room) / graph->y_tick_count;

            if(draw_left_to_right)
            {
                point = (HXBandwidthData*) first->data;            
                base_time = point->time;            
                iter = first;
            }
            else
            {
                point = (HXBandwidthData*) last->data;            
                base_time = point->time;            
                iter = last;
            }

            while(iter && !done)
            {
                gdouble bandwidth_percent_ideal;
                gdouble bandwidth_percent_graph;
                gdouble time_percent_graph;
                                        
                point = (HXBandwidthData*) iter->data;

                bandwidth_percent_ideal = point->percent;

                bandwidth_percent_graph = bandwidth_percent_ideal / graph_percent;

                if(bandwidth_percent_graph > 1.0)
                {
                    bandwidth_percent_graph = 1.0;
                }

                time_percent_graph = (point->time - base_time) / graph_time;

                if(time_percent_graph > 1.0)
                {
                    /* This shouldn't happen -- we should be drawing r->l in this
                       case, which would make this percentage negative. */
                    time_percent_graph = 1.0;
                }

                y2 = (gint)((1.0 - bandwidth_percent_graph) * height);

                if(draw_left_to_right)
                {
                    x2 = (gint)(time_percent_graph * width);
                }
                else
                {
                    /* time_percent_graph is negative */
                    x2 = (gint)((1.0 + time_percent_graph) * width);

                    if(x2 < 0 && (x1 >= 0 && y1 >= 0 && y2 >= 0))
                    {
                        /* extrapolate back to 0 */
                        gdouble m;
                        
                        m = (y2 - y1) / (x2 - x1);

                        x2 = 0;
                        y2 = (gint)(y1 - m * x1);
                    }

                    if(x2 <= 0)
                    {
                        done = TRUE;
                    }
                }
                
                if(x1 >= 0 && y1 >= 0 && x2 >= 0 && y2 >= 0)
                {                    
                    gdk_draw_line (graph->pixmap,
                                   graph->gc,
                                   x1, y1,
                                   x2, y2);
                }

                x1 = x2;
                y1 = y2;
                    
                if(draw_left_to_right)
                {
                    iter = g_list_next(iter);
                }
                else
                {
                    iter = g_list_previous(iter);
                }
            }
        
            /* Step 4: Remove trailing points older than 2 * the size of our graph. */
            gdouble delete_cutoff = b->time - 2 * graph_time;
        
            iter = first;
            while(iter)
            {
                point = (HXBandwidthData*) iter->data;
                if(point->time < delete_cutoff)
                {
                    GList* link = iter;
                    iter = g_list_next(iter);
                    g_free(point);
                    graph->points = g_list_delete_link(graph->points, link);
                }
                else
                {
                    iter = g_list_next(iter);
                }
            }
        }
    }
    
    /* Invalidate widget */
    gtk_widget_queue_draw_area(widget,
                               0,
                               0,
                               widget->allocation.width,
                               widget->allocation.height);

}

static gboolean
hx_bandwidth_graph_expose (GtkWidget*      widget,
                           GdkEventExpose* event)
{
    HXBandwidthGraph* graph;
    
    g_return_val_if_fail(widget != NULL, FALSE); 
    g_return_val_if_fail(HX_IS_BANDWIDTH_GRAPH(widget), FALSE); 
    
    graph = HX_BANDWIDTH_GRAPH(widget);
     
    if (GTK_WIDGET_DRAWABLE (widget))
    {
        if(graph->pixmap)
        {        
            gdk_draw_drawable(widget->window,
                              widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
                              graph->pixmap,                          
                              event->area.x,
                              event->area.y,
                              event->area.x,
                              event->area.y,
                              event->area.width,
                              event->area.height);
        }
    }

    return FALSE;
}

static void
hx_bandwidth_graph_realize (GtkWidget *widget)
{
    HXBandwidthGraph *graph;
    GdkWindowAttr attributes;
    gint attributes_mask;
    
    g_return_if_fail (widget != NULL);
    g_return_if_fail (HX_BANDWIDTH_GRAPH (widget));

    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
    graph = HX_BANDWIDTH_GRAPH (widget);

    attributes.x = widget->allocation.x;
    attributes.y = widget->allocation.y;
    attributes.width = widget->allocation.width;
    attributes.height = widget->allocation.height;
    attributes.wclass = GDK_INPUT_OUTPUT;
    attributes.window_type = GDK_WINDOW_CHILD;
    attributes.event_mask = gtk_widget_get_events (widget) | 
                            GDK_EXPOSURE_MASK;
    
    attributes.visual = gtk_widget_get_visual (widget);
    attributes.colormap = gtk_widget_get_colormap (widget);

    attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
    widget->window = gdk_window_new (widget->parent->window, &attributes, attributes_mask);

    widget->style = gtk_style_attach (widget->style, widget->window);

    gdk_window_set_user_data (widget->window, widget);

    gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);

    /* Create the gc */
    GdkColormap* colormap;
    gboolean result;
    GdkGCValues values;
    GdkGCValuesMask mask;        
            
    gdk_color_parse ("darkgreen", &graph->grid_color);    
    gdk_color_parse ("black", &graph->background_color);    
    gdk_color_parse ("green", &graph->line_color);    

    colormap = gtk_widget_get_colormap(widget);

    result = gdk_colormap_alloc_color(colormap,
                                      &graph->grid_color,
                                      FALSE,
                                      TRUE);            
    g_return_if_fail (result);

    result = gdk_colormap_alloc_color(colormap,
                                      &graph->background_color,
                                      FALSE,
                                      TRUE);            
    g_return_if_fail (result);

    result = gdk_colormap_alloc_color(colormap,
                                      &graph->line_color,
                                      FALSE,
                                      TRUE);            
    g_return_if_fail (result);

    values.function = GDK_COPY;
    values.line_width = 1;
    values.join_style = GDK_JOIN_ROUND;
    values.cap_style = GDK_CAP_ROUND;

    mask = (GdkGCValuesMask)(GDK_GC_FUNCTION |
                             GDK_GC_LINE_WIDTH |
                             GDK_GC_CAP_STYLE |
                             GDK_GC_JOIN_STYLE);
    
    graph->gc = gdk_gc_new_with_values(widget->window, &values, mask);            
    
    hx_bandwidth_graph_draw(graph);
}

static void
hx_bandwidth_graph_unrealize (GtkWidget *widget)
{
    HXBandwidthGraph *graph;

    g_return_if_fail (widget != NULL);
    g_return_if_fail (HX_BANDWIDTH_GRAPH (widget));

    graph = HX_BANDWIDTH_GRAPH (widget);

    if(graph->gc)
    {
        g_object_unref(graph->gc);
        graph->gc = NULL;
    }

    if (GTK_WIDGET_CLASS (parent_class)->unrealize)
    {
        (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
    }
}

static void
hx_bandwidth_graph_size_allocate (GtkWidget*     widget,
                                  GtkAllocation* allocation)
{
    HXBandwidthGraph* graph;
    
    g_return_if_fail (widget != NULL);
    g_return_if_fail (HX_IS_BANDWIDTH_GRAPH (widget));
    g_return_if_fail (allocation != NULL);

    graph = HX_BANDWIDTH_GRAPH(widget);
    
    widget->allocation = *allocation;
    if (GTK_WIDGET_REALIZED (widget))
    {
        if(graph->pixmap)
        {
            g_object_unref(graph->pixmap);
            graph->pixmap = NULL;
        }
        
        gdk_window_move_resize (widget->window,
                                allocation->x, allocation->y,
                                allocation->width, allocation->height);

        hx_bandwidth_graph_draw(graph);
    }
}

static void 
hx_bandwidth_graph_size_request (GtkWidget*      /* widget */,
                                 GtkRequisition* requisition)
{
    requisition->width = 1;
    requisition->height = 1;
}

void
hx_bandwidth_graph_add_value(HXBandwidthGraph* graph,
                             guint             bandwidth)
{
    HXBandwidthData* point;
    GTimeVal time_val;
    
    g_return_if_fail(graph != NULL);

    point = g_new0(HXBandwidthData, 1);

    g_get_current_time(&time_val);

    if(graph->ideal_bandwidth)
    {
        point->percent = (gdouble)bandwidth / (gdouble)graph->ideal_bandwidth;
    }
    else
    {
        point->percent = 0;
    }

    point->time = (gdouble)time_val.tv_sec + (gdouble)time_val.tv_sec / 1e6;

    graph->points = g_list_append(graph->points, point);
}

void
hx_bandwidth_graph_update(HXBandwidthGraph* graph)
{
    GList* item;
    
    g_return_if_fail(graph != NULL);
    
    item = g_list_last(graph->points);

    if(item)
    {
        HXBandwidthData* point;
        
        point = (HXBandwidthData*)item->data;

        graph->cutoff_time = point->time;
        hx_bandwidth_graph_draw(graph);
    }
}

void
hx_bandwidth_graph_set_ideal_bandwidth(HXBandwidthGraph* graph,
                                       guint             bandwidth)
{
    g_return_if_fail(graph != NULL);

    graph->ideal_bandwidth = bandwidth;
}
