/* 
 * Custom Widgets
 * Copyright (C) 1999 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 <math.h>
#include "widgets.h"
#include <gtk/gtkgc.h>
#include <gtk/gtkmain.h>

#define SCOPEMAXPOINTS 512
#define PRIO G_PRIORITY_LOW

struct _ScopePoint {
	guint16 dx;
	guint16 y;
};

static void scope_class_init(ScopeClass *klass);
static void scope_init(Scope *trace);
static void scope_finalize(GtkObject *object);
static gint scope_expose(GtkWidget *widget, GdkEventExpose *event);
static void scope_size_request(GtkWidget *widget, GtkRequisition *requisition);
static gint scope_idle_callback(gpointer data);

static GtkWidgetClass *scope_parent_class = NULL;
static ScopeClass *scope_class = NULL;


guint scope_get_type(void)
{
	static guint trace_type = 0;

	if (!trace_type)
	{
		static const GtkTypeInfo trace_info =
		{
			"Scope",
			sizeof(Scope),
			sizeof(ScopeClass),
			(GtkClassInitFunc) scope_class_init,
			(GtkObjectInitFunc) scope_init,
			/* reserved_1 */ NULL,
			/* reserved_2 */ NULL,
			(GtkClassInitFunc) NULL,
		};
		trace_type = gtk_type_unique(gtk_widget_get_type(), &trace_info);
	}
	return trace_type;
}

static void scope_class_init(ScopeClass *klass)
{
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;

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

	scope_parent_class = gtk_type_class(gtk_widget_get_type());
	scope_class = klass;

	object_class->finalize = scope_finalize;
	widget_class->size_request = scope_size_request;
	widget_class->expose_event = scope_expose;
}

static void scope_init(Scope *trace)
{
	GTK_WIDGET_SET_FLAGS(trace, GTK_NO_WINDOW);

	trace->idlefunc = 0;

	/* initialize the colors */
	trace->zerolinecol.red = 65535;
	trace->zerolinecol.green = 13107;
	trace->zerolinecol.blue = 0;
	trace->samplingcol.red = 13763;
	trace->samplingcol.green = 42598;
	trace->samplingcol.blue = 5243;
	trace->zeroline_gc = trace->sampling_gc = NULL;
	/* initialize the trace history */
	trace->ptnum = trace->ptptr = 0;
	trace->pt = NULL;
}


static void scope_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
	Scope *trace;
  
	g_return_if_fail(widget != NULL);
	g_return_if_fail(IS_SCOPE(widget));
	g_return_if_fail(requisition != NULL);
	trace = SCOPE(widget);
	requisition->width = MIN(trace->ptnum * 4, 512);
	requisition->height = 256;
}


GtkWidget* scope_new(char *name, char *str1, char *str2, gint int1, gint int2)
{
	Scope *trace;

	trace = gtk_type_new(scope_get_type());
	trace->ptnum = CLAMP(int1, 2, SCOPEMAXPOINTS);;
	trace->pt = g_malloc0(trace->ptnum * sizeof(struct _ScopePoint));
	return GTK_WIDGET(trace);
}

static void scope_finalize(GtkObject *object)
{
	Scope *trace;

	g_return_if_fail(object != NULL);
	g_return_if_fail(IS_SCOPE(object));
	trace = SCOPE(object);
	if (trace->idlefunc)
		gtk_idle_remove(trace->idlefunc);
	if (trace->zeroline_gc)
		gtk_gc_release(trace->zeroline_gc);
	if (trace->sampling_gc)
		gtk_gc_release(trace->sampling_gc);
	if (trace->pt)
		g_free(trace->pt);
	(*GTK_OBJECT_CLASS(scope_parent_class)->finalize)(object);
}

static gint scope_expose(GtkWidget *widget, GdkEventExpose *event)
{
	Scope *trace;

	g_return_val_if_fail(widget != NULL, FALSE);
	g_return_val_if_fail(IS_SCOPE(widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);
	if (GTK_WIDGET_DRAWABLE(widget)) {
		trace = SCOPE(widget);
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, scope_idle_callback, trace);
	}
	return FALSE;
}

