/***************************************************************************
 *            gtkvumeter.c
 *
 *  Fri Jan 10 20:06:23 2003
 *  Copyright  2003  Todd Goyen
 *  wettoad@knighthoodofbuh.org
 ****************************************************************************/

#include <math.h>
#include <gtk/gtk.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

static void gtk_vumeter_init (GtkVUMeter *vumeter);
static void gtk_vumeter_class_init (GtkVUMeterClass *class);
static void gtk_vumeter_destroy (GtkObject *object);
static void gtk_vumeter_realize (GtkWidget *widget);
static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition);
static void gtk_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
static gint gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event);
static void gtk_vumeter_free_colors (GtkVUMeter *vumeter);
static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter);
static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter);

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 GtkWidgetClass *parent_class = NULL;



GtkType gtk_vumeter_get_type (void)
{
    static GType vumeter_type = 0;
    
    if (!vumeter_type) {
        static const GTypeInfo vumeter_info = {
            sizeof (GtkVUMeterClass),
            NULL, NULL,
            (GClassInitFunc) gtk_vumeter_class_init,
            NULL, NULL, sizeof (GtkVUMeter), 0,
            (GInstanceInitFunc) gtk_vumeter_init,
        };
        vumeter_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkVUMeter", &vumeter_info, 0);
    }

    return vumeter_type;
}

GtkWidget* gtk_vumeter_new (gboolean vertical)
{
    GtkVUMeter *vumeter;
    vumeter = gtk_type_new (GTK_TYPE_VUMETER);
    vumeter->vertical = vertical;
    return GTK_WIDGET (vumeter);
}

static void gtk_vumeter_init (GtkVUMeter *vumeter)
{
    vumeter->colormap = NULL;
    vumeter->colors = 0;
    vumeter->f_gc = NULL;
    vumeter->b_gc = NULL;
    vumeter->f_colors = NULL;
    vumeter->b_colors = NULL;

    vumeter->level = 0;
    vumeter->min = 0;
    vumeter->max = 32767;
    vumeter->peaks_falloff = GTK_VUMETER_PEAKS_FALLOFF_MEDIUM;
    vumeter->peak_level = 0;
    
    vumeter->scale = GTK_VUMETER_SCALE_LINEAR;

    //XXX A bit heavy...
    gtk_vumeter_set_gradient(vumeter, 3, default_f_gradient_keys, 3, default_b_gradient_keys);
}

static void gtk_vumeter_class_init (GtkVUMeterClass *class)
{
    GtkObjectClass *object_class;
    GtkWidgetClass *widget_class;

    object_class = (GtkObjectClass*) class;
    widget_class = (GtkWidgetClass*) class;
    parent_class = gtk_type_class(gtk_widget_get_type());

    object_class->destroy = gtk_vumeter_destroy;
    
    widget_class->realize = gtk_vumeter_realize;
    widget_class->expose_event = gtk_vumeter_expose;
    widget_class->size_request = gtk_vumeter_size_request;
    widget_class->size_allocate = gtk_vumeter_size_allocate;    
}

static void gtk_vumeter_destroy (GtkObject *object)
{
    GtkVUMeter *vumeter = GTK_VUMETER (object);

    gtk_vumeter_free_colors (vumeter);
    
    GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

static void gtk_vumeter_realize (GtkWidget *widget)
{
    GtkVUMeter *vumeter;
    GdkWindowAttr attributes;
    gint attributes_mask;
    
    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_VUMETER (widget));

    GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
    vumeter = GTK_VUMETER (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);
    
    /* colors */
    vumeter->colormap = gdk_colormap_get_system ();
    gtk_vumeter_setup_colors (vumeter);
}

static void gtk_vumeter_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
    GtkVUMeter *vumeter;

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

    vumeter = GTK_VUMETER (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_vumeter_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
    GtkVUMeter *vumeter;
    
    g_return_if_fail (widget != NULL);
    g_return_if_fail (GTK_IS_VUMETER (widget));
    g_return_if_fail (allocation != NULL);

    widget->allocation = *allocation;
    vumeter = GTK_VUMETER (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_vumeter_setup_colors (vumeter);
    }
}

