/*
 Adapted From GtkVuMeter by Todd Goyen
 Copyright  2003  Todd Goyen
 wettoad@knighthoodofbuh.or

 Improved rendering performance (no individual drawline calls)

*/
#include <string.h>
#include "gtkvumeter.h"

#define MIN_HORIZONTAL_VUMETER_WIDTH   40
#define HORIZONTAL_VUMETER_HEIGHT  20
#define VERTICAL_VUMETER_WIDTH     20
#define MIN_VERTICAL_VUMETER_HEIGHT    40

G_DEFINE_TYPE (GtkVuMeter, gtk_vu_meter, GTK_TYPE_DRAWING_AREA);

static GdkColor default_f_gradient_keys[3] = {{0,65535,0,0},{0,65535,65535,0},{0,0,65535,0}};
static GdkColor default_b_gradient_keys[3] = {{0,49151,0,0},{0,49151,49151,0},{0,0,49151,0}};

static gboolean gtk_vu_meter_expose (GtkWidget *vumeter, GdkEventExpose *event);
static void gtk_vu_meter_setup_colors (GtkVuMeter *vumeter);
static void gtk_vu_meter_free_colors (GtkVuMeter *vumeter);
static void free_drawbuf(guchar *pixels, gpointer data);
static void gtk_vu_meter_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_vu_meter_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
static gint gtk_vu_meter_sound_level_to_draw_level (GtkVuMeter *vumeter);
static void gtk_vu_meter_realize (GtkWidget *widget);

static void gtk_vu_meter_class_init (GtkVuMeterClass *class) {
	GtkWidgetClass *widget_class;
	widget_class = GTK_WIDGET_CLASS (class);
	widget_class->realize = gtk_vu_meter_realize;
	widget_class->expose_event = gtk_vu_meter_expose;
	widget_class->size_request = gtk_vu_meter_size_request;
	widget_class->size_allocate = gtk_vu_meter_size_allocate;    
}

static void  gtk_vu_meter_init (GtkVuMeter *vumeter) {
	vumeter->vertical=TRUE;
    	gtk_vu_meter_set_gradient(vumeter, 3, default_f_gradient_keys, 3, default_b_gradient_keys);
        gtk_vu_meter_setup_colors(vumeter);
}

static void gtk_vu_meter_realize (GtkWidget *widget)
{
	GtkVuMeter *vumeter;
	GdkWindowAttr attributes;
	gint attributes_mask;

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

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
	vumeter = GTK_VU_METER (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_NORMAL);

	gtk_vu_meter_setup_colors (vumeter);
}

void gtk_vu_meter_set_gradient (GtkVuMeter *vumeter, gint f_gradient_key_count, GdkColor *f_gradient_keys, gint b_gradient_key_count, GdkColor *b_gradient_keys) {
    //XXX : memdup is a bad idea ?
    GdkColor *fgk = g_memdup(f_gradient_keys, f_gradient_key_count*sizeof(GdkColor));
    GdkColor *bgk = g_memdup(b_gradient_keys, b_gradient_key_count*sizeof(GdkColor));
    g_return_if_fail (fgk != NULL);
    g_return_if_fail (bgk != NULL);

    vumeter->f_gradient_keys = fgk;
    vumeter->f_gradient_key_count=f_gradient_key_count;
    vumeter->b_gradient_keys = bgk;
    vumeter->b_gradient_key_count=b_gradient_key_count;	
}
static void gtk_vu_meter_free_colors (GtkVuMeter *vumeter) {
	// TODO : free pixmaps	
}