static void scope_draw(Scope *trace)
{
	GdkGCValues gc_values;
	guint totx, idx, i, si;
	guint32 mulx, muly, zeroy, x;
	GdkPoint p[SCOPEMAXPOINTS+1];
	GdkSegment s[SCOPEMAXPOINTS];

	/* init gc's if necessary */
	if (!trace->zeroline_gc) {
		if (!gdk_color_alloc(trace->widget.style->colormap, &trace->zerolinecol))
			g_warning("unable to allocate color: ( %d %d %d )",
				  trace->zerolinecol.red, trace->zerolinecol.green, trace->zerolinecol.blue);
		gc_values.foreground = trace->zerolinecol;
		trace->zeroline_gc = gtk_gc_get(trace->widget.style->depth, 
						trace->widget.style->colormap,
						&gc_values, GDK_GC_FOREGROUND);
	}
	if (!trace->sampling_gc) {
		if (!gdk_color_alloc(trace->widget.style->colormap, &trace->samplingcol))
			g_warning("unable to allocate color: ( %d %d %d )",
				  trace->samplingcol.red, trace->samplingcol.green, trace->samplingcol.blue);
		gc_values.foreground = trace->samplingcol;
		trace->sampling_gc = gtk_gc_get(trace->widget.style->depth,
						trace->widget.style->colormap,
						&gc_values, GDK_GC_FOREGROUND);
	}
	/* first determine the total x size and calculate multiplication factors */
	for (totx = 0, idx = trace->ptptr, i = 1; i < trace->ptnum; i++) {
		idx++;
		if (idx >= trace->ptnum)
			idx = 0;
		totx += trace->pt[idx].dx & 0x7fff;
	}
	mulx = (((guint32)trace->widget.allocation.width) << 16) / MAX(totx, 1);
	muly = trace->widget.allocation.height;
	zeroy = (0x8000 * muly) >> 16;
	/* prepare sampling ticks and lines */
	for (si = x = i = 0, idx = trace->ptptr; i < trace->ptnum; i++) {
		p[i].x = trace->widget.allocation.x + ((x * mulx) >> 16);
		p[i].y = trace->widget.allocation.y + ((trace->pt[idx].y * muly) >> 16);
		if (trace->pt[idx].dx & 0x8000) {
			s[si].x1 = s[si].x2 = p[i].x;
			s[si].y1 = trace->widget.allocation.y + zeroy - 4;
			s[si].y2 = trace->widget.allocation.y + zeroy + 4;
			si++;
		}
		idx++;
		if (idx >= trace->ptnum)
			idx = 0;
		x += trace->pt[idx].dx & 0x7fff;
	}
	/* now draw everything */
	gdk_draw_rectangle(trace->widget.window, trace->widget.style->base_gc[trace->widget.state],
			   TRUE, trace->widget.allocation.x, trace->widget.allocation.y, 
			   trace->widget.allocation.x+trace->widget.allocation.width, 
			   trace->widget.allocation.y+trace->widget.allocation.height);
	gdk_draw_line(trace->widget.window, trace->zeroline_gc, 
		      trace->widget.allocation.x,
		      trace->widget.allocation.y+zeroy, 
		      trace->widget.allocation.x+trace->widget.allocation.width-1, 
		      trace->widget.allocation.y+zeroy);
	gdk_draw_segments(trace->widget.window, trace->sampling_gc, s, si);
	gdk_draw_lines(trace->widget.window, trace->widget.style->fg_gc[trace->widget.state],
		       p, trace->ptnum);
}


static gint scope_idle_callback(gpointer data)
{
	g_return_val_if_fail(data != NULL, FALSE);
	g_return_val_if_fail(IS_SCOPE(data), FALSE);
	SCOPE(data)->idlefunc = 0;
	if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(data)))
		return FALSE;
	if (!SCOPE(data)->pt || !SCOPE(data)->ptnum)
		return FALSE;
	scope_draw(SCOPE(data));
	return FALSE;  /* don't call this callback again */
}


void scope_addvalue(Scope *trace, guint16 deltax, gint16 y, guint flags)
{
	g_return_if_fail(trace != NULL);
	g_return_if_fail(IS_SCOPE(trace));
	trace->pt[trace->ptptr].y = 32767-y;
	trace->pt[trace->ptptr].dx = deltax & 0x7fff;
	if (flags)
		trace->pt[trace->ptptr].dx |= 0x8000;
	trace->ptptr++;
	if (trace->ptptr >= trace->ptnum)
		trace->ptptr = 0;
	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(trace))) {
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, scope_idle_callback, trace);
	}
}