static gint gtk_vumeter_expose (GtkWidget *widget, GdkEventExpose *event)
{
    GtkVUMeter *vumeter;
    gint index, level;
    gint width, height;
    
    g_return_val_if_fail (widget != NULL, FALSE);
    g_return_val_if_fail (GTK_IS_VUMETER (widget), FALSE);
    g_return_val_if_fail (event != NULL, FALSE);

    if (event->count > 0)
        return FALSE;

    vumeter = GTK_VUMETER (widget);
    level = gtk_vumeter_sound_level_to_draw_level (vumeter);

    if (vumeter->vertical == TRUE) {
        width = widget->allocation.width - 2;
        height = widget->allocation.height;

        /* draw border */
        gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, 
            NULL, widget, "trough", 0, 0, widget->allocation.width, height);
        /* draw background gradient */
        for (index = 0; index < level; index++) {
            gdk_draw_line (widget->window, vumeter->b_gc[index], 1, index + 1, width, index + 1);   
        }
        /* draw foreground gradient */
        for (index = level; index < height - 2; index++) {
            gdk_draw_line (widget->window, vumeter->f_gc[index], 1, index + 1, width, index + 1);            
        }
    } else { /* Horizontal */
        width = widget->allocation.width;
        height = widget->allocation.height - 2;

        /* draw border */
        gtk_paint_box (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, 
            NULL, widget, "trough", 0, 0, width, widget->allocation.height);
        /* draw background gradient */
        for (index = 0; index < level; index++) {
            gdk_draw_line (widget->window, vumeter->b_gc[index], index + 1, 1, index + 1, height);   
        }
        /* draw foreground gradient */
        for (index = level; index < height - 2; index++) {
            gdk_draw_line (widget->window, vumeter->f_gc[index], index + 1, 1, index + 1, height);            
        }        
    }

    return FALSE;
}

static void gtk_vumeter_free_colors (GtkVUMeter *vumeter)
{
    gint index;

    /* Free old gc's */
    if (vumeter->f_gc && vumeter->b_gc) {
        for (index = 0; index < vumeter->colors; index++) {    
            if (vumeter->f_gc[index]) {
                g_object_unref (G_OBJECT(vumeter->f_gc[index]));
            }
            if (vumeter->b_gc[index]) {        
                g_object_unref (G_OBJECT(vumeter->b_gc[index]));
            }
        }
        g_free(vumeter->f_gc);
        g_free(vumeter->b_gc);
        vumeter->f_gc = NULL;
        vumeter->b_gc = NULL;
    }
    
    /* Free old Colors */
    if (vumeter->f_colors) {
        gdk_colormap_free_colors (vumeter->colormap, vumeter->f_colors, vumeter->colors);
        g_free (vumeter->f_colors);
        vumeter->f_colors = NULL;
    }
    if (vumeter->b_colors) {
        gdk_colormap_free_colors (vumeter->colormap, vumeter->b_colors, vumeter->colors);
        g_free (vumeter->b_colors);
        vumeter->b_colors = NULL;
    }
}

