#include <pthread.h>

#include "support.h"
#include "main.h"
#include "wf.h"
#include "fft.h"
#include "scope.h"
#include "misc.h"
#include "trx.h"
#include "prefs.h"

#define	SRATE	8000
#define	DBSPAN	50.0
#define	OVERLAP	1024

G_LOCK_DEFINE_STATIC(wf_mutex);

struct wf {
	struct fft	*fft;
	gfloat		*window;
	gfloat		*buffer;
	complex		*ibuf;
	complex		*obuf;
	gint		fftlen;
	gint		buflen;
	gint		pointer;
	gint		start;
	gboolean	on;
	gboolean	pause;
};

static struct wf *wf = NULL;

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

static void setwindow(gint window);

#define	min(a,b)	((a)<(b)?(a):(b))
#define	max(a,b)	((a)>(b)?(a):(b))

gint init_wf(gint fftlen, gfloat freq)
{
	Waterfall *wfall;
	gfloat start, stop, span;
	gboolean pause, on;
	gint ret = -1;

	G_LOCK(wf_mutex);

	pause = FALSE;
	on = TRUE;

	if (wf != NULL && wf->fftlen != fftlen) {
		pause = wf->pause;
		on = wf->on;
		clear_wf();
	}

	if ((wf = calloc(1, sizeof(struct wf))) == NULL) {
		g_warning("init_wf: malloc failed\n");
		clear_wf();
		goto end;
	}

	wf->pause = pause;
	wf->on = on;

	wf->fftlen = fftlen;
	wf->buflen = fftlen * 4;

	if ((wf->fft = init_fft(wf->fftlen)) == NULL) {
		g_warning("init_wf: init_fft failed\n");
		clear_wf();
		goto end;
	}

	wf->window = calloc(wf->fftlen, sizeof(gfloat));
	wf->buffer = calloc(wf->buflen, sizeof(gfloat));
	wf->ibuf = calloc(wf->fftlen, sizeof(complex));
	wf->obuf = calloc(wf->fftlen, sizeof(complex));

	if (!wf->window || !wf->buffer || !wf->ibuf || !wf->obuf) {
		g_warning("init_wf: malloc failed\n");
		clear_wf();
		goto end;
	}

	span  = (float) WATERFALL_WIDTH * SRATE / fftlen;
	stop  = min(freq + span / 2.0, 2500);
	start = max(stop - span, 500.0);

	wf->start = (gint) (start * fftlen / SRATE);

	if ((wfall = WATERFALL(lookup_widget(appwindow, "waterfall"))))
		waterfall_setfreqs(wfall, start, start + span);

	setwindow(WINDOW_TRIA);

	ret = 0;
end:
	G_UNLOCK(wf_mutex);
	return ret;
}

void clear_wf(void)
{
	if (wf) {
		clear_fft(wf->fft);
		free(wf->window);
		free(wf->buffer);
		free(wf->ibuf);
		free(wf->obuf);
	}
	wf = NULL;
}

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

gint wf_ftox(gfloat f)
{
	if (wf != NULL)
		return (gint) ((f * wf->fftlen / SRATE) - wf->start + 0.5);

	return 0;
}

gint wf_ftow(gfloat f)
{
	if (wf != NULL)
		return (gint) ((f * wf->fftlen / SRATE) + 0.5);

	return 0;
}

gfloat wf_xtof(gint x)
{
	if (wf != NULL)
		return (gfloat) ((x + wf->start) * SRATE / wf->fftlen);

	return 0.0;
}

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

void wf_setdata(gfloat *samples, gint len)
{
	gint i;

	G_LOCK(wf_mutex);

	if (wf != NULL) {
		for (i = 0; i < len; i++) {
			if (wf->pointer >= wf->buflen) {
				/* buffer overflow */
				break;
			}
			wf->buffer[wf->pointer++] = samples[i];
		}
	}

	G_UNLOCK(wf_mutex);
}

void wf_setstate(gboolean on)
{
	G_LOCK(wf_mutex);

	if (wf != NULL)
		wf->on = on;

	G_UNLOCK(wf_mutex);
}

void wf_togglepause(void)
{
	G_LOCK(wf_mutex);

	if (wf != NULL)
		wf->pause = !wf->pause;

	G_UNLOCK(wf_mutex);
}

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

static void setwindow(gint window)
{
	gfloat pwr = 0.0;
	gint i;

	switch (window) {
	case WINDOW_RECT:
		for (i = 0; i < wf->fftlen; i++)
			wf->window[i] = 1.0;
		break;

	case WINDOW_TRIA:
		for (i = 0; i < wf->fftlen; i++) {
			if (i < wf->fftlen / 2)
				wf->window[i] = 2.0 * i / wf->fftlen;
			else
				wf->window[i] = 2.0 * (wf->fftlen - i) / wf->fftlen;
		}
		break;

	case WINDOW_HAMM:
		for (i = 0; i < wf->fftlen; i++)
			wf->window[i] = hamming(i / (wf->fftlen - 1.0));
		break;

	default:
		g_print("invalid window function\n");
		break;
	}

	for (i = 0; i < wf->fftlen; i++)
		pwr += wf->window[i] * wf->window[i];

	for (i = 0; i < wf->fftlen; i++)
		wf->window[i] /= pwr;
}

void wf_setwindow(gint window)
{
	G_LOCK(wf_mutex);

	if (wf != NULL)
		setwindow(window);

	G_UNLOCK(wf_mutex);
}

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

void wf_run(void)
{
	guchar wfdata[WATERFALL_WIDTH];
	gfloat scdata[SCOPE_WIDTH];
	Waterfall *wfall;
	Scope *scope;
	guint i, x1, x2;
	gfloat x;

	G_LOCK(wf_mutex);

	if (!wf || !wf->on || wf->pause || wf->pointer < wf->fftlen) {
		G_UNLOCK(wf_mutex);
		return;
	}

	for (i = 0; i < wf->fftlen; i++) {
		wf->ibuf[i].re = wf->buffer[i] * wf->window[i];
		wf->ibuf[i].im = 0.0;
	}

	for (i = OVERLAP; i < wf->pointer; i++)
		wf->buffer[i - OVERLAP] = wf->buffer[i];

	wf->pointer -= OVERLAP;

	fft(wf->fft, wf->ibuf, wf->obuf);

	memcpy(scdata, wf->buffer, sizeof(scdata));

	for (i = 0; i < WATERFALL_WIDTH; i++) {
		/* small bias to avoid upsetting log10() */
		x = cmod(wf->obuf[i + wf->start]) + 1e-10;
		x = -10.0 * log10(x) * 256.0 / prefs.sensitivity;
		wfdata[i] = (guchar) (255.0 - clamp(x, 0.0, 255.0));
	}

	G_UNLOCK(wf_mutex);

	gdk_threads_enter();

	wfall = WATERFALL(lookup_widget(appwindow, "waterfall"));
	waterfall_setdata(wfall, wfdata);

	x1 = wf_ftox(trx_get_freq());
	x2 = wf_ftow(trx_get_bandwidth());
	waterfall_setmarker2(wfall, x1);
	waterfall_setwidth(wfall, x2);

	scope = SCOPE(lookup_widget(appwindow, "scope"));
	scope_setdata(scope, scdata);

	gdk_threads_leave();

	return;
}

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