void scope_addvalues(Scope *trace, guint num, guint16 *deltax, gint16 *y)
{
	guint i;

	g_return_if_fail(trace != NULL);
	g_return_if_fail(IS_SCOPE(trace));
	g_return_if_fail(y != NULL);
	for (i = 0; i < num; i++) {
		trace->pt[trace->ptptr].y = 32767-y[i];
		trace->pt[trace->ptptr].dx = deltax ? (deltax[i] & 0x7fff) : 1;
		trace->ptptr++;
		if (trace->ptptr >= trace->ptnum)
			trace->ptptr = 0;
	}
	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(trace))) {
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, scope_idle_callback, trace);
	}
}


/* ---------------------------------------------------------------------- */

/*
 * This fft routine is from ~gabriel/src/filters/fft/fft.c;
 * I am unsure of the original source.  The file contains no
 * copyright notice or description.
 * The declaration is changed to the prototype form but the
 * function body is unchanged.  (J. T. Buck)
 */

#define SWAP(a, b) tempr=(a); (a)=(b); (b)=tempr

/*
 * Replace data by its discrete Fourier transform, if isign is
 * input as 1, or by its inverse discrete Fourier transform, if 
 * "isign" is input as -1.  "data'"is a complex array of length "nn",
 * input as a real array data[0..2*nn-1]. "nn" MUST be an integer
 * power of 2 (this is not checked for!?)
 */

static void fft_rif(float *data, int nn, int isign) {
        int n;
        int mmax;
        int m, j, istep, i;
        float wtemp, wr, wpr, wpi, wi, theta;
        float tempr, tempi;

        data--;
        n = nn << 1;
        j = 1;

        for (i = 1; i < n; i += 2) {
                if(j > i) {
                        SWAP(data[j], data[i]);
                        SWAP(data[j+1], data[i+1]);
                }
                m= n >> 1;
                while (m >= 2 && j >m) {
                        j -= m;
                        m >>= 1;
                }
                j += m;
        }
        mmax = 2;
        while (n > mmax) {
                istep = 2*mmax;
                theta = -6.28318530717959/(isign*mmax);
                wtemp = sin(0.5*theta);
                wpr = -2.0*wtemp*wtemp;
                wpi = sin(theta);
                wr = 1.0;
                wi = 0.0;
                for (m = 1; m < mmax; m += 2) {
                        for (i = m; i < n; i += istep) {
                                j = i + mmax;
                                tempr = wr*data[j] - wi*data[j+1];
                                tempi = wr*data[j+1] + wi*data[j];
                                data[j] = data[i] - tempr;
                                data[j+1] = data[i+1] - tempi;
                                data[i] += tempr;
                                data[i+1] += tempi;
                        }
                        wr = (wtemp=wr)*wpr - wi*wpi+wr;
                        wi = wi*wpr + wtemp*wpi + wi;
                }
                mmax = istep;
        }
}
        
#undef SWAP

static inline float hamming(float x)
{
        return 0.54-0.46*cos(2*M_PI*x);
}

/* ---------------------------------------------------------------------- */

#define SPECTRUMMAXPOINTS 1024

static void spectrum_class_init(SpectrumClass *klass);
static void spectrum_init(Spectrum *trace);
static void spectrum_finalize(GtkObject *object);
static gint spectrum_expose(GtkWidget *widget, GdkEventExpose *event);
static void spectrum_size_request(GtkWidget *widget, GtkRequisition *requisition);
static gint spectrum_idle_callback(gpointer data);

static GtkWidgetClass *spectrum_parent_class = NULL;
static SpectrumClass *spectrum_class = NULL;


guint spectrum_get_type(void)
{
	static guint trace_type = 0;

	if (!trace_type)
	{
		static const GtkTypeInfo trace_info =
		{
			"Spectrum",
			sizeof(Spectrum),
			sizeof(SpectrumClass),
			(GtkClassInitFunc) spectrum_class_init,
			(GtkObjectInitFunc) spectrum_init,
			/* reserved_1 */ NULL,
			/* reserved_2 */ NULL,
			(GtkClassInitFunc) NULL,
		};
		trace_type = gtk_type_unique(gtk_widget_get_type(), &trace_info);
	}
	return trace_type;
}

