/*
 * This code is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This code 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 General Public License for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "spectool_container.h"
#include "wispy_hw_gen1.h"
#include "wispy_hw_24x.h"
#include "wispy_hw_dbx.h"
#include "ubertooth_hw_u1.h"

int spectool_get_state(spectool_phy *phydev) {
	return phydev->state;
}

char *spectool_get_error(spectool_phy *phydev) {
	return phydev->errstr;
}

char *spectool_phy_getname(spectool_phy *phydev) {
	if (phydev->device_spec == NULL)
		return NULL;

	return phydev->device_spec->device_name;
}

int spectool_phy_getdevid(spectool_phy *phydev) {
	if (phydev->device_spec == NULL)
		return 0;

	return phydev->device_spec->device_id;
}

void spectool_phy_setname(spectool_phy *phydev, char *name) {
	if (phydev->device_spec == NULL)
		return;

	snprintf(phydev->device_spec->device_name, SPECTOOL_PHY_NAME_MAX, "%s", name);
}

int spectool_phy_open(spectool_phy *phydev) {
	if (phydev->open_func == NULL)
		return 0;

	return (*(phydev->open_func))(phydev);
}

int spectool_phy_close(spectool_phy *phydev) {
	if (phydev->close_func == NULL)
		return 0;

	return (*(phydev->close_func))(phydev);
}

int spectool_phy_poll(spectool_phy *phydev) {
	if (phydev->poll_func == NULL)
		return SPECTOOL_POLL_ERROR;

	return (*(phydev->poll_func))(phydev);
}

int spectool_phy_getpollfd(spectool_phy *phydev) {
	if (phydev == NULL)
		return -1;

	if (phydev->pollfd_func == NULL)
		return -1;

	return (*(phydev->pollfd_func))(phydev);
}

void spectool_phy_setcalibration(spectool_phy *phydev, int enable) {
	if (phydev->setcalib_func != NULL)
		return (*(phydev->setcalib_func))(phydev, enable);
}

int spectool_phy_getflags(spectool_phy *phydev) {
	return phydev->device_spec->device_flags;
}

int spectool_phy_setposition(spectool_phy *phydev, int in_profile, 
						  int start_khz, int res_hz) {
	if (phydev->setposition_func != NULL)
		return (*(phydev->setposition_func))(phydev, in_profile, start_khz, res_hz);

	snprintf(phydev->errstr, SPECTOOL_ERROR_MAX, "Device does not support setting "
			 "scan position or resolution");
	return -1;
}

spectool_sample_sweep *spectool_phy_getsweep(spectool_phy *phydev) {
	if (phydev->getsweep_func == NULL)
		return NULL;

	return (*(phydev->getsweep_func))(phydev);
}

spectool_sweep_cache *spectool_cache_alloc(int nsweeps, int calc_peak, int calc_avg) {
	int x;
	spectool_sweep_cache *c = (spectool_sweep_cache *) malloc(sizeof(spectool_sweep_cache));

	c->sweeplist = 
		(spectool_sample_sweep **) malloc(sizeof(spectool_sample_sweep *) * nsweeps);
	c->avg = NULL;
	c->peak = NULL;
	c->roll_peak = NULL;
	c->latest = NULL;

	for (x = 0; x < nsweeps; x++) {
		c->sweeplist[x] = NULL;
	}

	c->num_alloc = nsweeps;
	c->pos = -1;
	c->looped = 0;
	c->num_used = 0;

	c->calc_peak = calc_peak;
	c->calc_avg = calc_avg;

	return c;
}

void spectool_cache_free(spectool_sweep_cache *c) {
	if (c->avg != NULL)
		free(c->avg);
	if (c->peak != NULL)
		free(c->peak);
	free(c->sweeplist);
}

void spectool_cache_clear(spectool_sweep_cache *c) {
	if (c->avg != NULL)
		free(c->avg);
	c->avg = NULL;

	if (c->peak != NULL)
		free(c->peak);
	c->peak = NULL;

	if (c->roll_peak != NULL)
		free(c->roll_peak);
	c->roll_peak = NULL;

	c->pos = 0;
	c->looped = 0;
	c->num_used = 0;
}

void spectool_cache_append(spectool_sweep_cache *c, spectool_sample_sweep *s) {
	int x, y, sum = 0;
	int *avgdata, *avgsum;
	int navg;
	int nsampled;

	/* Make sure we don't overflow and crash, should be sufficient
	 * to make sure that the new sweep is reasonable */
	if (c->avg != NULL && c->avg->num_samples != s->num_samples) 
		return;

	if (c->pos == (c->num_alloc - 1)) {
		c->looped = 1;
		c->pos = 0;
	} else {
		c->pos++;

		if (c->num_used < c->num_alloc)
			c->num_used++;
	}

	if (c->sweeplist[c->pos] != NULL) {
		free(c->sweeplist[c->pos]);
	}

	if (c->looped)
		nsampled = c->num_alloc;
	else
		nsampled = c->pos;

	c->sweeplist[c->pos] = 
		(spectool_sample_sweep *) malloc(SPECTOOL_SWEEP_SIZE(s->num_samples));

	memcpy(c->sweeplist[c->pos], s, SPECTOOL_SWEEP_SIZE(s->num_samples));

	c->latest = c->sweeplist[c->pos];

	if (c->avg == NULL && c->calc_avg) {
		c->avg = (spectool_sample_sweep *) malloc(SPECTOOL_SWEEP_SIZE(s->num_samples));
		memcpy(c->avg, s, SPECTOOL_SWEEP_SIZE(s->num_samples));
	} else if (c->calc_avg) {
		/* Reset average times */
		c->avg->tm_start.tv_sec = 0;
		c->avg->tm_start.tv_usec = 0;
		c->avg->tm_end.tv_sec = 0;
		c->avg->tm_end.tv_usec = 0;

		/* Allocate a large int for summing */
		avgdata = (int *) malloc(sizeof(int) * c->avg->num_samples);
		avgsum = (int *) malloc(sizeof(int) * c->avg->num_samples);
		for (x = 0; x < c->avg->num_samples; x++) {
			avgdata[x] = 0;
			avgsum[x] = 0;
		}

		/* Sum them up */

		for (x = 0; x < nsampled; x++) {
			if (c->sweeplist[x] == NULL)
				continue;

			for (y = 0; y < c->avg->num_samples; y++) {
				avgdata[y] += c->sweeplist[x]->sample_data[y];
				avgsum[y]++;
			}

			/* Update the time of the average network */
			if (c->sweeplist[x]->tm_start.tv_sec > c->avg->tm_start.tv_sec) {
				c->avg->tm_start.tv_sec = c->sweeplist[x]->tm_start.tv_sec;
				c->avg->tm_start.tv_usec = c->sweeplist[x]->tm_start.tv_usec;
			} else if (c->sweeplist[x]->tm_start.tv_sec == c->avg->tm_start.tv_sec &&
					   c->sweeplist[x]->tm_start.tv_usec > c->avg->tm_start.tv_usec) {
				c->avg->tm_start.tv_usec = c->sweeplist[x]->tm_start.tv_usec;
			}
			if (c->sweeplist[x]->tm_end.tv_sec > c->avg->tm_end.tv_sec) {
				c->avg->tm_end.tv_sec = c->sweeplist[x]->tm_end.tv_sec;
				c->avg->tm_end.tv_usec = c->sweeplist[x]->tm_end.tv_usec;
			} else if (c->sweeplist[x]->tm_end.tv_sec == c->avg->tm_end.tv_sec &&
					   c->sweeplist[x]->tm_end.tv_usec > c->avg->tm_end.tv_usec) {
				c->avg->tm_end.tv_usec = c->sweeplist[x]->tm_end.tv_usec;
			}

		}

		for (x = 0; x < c->avg->num_samples; x++) {
			if (avgsum[x] == 0) {
				c->avg->sample_data[x] = 0;
				continue;
			}

			c->avg->sample_data[x] = (float) avgdata[x] / (float) avgsum[x];
		}

		free(avgdata);
		free(avgsum);
	}

	/* Allocate or update the peak.  We don't track peak timelines */
	if (c->peak == NULL && c->calc_peak) {
		c->peak = (spectool_sample_sweep *) malloc(SPECTOOL_SWEEP_SIZE(s->num_samples));
		memcpy(c->peak, s, SPECTOOL_SWEEP_SIZE(s->num_samples));
	
		/* This will never be allocated if peak is not */
		c->roll_peak = (spectool_sample_sweep *) malloc(SPECTOOL_SWEEP_SIZE(s->num_samples));
		memcpy(c->roll_peak, s, SPECTOOL_SWEEP_SIZE(s->num_samples));
	} else if (c->calc_peak) {

		for (x = 0; x < c->peak->num_samples; x++) {
			if (c->peak->sample_data[x] < s->sample_data[x]) {
				c->peak->sample_data[x] = s->sample_data[x];
			}
		}

		memcpy(c->roll_peak, s, SPECTOOL_SWEEP_SIZE(s->num_samples));
		for (x = 0; x < nsampled; x++) {
			for (y = 0; y < c->roll_peak->num_samples; y++) {
				// printf("debug - compare sampled %d pos %d loop %d set %d sample %d\n", nsampled, c->pos, c->looped, x, y);
				if (c->roll_peak->sample_data[y] < c->sweeplist[x]->sample_data[y]) {
					c->roll_peak->sample_data[y] = c->sweeplist[x]->sample_data[y];
				}
			}
		}

	}
}

