/* 
 * Waterfall Spectrum Analyzer Widget
 * Copyright (C) 2001 Tomi Manninen <oh2bns@sral.fi>
 *
 * Based on the Spectrum Widget by Thomas Sailer <sailer@ife.ee.ethz.ch>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "waterfall.h"

#include <gtk/gtkgc.h>
#include <gtk/gtkmain.h>
#include <math.h>

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

#define PRIO G_PRIORITY_LOW

static void waterfall_class_init(WaterfallClass *klass);
static void waterfall_init(Waterfall *wfall);
static void waterfall_finalize(GtkObject *object);
static gint waterfall_expose(GtkWidget *widget, GdkEventExpose *event);
static void waterfall_realize(GtkWidget *widget);
static void waterfall_unrealize(GtkWidget *widget);
static void waterfall_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
static void waterfall_send_configure (Waterfall *wfall);
static gint idle_callback(gpointer data);

static GtkWidgetClass *parent_class = NULL;
static WaterfallClass *waterfall_class = NULL;


guint waterfall_get_type(void)
{
	static guint waterfall_type = 0;

	if (!waterfall_type)
	{
		static const GtkTypeInfo waterfall_info =
		{
			"Waterfall",
			sizeof(Waterfall),
			sizeof(WaterfallClass),
			(GtkClassInitFunc)waterfall_class_init,
			(GtkObjectInitFunc)waterfall_init,
			/* reserved_1 */ NULL,
			/* reserved_2 */ NULL,
			(GtkClassInitFunc)NULL,
		};
		waterfall_type = gtk_type_unique(gtk_widget_get_type(), &waterfall_info);
	}
	return waterfall_type;
}

static void waterfall_class_init(WaterfallClass *klass)
{
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;

	gdk_rgb_init();
	// gtk_widget_set_default_colormap(gdk_rgb_get_cmap());
	// gtk_widget_set_default_visual(gdk_rgb_get_visual());

	object_class = (GtkObjectClass*)klass;
	widget_class = (GtkWidgetClass*)klass;

	parent_class = gtk_type_class(gtk_widget_get_type());
	waterfall_class = klass;

	object_class->finalize = waterfall_finalize;
	widget_class->expose_event = waterfall_expose;
	widget_class->realize = waterfall_realize;
	widget_class->unrealize = waterfall_unrealize;
	widget_class->size_allocate = waterfall_size_allocate;
}

static void waterfall_init(Waterfall *wfall)
{
	wfall->idlefunc = 0;

	/* initialize the colors */
	wfall->pointer1col.red = 65535;
	wfall->pointer1col.green = 0;
	wfall->pointer1col.blue = 65535;
	wfall->pointer1_gc = NULL;

	wfall->pointer2col.red = 0;
	wfall->pointer2col.green = 0;
	wfall->pointer2col.blue = 65535;
	wfall->pointer2_gc = NULL;

	wfall->pixmap = NULL;

	/* initialize the data */
	memset(wfall->buf, 0, sizeof(wfall->buf));
	wfall->ptr = wfall->buf;

	wfall->pointer1 = -1;
	wfall->pointer2 = -1;
	wfall->width = 0;
	wfall->center = FALSE;

	wfall->start = 0;
	wfall->stop = 5000;
}

static void waterfall_realize(GtkWidget *widget)
{
	Waterfall *wfall;
	GdkWindowAttr attributes;
	gint attributes_mask;
	GdkGCValues gc_values;

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

	wfall = WATERFALL(widget);
	GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);

	attributes.window_type = GDK_WINDOW_CHILD;
	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.visual = gtk_widget_get_visual(widget);
	attributes.colormap = gtk_widget_get_colormap(widget);
	attributes.event_mask = \
		gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;

	attributes_mask = \
		GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

	widget->window = gdk_window_new(gtk_widget_get_parent_window(widget),
					&attributes, attributes_mask);
	gdk_window_set_user_data(widget->window, wfall);

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

	if (!gdk_color_alloc(widget->style->colormap, &wfall->pointer1col))
		g_warning("unable to allocate color: ( %d %d %d )",
			  wfall->pointer1col.red,
			  wfall->pointer1col.green,
			  wfall->pointer1col.blue);
	gc_values.foreground = wfall->pointer1col;
	wfall->pointer1_gc = gtk_gc_get(widget->style->depth,
					widget->style->colormap,
					&gc_values, GDK_GC_FOREGROUND);

	if (!gdk_color_alloc(widget->style->colormap, &wfall->pointer2col))
		g_warning("unable to allocate color: ( %d %d %d )",
			  wfall->pointer2col.red,
			  wfall->pointer2col.green,
			  wfall->pointer2col.blue);
	gc_values.foreground = wfall->pointer2col;
	wfall->pointer2_gc = gtk_gc_get(widget->style->depth,
					widget->style->colormap,
					&gc_values, GDK_GC_FOREGROUND);

	/* create backing store */
	wfall->pixmap = gdk_pixmap_new(widget->window,
				       WATERFALL_WIDTH, WATERFALL_HEIGHT, -1);

	waterfall_send_configure(WATERFALL(widget));
}