static void  gtk_vu_meter_setup_colors (GtkVuMeter *vumeter) {

	gint i,j;
	guchar *f_drawbuf, *b_drawbuf, f_r, f_g, f_b, b_r, b_g, b_b;
	gint f_key_len, b_key_len;
	gint f_key_i, b_key_i;
	gdouble f_key_pos, b_key_pos;
	GdkColor *fgk, *bgk;

	gint h=GTK_WIDGET(vumeter)->allocation.height;
	gint w=GTK_WIDGET(vumeter)->allocation.width;

	// Clean every previous colors and buffers
	gtk_vu_meter_free_colors (vumeter);

	if (vumeter->vertical == TRUE) {
		vumeter->colors = MAX(h, 0);
	} else {
		vumeter->colors = MAX(w, 0);
	}

	// Allocate a memory buffers to hold the gradients
	f_drawbuf=g_malloc(h*w*3);
	b_drawbuf=g_malloc(h*w*3);
	g_return_if_fail (f_drawbuf != NULL);
	g_return_if_fail (b_drawbuf != NULL);

	// Compute some values before looping for gradient generation
	f_key_len = vumeter->colors / (vumeter->f_gradient_key_count-1) + 1;
	b_key_len = vumeter->colors / (vumeter->b_gradient_key_count-1) + 1;
	fgk=vumeter->f_gradient_keys;
	bgk=vumeter->b_gradient_keys;

	for (i=0; i<vumeter->colors; i++) {
		// Compute the current position in the gradient keys
		f_key_i=i/f_key_len;
		f_key_pos=((gdouble) (i%f_key_len)/f_key_len);
		b_key_i=i/f_key_len;
		b_key_pos=((gdouble) (i%b_key_len)/b_key_len);

		/* Generate the Colours */
		/* foreground */
		f_r = ( fgk[f_key_i].red*(1.0-f_key_pos) + fgk[f_key_i+1].red*f_key_pos ) / 256;
		f_g = ( fgk[f_key_i].green*(1.0-f_key_pos) + fgk[f_key_i+1].green*f_key_pos ) / 256;
		f_b = ( fgk[f_key_i].blue*(1.0-f_key_pos) + fgk[f_key_i+1].blue*f_key_pos ) / 256;
		/* background */
		b_r = ( bgk[b_key_i].red*(1.0-b_key_pos) + bgk[b_key_i+1].red*b_key_pos ) / 256;
		b_g = ( bgk[b_key_i].green*(1.0-b_key_pos) + bgk[b_key_i+1].green*b_key_pos ) / 256;
		b_b = ( bgk[b_key_i].blue*(1.0-b_key_pos) + bgk[b_key_i+1].blue*b_key_pos ) / 256;

		/* Apply the color in the drawbufs */
		if (vumeter->vertical == TRUE) {
			// Vertical mode : draw directly the whole line of identical color
			for (j=3*w*i; j<3*w*(i+1); ) {
				f_drawbuf[j++]=f_r;
				f_drawbuf[j++]=f_g;
				f_drawbuf[j++]=f_b;
			}
			for (j=3*w*i; j<3*w*(i+1); ) {
				b_drawbuf[j++]=b_r;
				b_drawbuf[j++]=b_g;
				b_drawbuf[j++]=b_b;
			}
		} else {
			// Horiziontal mode : draw only the first line (color change at each pixel)
			// Others line will be duplicated at the end
			f_drawbuf[i]=f_r;
			f_drawbuf[i+1]=f_g;
			f_drawbuf[i+2]=f_b;
			b_drawbuf[i]=b_r;
			b_drawbuf[i+1]=b_g;
			b_drawbuf[i+2]=b_b;
		}
	}
	if (vumeter->vertical != TRUE) {
		// Duplicate the first line over the others
		for (j=1; j<h; j++) {
			memcpy(f_drawbuf, f_drawbuf+3*w*j, w);
		}
	}
	
	vumeter->f_pixbuf = gdk_pixbuf_new_from_data(f_drawbuf, GDK_COLORSPACE_RGB, FALSE, 8, w, h, w*3, free_drawbuf, NULL);
	vumeter->b_pixbuf = gdk_pixbuf_new_from_data(b_drawbuf, GDK_COLORSPACE_RGB, FALSE, 8, w, h, w*3, free_drawbuf, NULL);
}

static void gtk_vu_meter_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
    GtkVuMeter *vumeter;

    g_return_if_fail (GTK_IS_VU_METER (widget));
    g_return_if_fail (requisition != NULL);

    vumeter = GTK_VU_METER (widget);

    if (vumeter->vertical == TRUE) {
        requisition->width = VERTICAL_VUMETER_WIDTH;
        requisition->height = MIN_VERTICAL_VUMETER_HEIGHT;
    } else {
        requisition->width = MIN_HORIZONTAL_VUMETER_WIDTH;
        requisition->height = HORIZONTAL_VUMETER_HEIGHT;        
    }
}