static void gtk_vumeter_setup_colors (GtkVUMeter *vumeter)
{
    gint index;
    gint max;
    gint f_key_len, b_key_len;
    gint f_key_index, b_key_index;
    gdouble f_key_pos, b_key_pos;
    GdkColor *fgk, *bgk;
    
    g_return_if_fail (vumeter->colormap != NULL);
    
    gtk_vumeter_free_colors (vumeter);
    
    /* Set new size */
    if (vumeter->vertical == TRUE) {
        vumeter->colors = MAX(GTK_WIDGET(vumeter)->allocation.height - 2, 0);
    } else {
        vumeter->colors = MAX(GTK_WIDGET(vumeter)->allocation.width - 2, 0);
    }
    
    /* allocate new memory */
    vumeter->f_colors = g_malloc (vumeter->colors * sizeof(GdkColor));
    vumeter->b_colors = g_malloc (vumeter->colors * sizeof(GdkColor));    
    vumeter->f_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));
    vumeter->b_gc = g_malloc (vumeter->colors * sizeof(GdkGC *));    
    
    /* Initialize stuff */
    
    max=vumeter->colors;
    f_key_len = max / (vumeter->f_gradient_key_count-1) + 1;
    b_key_len = max / (vumeter->b_gradient_key_count-1) + 1;
    fgk=vumeter->f_gradient_keys;
    bgk=vumeter->b_gradient_keys;
    
    for (index = 0; index < max; index++) {
	f_key_index=index/f_key_len;
	f_key_pos=((gdouble) (index%f_key_len)/f_key_len);
	b_key_index=index/f_key_len;
	b_key_pos=((gdouble) (index%b_key_len)/b_key_len);
	
        /* Generate the Colours */
        /* foreground */
        vumeter->f_colors[index].red = fgk[f_key_index].red*(1.0-f_key_pos) + fgk[f_key_index+1].red*f_key_pos;
        vumeter->f_colors[index].green = fgk[f_key_index].green*(1.0-f_key_pos) + fgk[f_key_index+1].green*f_key_pos;
        vumeter->f_colors[index].blue = fgk[f_key_index].blue*(1.0-f_key_pos) + fgk[f_key_index+1].blue*f_key_pos;
        /* background */
        vumeter->b_colors[index].red = bgk[b_key_index].red*(1.0-b_key_pos) + bgk[b_key_index+1].red*b_key_pos;
        vumeter->b_colors[index].green = bgk[b_key_index].green*(1.0-b_key_pos) + bgk[b_key_index+1].green*b_key_pos;
        vumeter->b_colors[index].blue = bgk[b_key_index].blue*(1.0-b_key_pos) + bgk[b_key_index+1].blue*b_key_pos;

        /* Allocate the Colours */
        /* foreground */
	//TODO : try to use gdk_colormap_alloc_colors or suppress f_colors array
        gdk_colormap_alloc_color (vumeter->colormap, &vumeter->f_colors[index], FALSE, TRUE);
        vumeter->f_gc[index] = gdk_gc_new(GTK_WIDGET(vumeter)->window);
        gdk_gc_set_foreground(vumeter->f_gc[index], &vumeter->f_colors[index]);
        /* background */
        gdk_colormap_alloc_color (vumeter->colormap, &vumeter->b_colors[index], FALSE, TRUE);
        vumeter->b_gc[index] = gdk_gc_new(GTK_WIDGET(vumeter)->window);
        gdk_gc_set_foreground(vumeter->b_gc[index], &vumeter->b_colors[index]);        
    }
}

static gint gtk_vumeter_sound_level_to_draw_level (GtkVUMeter *vumeter)
{
    gdouble draw_level;
    gdouble level, min, max, height;
    gdouble log_level, log_max;
    
    level = (gdouble)vumeter->level;
    min = (gdouble)vumeter->min;
    max = (gdouble)vumeter->max;
    height = (gdouble)vumeter->colors;
    
    if (vumeter->scale == GTK_VUMETER_SCALE_LINEAR) {
        draw_level = (1.0 - (level - min)/(max - min)) * height;
    } else {
        log_level = log10((level - min + 1)/(max - min + 1));
        log_max = log10(1/(max - min + 1));
        draw_level = log_level/log_max * height;
    }
    
    return ((gint)draw_level);
}

void gtk_vumeter_set_min_max (GtkVUMeter *vumeter, gint min, gint max)
{
    g_return_if_fail (vumeter != NULL);
    g_return_if_fail (GTK_IS_VUMETER (vumeter));

    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_vumeter_set_level (GtkVUMeter *vumeter, gint level)
{
    g_return_if_fail (vumeter != NULL);
    g_return_if_fail (GTK_IS_VUMETER (vumeter));

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

void gtk_vumeter_set_peaks_falloff (GtkVUMeter *vumeter, gint peaks_falloff)
{
    g_return_if_fail (vumeter != NULL);
    g_return_if_fail (GTK_IS_VUMETER (vumeter));  
}

void gtk_vumeter_set_scale (GtkVUMeter *vumeter, gint scale)
{
    g_return_if_fail (vumeter != NULL);
    g_return_if_fail (GTK_IS_VUMETER (vumeter));  
    
    if (scale != vumeter->scale) {
        vumeter->scale = CLAMP(scale, GTK_VUMETER_SCALE_LINEAR, GTK_VUMETER_SCALE_LAST - 1);
        if (GTK_WIDGET_REALIZED(vumeter)) {
            gtk_vumeter_setup_colors (vumeter);
            gtk_widget_queue_draw (GTK_WIDGET(vumeter));
        }            
    }
}

void gtk_vumeter_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;	
}