static void waterfall_unrealize(GtkWidget *widget)
{
	Waterfall *wfall;

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

	wfall = WATERFALL(widget);
	if (wfall->idlefunc)
		gtk_idle_remove(wfall->idlefunc);
	if (wfall->pointer1_gc)
		gtk_gc_release(wfall->pointer1_gc);
	wfall->pointer1_gc = NULL;
	if (wfall->pointer2_gc)
		gtk_gc_release(wfall->pointer2_gc);
	wfall->pointer2_gc = NULL;
	if (wfall->pixmap)
		gdk_pixmap_unref(wfall->pixmap);
	wfall->pixmap = NULL;
	if (GTK_WIDGET_CLASS(parent_class)->unrealize)
		(*GTK_WIDGET_CLASS(parent_class)->unrealize)(widget);
}

static void waterfall_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
{
	g_return_if_fail(widget != NULL);
	g_return_if_fail(IS_WATERFALL(widget));
	g_return_if_fail(allocation != NULL);
	
	widget->allocation = *allocation;
	widget->allocation.width = WATERFALL_WIDTH;
	widget->allocation.height = WATERFALL_HEIGHT;

	if (GTK_WIDGET_REALIZED(widget)) {
		gdk_window_move_resize (widget->window,
					allocation->x, allocation->y,
					allocation->width, allocation->height);
		waterfall_send_configure(WATERFALL(widget));
	}
}

static void waterfall_send_configure(Waterfall *wfall)
{
	GtkWidget *widget;
	GdkEventConfigure event;

	widget = GTK_WIDGET(wfall);

	event.type = GDK_CONFIGURE;
	event.window = widget->window;
	event.send_event = TRUE;
	event.x = widget->allocation.x;
	event.y = widget->allocation.y;
	event.width = widget->allocation.width;
	event.height = widget->allocation.height;
  
	gtk_widget_event(widget, (GdkEvent*)&event);
}


GtkWidget *waterfall_new(const char *name,
			 void *dummy0, void *dummy1,
			 unsigned int dummy2, unsigned int dummy3)
{
	Waterfall *wfall;
	
	wfall = gtk_type_new(waterfall_get_type());
	memset(wfall->buf, 0, sizeof(wfall->buf));
	wfall->ptr = wfall->buf;

	return GTK_WIDGET(wfall);
}

static void waterfall_finalize(GtkObject *object)
{
	g_return_if_fail(object != NULL);
	g_return_if_fail(IS_WATERFALL(object));
	(*GTK_OBJECT_CLASS(parent_class)->finalize)(object);
}