static void spectrum_class_init(SpectrumClass *klass)
{
	GtkObjectClass *object_class;
	GtkWidgetClass *widget_class;

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

	spectrum_parent_class = gtk_type_class(gtk_widget_get_type());
	spectrum_class = klass;

	object_class->finalize = spectrum_finalize;
	widget_class->size_request = spectrum_size_request;
	widget_class->expose_event = spectrum_expose;
}

static void spectrum_init(Spectrum *trace)
{
	GTK_WIDGET_SET_FLAGS(trace, GTK_NO_WINDOW);

	trace->idlefunc = 0;

	/* initialize the colors */
	trace->gridcol.red = 65535;
	trace->gridcol.green = 13107;
	trace->gridcol.blue = 0;
	trace->samplingcol.red = 13763;
	trace->samplingcol.green = 42598;
	trace->samplingcol.blue = 5243;
	trace->grid_gc = trace->sampling_gc = NULL;
	/* initialize the trace history */
	trace->ptnum = trace->ptptr = 0;
	trace->pt = NULL;
}


static void spectrum_size_request(GtkWidget *widget, GtkRequisition *requisition)
{
	Spectrum *trace;
  
	g_return_if_fail(widget != NULL);
	g_return_if_fail(IS_SPECTRUM(widget));
	g_return_if_fail(requisition != NULL);
	trace = SPECTRUM(widget);
	requisition->width = MIN(trace->ptnum * 4, 512);
	requisition->height = 256;
}


GtkWidget* spectrum_new(char *name, char *str1, char *str2, gint int1, gint int2)
{
	guint i, j;
	Spectrum *trace;

	trace = gtk_type_new(spectrum_get_type());
	i = CLAMP(int1, 4, SPECTRUMMAXPOINTS);
	j = 0;
	while (i > 1) {
		i >>= 1;
		j++;
	}
	trace->ptnum = 1 << j;
       	trace->pt = g_malloc0(trace->ptnum * sizeof(gint16));
	return GTK_WIDGET(trace);
}

static void spectrum_finalize(GtkObject *object)
{
	Spectrum *trace;

	g_return_if_fail(object != NULL);
	g_return_if_fail(IS_SPECTRUM(object));
	trace = SPECTRUM(object);
	if (trace->idlefunc)
		gtk_idle_remove(trace->idlefunc);
	if (trace->grid_gc)
		gtk_gc_release(trace->grid_gc);
	if (trace->sampling_gc)
		gtk_gc_release(trace->sampling_gc);
	if (trace->pt)
		g_free(trace->pt);
	(*GTK_OBJECT_CLASS(spectrum_parent_class)->finalize)(object);
}

static gint spectrum_expose(GtkWidget *widget, GdkEventExpose *event)
{
	Spectrum *trace;

	g_return_val_if_fail(widget != NULL, FALSE);
	g_return_val_if_fail(IS_SPECTRUM(widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);
	if (GTK_WIDGET_DRAWABLE(widget)) {
		trace = SPECTRUM(widget);
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, spectrum_idle_callback, trace);
	}
	return FALSE;
}