void spectool_cache_itr_init(spectool_sweep_cache *c, spectool_sweep_cache_itr *i) {
	i->pos_start = c->pos;
	i->pos_cur = c->pos + 1;
	i->looped_start = 0;
}

spectool_sample_sweep *spectool_cache_itr_next(spectool_sweep_cache *c, spectool_sweep_cache_itr *i) {
	// if we've covered it all, punt
	if (i->pos_cur == i->pos_start) {
		if (i->looped_start == 0)
			i->looped_start = 1;
		else
			return NULL;
	}

	// 'next' is actually backwards
	i->pos_cur--;

	// if we've hit the bottom but we're looped, go to the max filled slot
	if (i->pos_cur < 0 && c->looped) i->pos_cur = c->num_used - 1;
	// Or we're done
	if (i->pos_cur < 0) return NULL;
	// safety
	if (i->pos_cur >= c->num_used) return NULL;

	return c->sweeplist[i->pos_cur];
}

void spectool_device_scan_init(spectool_device_list *list) {
	list->list = (spectool_device_rec *) malloc(sizeof(spectool_device_rec) * MAX_SCAN_RESULT);
	list->num_devs = 0;
	list->max_devs = MAX_SCAN_RESULT;
}

int spectool_device_scan(spectool_device_list *list) {
	spectool_device_scan_init(list);

	if (wispy1_usb_device_scan(list) < 0) {
		return -1;
	}

	if (wispy24x_usb_device_scan(list) < 0) {
		return -1;
	}

	if (wispydbx_usb_device_scan(list) < 0) {
		return -1;
	}

	if (ubertooth_u1_device_scan(list) < 0) {
		return -1;
	}

	return list->num_devs;
}

void spectool_device_scan_free(spectool_device_list *list) {
	int x;

	for (x = 0; x < list->num_devs && x < list->max_devs; x++) {
		if (list->list[x].hw_rec != NULL)
			free(list->list[x].hw_rec);
	}

	list->num_devs = 0;
	list->max_devs = 0;

	if (list->list != NULL)
		free(list->list);
}

int spectool_device_init(spectool_phy *phydev, spectool_device_rec *rec) {
	return (*(rec->init_func))(phydev, rec);
}

spectool_sample_sweep *spectool_phy_getcurprofile(spectool_phy *phydev) {
	if (phydev == NULL)
		return NULL;

	if (phydev->device_spec->cur_profile < 0 || 
		phydev->device_spec->cur_profile > phydev->device_spec->num_sweep_ranges)
		return NULL;

	return &(phydev->device_spec->supported_ranges[phydev->device_spec->cur_profile]);
}