static gint waterfall_expose(GtkWidget *widget, GdkEventExpose *event)
{
	Waterfall *wfall;

	g_return_val_if_fail(widget != NULL, FALSE);
	g_return_val_if_fail(IS_WATERFALL(widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	wfall = WATERFALL(widget);
	if (GTK_WIDGET_DRAWABLE(widget) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);

	return FALSE;
}

static void draw_line(GtkWidget *widget, gint x1, gint x2, gint y1, gint y2)
{
	Waterfall *wfall = WATERFALL(widget);

	gdk_draw_line(wfall->pixmap, widget->style->white_gc, x1, y1, x2, y2);
}

static void draw(Waterfall *wfall)
{
	GtkWidget *widget;
	gdouble lo, hi, step;
	gint i, x, f;
	gchar *str;

	widget = GTK_WIDGET(wfall);
	g_return_if_fail(GTK_WIDGET_DRAWABLE(widget));
	g_return_if_fail(wfall->pixmap);

	/* draw the waterfall */
	gdk_draw_gray_image(wfall->pixmap,
			    widget->style->base_gc[widget->state], 0, 0,
			    WATERFALL_WIDTH, WATERFALL_HEIGHT,
			    GDK_RGB_DITHER_MAX,
			    wfall->ptr,
			    WATERFALL_WIDTH);

	/* draw markers */
	if (wfall->pointer1 != -1) {
		gdk_draw_line(wfall->pixmap, wfall->pointer1_gc, 
			      wfall->pointer1,
			      0, 
			      wfall->pointer1, 
			      widget->allocation.height-1);
		gdk_draw_line(wfall->pixmap, wfall->pointer1_gc, 
			      wfall->pointer1 + wfall->width,
			      0, 
			      wfall->pointer1 + wfall->width, 
			      widget->allocation.height-1);
		if (wfall->center) {
			gdk_draw_line(wfall->pixmap, wfall->pointer1_gc, 
				      wfall->pointer1 + wfall->width / 2,
				      0, 
				      wfall->pointer1 + wfall->width / 2, 
				      widget->allocation.height-1);
		}
	}
	if (wfall->pointer2 != -1) {
		gdk_draw_line(wfall->pixmap, wfall->pointer2_gc, 
			      wfall->pointer2,
			      0, 
			      wfall->pointer2, 
			      widget->allocation.height-1);
		gdk_draw_line(wfall->pixmap, wfall->pointer2_gc, 
			      wfall->pointer2 + wfall->width,
			      0, 
			      wfall->pointer2 + wfall->width, 
			      widget->allocation.height-1);
		if (wfall->center) {
			gdk_draw_line(wfall->pixmap, wfall->pointer2_gc, 
				      wfall->pointer2 + wfall->width / 2,
				      0, 
				      wfall->pointer2 + wfall->width / 2, 
				      widget->allocation.height-1);
		}
	}

	draw_line(widget, 0, WATERFALL_WIDTH, 0, 0);

	step = (wfall->stop - wfall->start) / WATERFALL_WIDTH;
	lo = wfall->start;

	for (i = 0; i < WATERFALL_WIDTH; i++) {
		hi = lo + step;

		for (f = 0; f < 5000; f += 100)
			if (f >= lo && f < hi)
				break;

		lo += step;

		if (f == 5000)
			continue;

		if ((f % 500) == 0) {
			draw_line(widget, i, i, 0, 10);

			str = g_strdup_printf("%d", f);

			x = MAX(i - 14, 0);
			x = MIN(x, 512 - 28);

			gdk_draw_string(wfall->pixmap,
					widget->style->font,
					widget->style->white_gc,
					x, 22,
					str);
			g_free(str);

			continue;
		}
		if ((f % 100) == 0) {
			draw_line(widget, i, i, 0, 5);
			continue;
		}
	}

	/* draw to screen */
	gdk_draw_pixmap(widget->window,
			widget->style->base_gc[widget->state],
			wfall->pixmap, 
			0, 0, 0, 0,
			widget->allocation.width, widget->allocation.height);
}

static gint idle_callback(gpointer data)
{
	g_return_val_if_fail(data != NULL, FALSE);
	g_return_val_if_fail(IS_WATERFALL(data), FALSE);
	gdk_threads_enter();
	WATERFALL(data)->idlefunc = 0;
	if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(data))) {
		gdk_threads_leave();
		return FALSE;
	}
	draw(WATERFALL(data));
	gdk_threads_leave();
	return FALSE;  /* don't call this callback again */
}

void waterfall_setdata(Waterfall *wfall, unsigned char *data)
{
	guchar *ptr;

	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	/* scroll */
	wfall->ptr += WATERFALL_WIDTH;

	ptr = wfall->buf + WATERFALL_SIZE;
	if (wfall->ptr > ptr) {
		memcpy(wfall->buf, ptr, WATERFALL_SIZE);
		wfall->ptr = wfall->buf;
	}

	/* copy new data */
	ptr = wfall->ptr + WATERFALL_SIZE - WATERFALL_WIDTH;
	memcpy(ptr, data, WATERFALL_WIDTH);

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}

void waterfall_setmarker1(Waterfall *wfall, gint pointer)
{
	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	if (pointer >= -1 && pointer < WATERFALL_WIDTH)
		wfall->pointer1 = pointer;

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}

void waterfall_setmarker2(Waterfall *wfall, gint pointer)
{
	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	if (pointer >= -1 && pointer < WATERFALL_WIDTH)
		wfall->pointer2 = pointer;

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}

void waterfall_setwidth(Waterfall *wfall, gint width)
{
	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	wfall->width = width;

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}

void waterfall_setcenter(Waterfall *wfall, gboolean center)
{
	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	wfall->center = center;

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}

void waterfall_setfreqs(Waterfall *wfall, gfloat start, gfloat stop)
{
	g_return_if_fail(wfall != NULL);
	g_return_if_fail(IS_WATERFALL(wfall));

	wfall->start = start;
	wfall->stop = stop;

	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(wfall)) && !wfall->idlefunc)
		wfall->idlefunc = gtk_idle_add_priority(PRIO,
							idle_callback,
							wfall);
}