static void spectrum_draw(Spectrum *trace)
{
	GdkGCValues gc_values;
	guint idx, i;
	guint32 mulx;
	gint y;
	GdkPoint p[SPECTRUMMAXPOINTS/2+1];
	float f[2*SPECTRUMMAXPOINTS];
	float ftmp1, ftmp2, ftmp3;

	/* init gc's if necessary */
	if (!trace->grid_gc) {
		if (!gdk_color_alloc(trace->widget.style->colormap, &trace->gridcol))
			g_warning("unable to allocate color: ( %d %d %d )",
				  trace->gridcol.red, trace->gridcol.green, trace->gridcol.blue);
		gc_values.foreground = trace->gridcol;
		trace->grid_gc = gtk_gc_get(trace->widget.style->depth, 
					    trace->widget.style->colormap,
					    &gc_values, GDK_GC_FOREGROUND);
	}
	if (!trace->sampling_gc) {
		if (!gdk_color_alloc(trace->widget.style->colormap, &trace->samplingcol))
			g_warning("unable to allocate color: ( %d %d %d )",
				  trace->samplingcol.red, trace->samplingcol.green, trace->samplingcol.blue);
		gc_values.foreground = trace->samplingcol;
		trace->sampling_gc = gtk_gc_get(trace->widget.style->depth,
						trace->widget.style->colormap,
						&gc_values, GDK_GC_FOREGROUND);
	}
	/* first determine the total x size and calculate multiplication factors */
	ftmp1 = 1.0 / (float)trace->ptnum;
	for (idx = trace->ptptr, i = 0; i < trace->ptnum; i++) {
		f[2*i] = trace->pt[idx] * hamming((float)i * ftmp1);
		f[2*i+1] = 0;
		idx++;
		if (idx >= trace->ptnum)
			idx = 0;
	}
	fft_rif(f, trace->ptnum, 1);
	mulx = (((guint32)trace->widget.allocation.width) << 17) / trace->ptnum;
	/* prepare sampling ticks and lines */
	ftmp2 = trace->widget.allocation.height * 0.0723824136504;  /* 1/ln(10^6) */
	ftmp3 = 21.4875015613 + 4.6;
	for (i = 0; i < trace->ptnum / 2; i++) {
		ftmp1 = f[2*i] * f[2*i] + f[2*i+1] * f[2*i+1];
		if (ftmp1 < 2168)
			p[i].y = trace->widget.allocation.y + trace->widget.allocation.height - 1;
		else {
			y = ftmp2 * (ftmp3 - log(ftmp1));
			y = CLAMP(y, 0, trace->widget.allocation.height-1);
			p[i].y = trace->widget.allocation.y + y;
		}
		p[i].x = trace->widget.allocation.x + ((i * mulx) >> 16);
	}
	/* now draw everything */
	gdk_draw_rectangle(trace->widget.window, trace->widget.style->base_gc[trace->widget.state],
			   TRUE, trace->widget.allocation.x, trace->widget.allocation.y, 
			   trace->widget.allocation.x+trace->widget.allocation.width, 
			   trace->widget.allocation.y+trace->widget.allocation.height);
	for (i = 0; i < 3; i++)
		gdk_draw_line(trace->widget.window, trace->grid_gc, 
			      trace->widget.allocation.x,
			      trace->widget.allocation.y+i*(trace->widget.allocation.height/3), 
			      trace->widget.allocation.x+trace->widget.allocation.width-1,
			      trace->widget.allocation.y+i*(trace->widget.allocation.height/3));
	gdk_draw_line(trace->widget.window, trace->grid_gc, 
		      trace->widget.allocation.x,
		      trace->widget.allocation.y+trace->widget.allocation.height-1, 
		      trace->widget.allocation.x+trace->widget.allocation.width-1,
		      trace->widget.allocation.y+trace->widget.allocation.height-1);
	gdk_draw_lines(trace->widget.window, trace->widget.style->fg_gc[trace->widget.state],
		       p, trace->ptnum / 2);
}


static gint spectrum_idle_callback(gpointer data)
{
	g_return_val_if_fail(data != NULL, FALSE);
	g_return_val_if_fail(IS_SPECTRUM(data), FALSE);
	SPECTRUM(data)->idlefunc = 0;
	if (!GTK_WIDGET_DRAWABLE(GTK_WIDGET(data)))
		return FALSE;
	if (!SPECTRUM(data)->pt || !SPECTRUM(data)->ptnum)
		return FALSE;
	spectrum_draw(SPECTRUM(data));
	return FALSE;  /* don't call this callback again */
}


void spectrum_addvalue(Spectrum *trace, gint16 y)
{
	g_return_if_fail(trace != NULL);
	g_return_if_fail(IS_SPECTRUM(trace));
	trace->pt[trace->ptptr] = y;
	trace->ptptr++;
	if (trace->ptptr >= trace->ptnum)
		trace->ptptr = 0;
	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(trace))) {
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, spectrum_idle_callback, trace);
	}
}

void spectrum_addvalues(Spectrum *trace, guint num, gint16 *y)
{
	guint i;

	g_return_if_fail(trace != NULL);
	g_return_if_fail(IS_SPECTRUM(trace));
	g_return_if_fail(y != NULL);
	for (i = 0; i < num; i++) {
		trace->pt[trace->ptptr] = y[i];
		trace->ptptr++;
		if (trace->ptptr >= trace->ptnum)
			trace->ptptr = 0;
	}
	if (GTK_WIDGET_DRAWABLE(GTK_WIDGET(trace))) {
		if (!trace->idlefunc)
			trace->idlefunc = gtk_idle_add_priority(PRIO, spectrum_idle_callback, trace);
	}
}
