#include <stdint.h>
#include <assert.h>	/* sheer abuse */
#include <math.h>

#include <usrp2/usrp2.h>
#include <usrp2/rx_nop_handler.h>
#include <usrp2/copiers.h>

#include <fftw3.h>

#include "SDL.h"
#include "SDL_gfxPrimitives.h"


//#define	N	1024
#define	XX	100
#define	N	(1024*XX)
#define	AVG	1


#define	SCALE	(2*XX)
#define	XRES	(N/SCALE)
#define	YRES	(768*XX/SCALE)
#define	DECIMATION	16

static fftw_plan plan;
static fftw_complex *in, *out;
static double *w;
static SDL_Surface *surf;


#define	SIZE	(surf->h*surf->pitch)


static int frames = 0;
static int paws = 0;
static int y_scale = 60;
static uint32_t *bg = NULL;
static long rate;
static int snapshot = 0;
static int avg_mode = 1;


static void draw(const double *s)
{
	int i, x, y, first, last;

	if (!bg)
		bg = (uint32_t *) calloc(SIZE, 1);
	if (paws)
		return;
	SDL_LockSurface(surf);
	memcpy(surf->pixels, bg, SIZE);
	for (y = y_scale; y < YRES; y += y_scale)
		hlineColor(surf, 0, XRES-1, y, 0x008040ff);
	uint32_t color = snapshot ? 0xff4000ff : 0xffffffff;
	last = YRES-1;
	for (i = 0; i != XRES; i++) {
		y = s[i]*y_scale;
		y = YRES-y+4*y_scale;
		if (y < 0)
			y = 0;
		if (y >= YRES)
			y = YRES-1;
		x = (i+XRES/2) % XRES;
		if (i)
			vlineColor(surf, x, last, y, color);
		if (!i)
			first = y;
		last = y;
	}
	vlineColor(surf, x+1, first, last, color);
	if (snapshot) {
		memcpy(bg, surf->pixels, SIZE);
		snapshot = 0;
	}
	SDL_UnlockSurface(surf);
	SDL_UpdateRect(surf, 0, 0, 0, 0);
	frames++;
}


static void collect(void)
{
	static double sum[N];
	static int cnt = 0;
	int i, x, j;

	i = 0;
	for (x = 0; x != XRES; x++)
		for (j = 0; j != SCALE; j++) {
			double s = pow(out[i][0], 2)+pow(out[i][1], 2);

			if (avg_mode)
				sum[x] += s;
			else {
				if (sum[x] < s)
					sum[x] = s;
			}
			i++;
		}
	if (++cnt != AVG)
		return;
	cnt = 0;
	for (i = 0; i != XRES; i++)
		if (avg_mode)
			sum[i] = log(sum[i]/AVG/SCALE)/log(10);
		else
			sum[i] = log(sum[i])/log(10);
	draw(sum);
	memset(sum, 0, sizeof(sum));
}


class rx : public usrp2::rx_nop_handler
{
public:
	rx(uint64_t max_samples) : usrp2::rx_nop_handler(max_samples)
	{
	}

	bool operator()(const uint32_t *items, size_t nitems,
	    const usrp2::rx_metadata *metadata);

	~rx(void)
	{
	}

};


bool rx::operator()(const uint32_t *items, size_t nitems,
	    const usrp2::rx_metadata *metadata)
{
	static std::complex<int16_t> host_items[N];
	static size_t have = 0, copy, over;
	bool ok;
	size_t i;

	ok = rx_nop_handler::operator()(items, nitems, metadata);
	copy = N-have;
	if (copy > nitems)
		copy = nitems;
	usrp2::copy_u2_16sc_to_host_16sc(copy, items, host_items+have);
//fprintf(stderr, "nitems %u have %u copy %u\n", (unsigned) nitems, (unsigned) have, (unsigned) copy);
	have += copy;
	if (have == N) {
		for (i = 0; i != nitems; i++) {
			in[i][0] = host_items[i].real()*w[i];
			in[i][1] = host_items[i].imag()*w[i];
		}
		fftw_execute(plan);
		collect();
		have = 0;
	}
	over = nitems-copy;
	if (over)
		usrp2::copy_u2_16sc_to_host_16sc(over, items+copy,
		    host_items+have);
	have += over;
	return ok;
}


static int key(SDLKey sym)
{
	switch (sym) {
	case SDLK_a:
		avg_mode = 1;
		break;
	case SDLK_c:
		memset(bg, 0, SIZE);
		break;
	case SDLK_f:
		fprintf(stderr, "%d\n", frames);
		break;
	case SDLK_m:
		avg_mode = 0;
		break;
	case SDLK_p:
		paws = !paws;
		break;
	case SDLK_q:
		return 1;
	case SDLK_s:
		memset(bg, 0, SIZE);
		snapshot = 1;
		break;
	default:
		break;
	}
	return 0;
}


static void make_window(void)
{
	int i;

	w = (double *) calloc(N, sizeof(double));
	for (i = 0; i != N; i++)
// hamming
		w[i] = 0.54-0.46*cos(M_PI*2*i/(N-1));
// hann		w[i] = 0.5-0.5*cos(M_PI*2*i/(N-1));
// blackman	w[i] = 0.42-0.5*cos(2*M_PI*i/(N-1))+0.08*cos(4*M_PI*i/(N-1));
}


int main(void)
{
	usrp2::usrp2::sptr u;
	usrp2::rx_nop_handler::sptr handler;
	usrp2::tune_result tr;

	handler = usrp2::rx_nop_handler::sptr(new rx(N));
	u = usrp2::usrp2::make("eth0", "");
	assert(u->stop_rx_streaming());
	assert(u->set_rx_gain(46));
	assert(u->set_rx_center_freq(2450000000ULL, &tr));
	assert(u->set_rx_decim(DECIMATION));
//	rate = u->adc_rate()/u->decim():
//	u->set_sample_rate(rate);

	u->adc_rate(&rate);

	in = (fftw_complex *) fftw_malloc(sizeof(fftw_complex)*N);
	out = (fftw_complex *) fftw_malloc(sizeof(fftw_complex)*N);
	plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE);

	make_window();

	if (SDL_Init(SDL_INIT_VIDEO) < 0) {
		fprintf(stderr, "SDL_init: %s\n", SDL_GetError());
		exit(1);
	}
	atexit(SDL_Quit);

	surf = SDL_SetVideoMode(XRES, YRES, 0, SDL_SWSURFACE);
	if (!surf) {
		fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError());
		exit(1);
	}

	assert(u->start_rx_streaming(0));
	while (1||(!handler->has_errored_p() && !handler->has_finished_p())) {
//	while (!handler->has_finished_p()) {
		SDL_Event event;

		if (SDL_PollEvent(&event)) {
			if (event.type == SDL_KEYDOWN)
				if (key(event.key.keysym.sym))
					break;
		}
		assert(u->rx_samples(0, handler.get()));
		u->rx_samples(0, handler.get());
	}
	u->stop_rx_streaming(0);
	return 0;
}