static void gtk_vu_meter_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
    GtkVuMeter *vumeter;
    
    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_VU_METER (widget));
    g_return_if_fail (allocation != NULL);

    widget->allocation = *allocation;
    vumeter = GTK_VU_METER (widget);

    if (GTK_WIDGET_REALIZED (widget)) {
        if (vumeter->vertical == TRUE) { /* veritcal */
            gdk_window_move_resize (widget->window, allocation->x, allocation->y,
                VERTICAL_VUMETER_WIDTH, MAX(allocation->height, MIN_VERTICAL_VUMETER_HEIGHT));
        } else { /* horizontal */
            gdk_window_move_resize (widget->window, allocation->x, allocation->y,
                MAX(allocation->width, MIN_HORIZONTAL_VUMETER_WIDTH), HORIZONTAL_VUMETER_HEIGHT);
        }
        /* Fix the colours */
        gtk_vu_meter_setup_colors (vumeter);
    }
}

static void free_drawbuf(guchar *pixels, gpointer data) {
	g_free(pixels);
}

static gboolean gtk_vu_meter_expose (GtkWidget *vumeter, GdkEventExpose *event) {
	//gdk_pixbuf_render_to_drawable(pixbuf, dbuf_pixmap, app->style->fg_gc[GTK_STATE_NORMAL], 0, 0, 0, 0, WIDTH, HEIGHT, GDK_RGB_DITHER_NORMAL, 0, 0);
        cairo_t *cr;
	gint draw_level = gtk_vu_meter_sound_level_to_draw_level(GTK_VU_METER(vumeter));
	
        cr = gdk_cairo_create (vumeter->window);

        // set a clip region for the expose event
        cairo_rectangle (cr,event->area.x, event->area.y, event->area.width, event->area.height);
        cairo_clip(cr);

	// Paint from the stored gradient
	gdk_cairo_set_source_pixbuf(cr, GTK_VU_METER(vumeter)->b_pixbuf,0,0);
	cairo_paint(cr);

        // set a clip region for the expose event
	if (GTK_VU_METER(vumeter)->vertical==TRUE) {
	        cairo_rectangle (cr,event->area.x, event->area.y+draw_level, event->area.width, event->area.height-draw_level);
	} else {
		//TODO
	        cairo_rectangle (cr,event->area.x, event->area.y, event->area.width, 12/*event->area.height*/);
	}
        cairo_clip(cr);

	// Paint from the stored gradient
	gdk_cairo_set_source_pixbuf(cr, GTK_VU_METER(vumeter)->f_pixbuf,0,0);
	cairo_paint(cr);

        cairo_destroy(cr);

	return FALSE;
}

static gint gtk_vu_meter_sound_level_to_draw_level (GtkVuMeter *vumeter) {
	gint draw_level;
	gdouble level, min, max, height;

	level = (gdouble)vumeter->level;
	min = (gdouble)vumeter->min;
	max = (gdouble)vumeter->max;
	height = (gdouble)vumeter->colors;

	draw_level = (1.0 - (level - min)/(max - min)) * height;

	//printf("1.0 - (%lf - %lf)/(%lf - %lf)) * %lf => %i\n", level, min, max, min, height, draw_level);

	return draw_level;
}

GtkWidget * gtk_vu_meter_new (gboolean vertical) {
	GtkWidget *vumeter = g_object_new (GTK_TYPE_VU_METER, NULL);
	GTK_VU_METER(vumeter)->vertical=vertical;
	GTK_VU_METER(vumeter)->level=0;
	GTK_VU_METER(vumeter)->min=-32767;
	GTK_VU_METER(vumeter)->max=32767;

	return vumeter;
}


void gtk_vu_meter_set_min_max (GtkVuMeter *vumeter, gint min, gint max)
{
    g_return_if_fail (vumeter != NULL);

    vumeter->max = MAX(max, min);
    vumeter->min = MIN(min, max);
    if (vumeter->max == vumeter->min) {
	    vumeter->max++;
    }
    vumeter->level = CLAMP (vumeter->level, vumeter->min, vumeter->max);
    gtk_widget_queue_draw (GTK_WIDGET(vumeter)); 
}

void gtk_vu_meter_set_level(GtkVuMeter *vumeter, gint level)
{
    g_return_if_fail (vumeter != NULL);

    vumeter->level = CLAMP (level, vumeter->min, vumeter->max);
    gtk_widget_queue_draw (GTK_WIDGET(vumeter));    
}