#include <Python.h>
#include <stdlib.h>
#include <math.h>
#include <complex.h>	// Use native C99 complex type for fftw3
#include <sys/types.h>

#include "quisk.h"
#include "freedv.h"

int DEBUG;

#define MAX_RECEIVERS	2

typedef struct {	// from comp.h
  float real;
  float imag;
} COMP;

struct freedv;		// from freedv_api.h
typedef void (*freedv_callback_rx)(void *, char);
typedef char (*freedv_callback_tx)(void *);
/* Protocol bits are packed MSB-first */
/* Called when a frame containing protocol data is decoded */
typedef void (*freedv_callback_protorx)(void *, char *);
/* Called when a frame containing protocol data is to be sent */
typedef void (*freedv_callback_prototx)(void *, char *);
/* Data packet callbacks */
/* Called when a packet has been received */
typedef void (*freedv_callback_datarx)(void *, unsigned char *packet, size_t size);
/* Called when a new packet can be send */
typedef void (*freedv_callback_datatx)(void *, unsigned char *packet, size_t *size);

/* advanced freedv open options required by some modes */
struct freedv_advanced {	// from freedv_api.h
	int interleave_frames;
};

#ifdef MS_WINDOWS
#include <windows.h>
HMODULE WINAPI hLib;
#define GET_HANDLE1			hLib = LoadLibrary(".\\freedvpkg\\libcodec2.dll")
#define GET_HANDLE2			hLib = LoadLibrary(".\\freedvpkg\\libcodec2_32.dll")
#define GET_HANDLE3			hLib = LoadLibrary(".\\freedvpkg\\libcodec2_64.dll")
#define GET_HANDLE4			hLib = LoadLibrary("libcodec2.dll")
#define GET_ADDR(name)		(void *)GetProcAddress(hLib, name)
#define CLOSE_LIB			FreeLibrary(hLib)
#else
#include <dlfcn.h>
void * hLib;
#define GET_HANDLE1			hLib = dlopen("./freedvpkg/libcodec2.so", RTLD_LAZY)
#define GET_HANDLE2			hLib = dlopen("./freedvpkg/libcodec2_32.so", RTLD_LAZY)
#define GET_HANDLE3			hLib = dlopen("./freedvpkg/libcodec2_64.so", RTLD_LAZY)
#define GET_HANDLE4			hLib = dlopen("libcodec2.so", RTLD_LAZY)
#define GET_ADDR(name)		dlsym(hLib,  name)
#define CLOSE_LIB			dlclose(hLib)
#endif

static int requested_mode = -1;			// requested mode
int freedv_current_mode = -1;			// the current running mode
static int quisk_freedv_squelch;
static int interleave_frames = 1;
static int freedv_version = -1;
static int quisk_set_tx_bpf = 1;

#define SPEECH_BUF_SIZE		3000		// speech buffer size
static struct _rx_channel{
	struct freedv * hFreedv;
	COMP * demod_in;
	int rxdata_index;
	short speech_out[SPEECH_BUF_SIZE];		// output buffer
	int speech_available;					// number of samples in output buffer
	int playing;							// are we currently returning speech samples?
} rx_channel[MAX_RECEIVERS] ;

// freedv_version is the library version number, or
//   -1		no library was found
//   -2		a library was found, but freedv_get_version is missing

// FreeDV API functions:
// open, close
struct freedv * (*freedv_open)(int mode);
struct freedv * (*freedv_open_advanced)(int mode, struct freedv_advanced *adv);
void (*freedv_close)(struct freedv *freedv);
// Transmit
void (*freedv_tx)(struct freedv *freedv, short *, short *);
void (*freedv_comptx)(struct freedv *freedv, COMP *, short *);
// Receive
int (*freedv_nin)(struct freedv *freedv);
int (*freedv_rx)(struct freedv *freedv, short *, short demod_in[]);
int (*freedv_floatrx)(struct freedv *freedv, short *, float demod_in[]);
int (*freedv_comprx)(struct freedv *freedv, short *, COMP demod_in[]);
// Set parameters
void (*freedv_set_callback_txt)		(struct freedv *freedv, freedv_callback_rx rx, freedv_callback_tx tx, void *callback_state);
void (*freedv_set_test_frames)			(struct freedv *freedv, int test_frames);
void (*freedv_set_smooth_symbols)		(struct freedv *freedv, int smooth_symbols);
void (*freedv_set_squelch_en)			(struct freedv *freedv, int squelch_en);
void (*freedv_set_snr_squelch_thresh)	(struct freedv *freedv, float snr_squelch_thresh);
void (*freedv_set_tx_bpf)		(struct freedv *freedv, int val);
// Get parameters
int (*freedv_get_version)(void);
void (*freedv_get_modem_stats)(struct freedv *freedv, int *sync, float *snr_est);
int (*freedv_get_test_frames)			(struct freedv *freedv);
int (*freedv_get_n_speech_samples)		(struct freedv *freedv);
int (*freedv_get_n_max_modem_samples)	(struct freedv *freedv);
int (*freedv_get_n_nom_modem_samples)	(struct freedv *freedv);
int (*freedv_get_total_bits)			(struct freedv *freedv);
int (*freedv_get_total_bit_errors)		(struct freedv *freedv);
//
int (*freedv_get_sync)					(struct freedv *freedv);
void (*freedv_set_callback_protocol)	(struct freedv *freedv, freedv_callback_protorx rx, freedv_callback_prototx tx, void *callback_state);
void (*freedv_set_callback_data)	(struct freedv *freedv, freedv_callback_datarx datarx, freedv_callback_datatx datatx, void *callback_state);

/* Called when a new packet can be sent */
void my_datatx(void *callback_state, unsigned char *packet, size_t *size) {
	*size = 0;
}

static void GetAddrs(void)
{
	if (DEBUG) printf("Try handle 1\n");
	GET_HANDLE1;
	if (hLib) {		// check the first library name
		freedv_version = -2;
		freedv_get_version = GET_ADDR("freedv_get_version");
		if (freedv_get_version != NULL)
			freedv_version = freedv_get_version();
	}
	if (freedv_version < 10) {		// try the next library
		if (hLib)
			CLOSE_LIB;
		if (DEBUG) printf("Try handle 2\n");
		GET_HANDLE2;
		if (hLib) {
			freedv_version = -2;
			freedv_get_version = GET_ADDR("freedv_get_version");
			if (freedv_get_version != NULL)
				freedv_version = freedv_get_version();
		}
	}
	if (freedv_version < 10) {		// try the next library
		if (hLib)
			CLOSE_LIB;
		if (DEBUG) printf("Try handle 3\n");
		GET_HANDLE3;
		if (hLib) {
			freedv_version = -2;
			freedv_get_version = GET_ADDR("freedv_get_version");
			if (freedv_get_version != NULL)
				freedv_version = freedv_get_version();
		}
	}
	if (freedv_version < 10) {		// try the next library
		if (hLib)
			CLOSE_LIB;
		if (DEBUG) printf("Try handle 4\n");
		GET_HANDLE4;
		if (hLib) {
			freedv_version = -2;
			freedv_get_version = GET_ADDR("freedv_get_version");
			if (freedv_get_version != NULL)
				freedv_version = freedv_get_version();
		}
	}
	if (DEBUG) printf("freedv_version is %d\n", freedv_version);
	if (freedv_version < 10) {
		if (hLib)
			CLOSE_LIB;
		return;
	}

// open, close
	freedv_open = GET_ADDR("freedv_open");
	freedv_open_advanced = GET_ADDR("freedv_open_advanced");
	freedv_close = GET_ADDR("freedv_close");
// Transmit
	freedv_tx = GET_ADDR("freedv_tx");
	freedv_comptx = GET_ADDR("freedv_comptx");
// Receive
	freedv_nin = GET_ADDR("freedv_nin");
	freedv_rx = GET_ADDR("freedv_rx");
	freedv_floatrx = GET_ADDR("freedv_floatrx");
	freedv_comprx = GET_ADDR("freedv_comprx");
// Set parameters
	freedv_set_callback_txt = GET_ADDR("freedv_set_callback_txt");
	freedv_set_callback_protocol = GET_ADDR("freedv_set_callback_protocol");
	freedv_set_callback_data = GET_ADDR("freedv_set_callback_data");
	freedv_set_test_frames = GET_ADDR("freedv_set_test_frames");
	freedv_set_smooth_symbols = GET_ADDR("freedv_set_smooth_symbols");
	freedv_set_squelch_en = GET_ADDR("freedv_set_squelch_en");
	freedv_set_snr_squelch_thresh = GET_ADDR("freedv_set_snr_squelch_thresh");
	freedv_set_tx_bpf = GET_ADDR("freedv_set_tx_bpf");
// Get parameters
	freedv_get_modem_stats = GET_ADDR("freedv_get_modem_stats");
	freedv_get_test_frames = GET_ADDR("freedv_get_test_frames");
	freedv_get_n_speech_samples = GET_ADDR("freedv_get_n_speech_samples");
	freedv_get_n_max_modem_samples = GET_ADDR("freedv_get_n_max_modem_samples");
	freedv_get_n_nom_modem_samples = GET_ADDR("freedv_get_n_nom_modem_samples");
	freedv_get_total_bits = GET_ADDR("freedv_get_total_bits");
	freedv_get_total_bit_errors = GET_ADDR("freedv_get_total_bit_errors");
	freedv_get_sync = GET_ADDR("freedv_get_sync");		// requires version 11
	return;
}

static int quisk_freedv_rx(complex double * cSamples, double * dsamples, int count, int bank)	// Called from the sound thread.
{	// Input digital modulation is cSamples; decoded voice is dsamples.  Each "bank" is a stream of audio.
	int i, nout, need, have, sync;
	int n_speech_samples;
	complex double cx;
	double scale = (double)CLIP32 / CLIP16;	// convert 32 bits to 16 bits
	struct freedv * hF;
	struct _rx_channel * pCh;

	if (cSamples == NULL) {		// shutdown
		for (i = 0; i < MAX_RECEIVERS; i++) {
			if (rx_channel[i].demod_in) {
				free(rx_channel[i].demod_in);
				rx_channel[i].demod_in = NULL;
			}
		}
		return 0;
	}

	if (bank < 0 || bank >= MAX_RECEIVERS)
		return 0;
	hF = rx_channel[bank].hFreedv;
	if ( ! hF)
		return 0;
	pCh = rx_channel + bank;
	n_speech_samples = freedv_get_n_speech_samples(hF);
	nout = 0;
	need = freedv_nin(hF);
	for (i = 0; i < count; i++) {
		cx = cRxFilterOut(cSamples[i], bank, 0);
		if (rxMode == FDV_L)		// lower sideband
			cx = conj(cx);
#if 0
		pCh->demod_in[pCh->rxdata_index].real = creal(cx) / scale;
		pCh->demod_in[pCh->rxdata_index].imag = cimag(cx) / scale;
#else
		pCh->demod_in[pCh->rxdata_index].real = (creal(cx) - cimag(cx)) / scale;
		pCh->demod_in[pCh->rxdata_index].imag = 0;
#endif
		pCh->rxdata_index++;
		if (pCh->rxdata_index >= need) {
			if (pCh->speech_available + n_speech_samples < SPEECH_BUF_SIZE) {		// check for buffer space
				have = freedv_comprx(hF, pCh->speech_out + pCh->speech_available, pCh->demod_in);
				if (freedv_version > 10)
					sync = freedv_get_sync(hF);
				else
					freedv_get_modem_stats(hF, &sync, NULL);
				if (freedv_current_mode == 0) {		// mode 1600
					if (sync)		// throw away speech if not in sync
						pCh->speech_available += have;
				}
				else if (pCh->speech_available < SPEECH_BUF_SIZE * 2 / 3) {
					pCh->speech_available += have;	// keep speech if there is space
				}
				else {
					if (DEBUG) printf("Close to maximum in speech output buffer\n");
				}
			}
			else {		// no space in buffer
				if (DEBUG) printf("Overflow in speech output buffer\n");
			}
			pCh->rxdata_index = 0;
			need = freedv_nin(hF);
		}
	}
	if ( ! pCh->playing) {
		if (pCh->speech_available >= 2 * n_speech_samples) {
			pCh->playing = 1;
		}
		else {		// return zero samples
			for (i = 0; i < count; i++)
				dsamples[i] = 0;
			//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
			return count;
		}
	}
	for (nout = 0; nout < pCh->speech_available && nout < count; nout++)
		dsamples[nout] = pCh->speech_out[nout] * scale * 0.7;
	if (nout) {
		pCh->speech_available -= nout;
		memmove(pCh->speech_out, pCh->speech_out + nout, (pCh->speech_available) * sizeof(short));
	}
	if ( ! pCh->speech_available) {
		pCh->playing = 0;
		while (nout < count)
			dsamples[nout++] = 0;
	}
	//if (DEBUG) printf("Rx buffer playing %d available %d\n", pCh->playing, pCh->speech_available);
	return nout;
}

static int quisk_freedv_tx(complex double * cSamples, double * dsamples, int count)	// Called from the sound thread.
{	// Input voice samples are dsamples; output digital modulation is cSamples.
	int i, nout;
	int n_speech_samples;
	int n_nom_modem_samples;
	static COMP * mod_out = NULL;
	static short * speech_in = NULL;
	static int speech_index=0, mod_index=0;

	if (dsamples == NULL) {		// shutdown
		if (mod_out)
			free(mod_out);
		mod_out = NULL;
		if (speech_in)
			free(speech_in);
		speech_in = NULL;
		return 0;
	}
	if ( ! rx_channel[0].hFreedv)
		return 0;
	n_speech_samples = freedv_get_n_speech_samples(rx_channel[0].hFreedv);
	n_nom_modem_samples = freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv);
	if (mod_out == NULL) {		// initialize
		mod_out = (COMP *)malloc(sizeof(COMP) * n_nom_modem_samples);
		memset(mod_out, 0, sizeof(COMP) * n_nom_modem_samples);
		speech_in = (short*)malloc(sizeof(short) * n_speech_samples);
		speech_index=0;
		mod_index=0;
	}
	nout = 0;
	for (i = 0; i < count; i++) {
		speech_in[speech_index++] = (short)dsamples[i];
		if (speech_index >= n_speech_samples) {
			// Calculate a new block, but first write out the rest of the old block
			for ( ; mod_index < n_nom_modem_samples; mod_index++)
				cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
			freedv_comptx(rx_channel[0].hFreedv, mod_out, speech_in);
			mod_index = 0;
			speech_index = 0;
		}
		else {		// write out samples slowly
			if (mod_index < n_nom_modem_samples) {
				cSamples[nout++] = mod_out[mod_index].real + I * mod_out[mod_index].imag;
				mod_index++;
			}
		}
	}
	if (rxMode == FDV_L)
		for (i = 0; i < nout; i++)
			cSamples[i] = conj(cSamples[i]);
	return nout;
}

#define TX_MSG_SIZE		80
static char quisk_tx_msg[TX_MSG_SIZE];

static char get_next_tx_char(void * callback_state)
{
	char c;
	static int index = 0;

	c = quisk_tx_msg[index++];
	if (index >= TX_MSG_SIZE)
		index = 0;
	if ( ! c) {
		index = 0;
		c = quisk_tx_msg[index++];
	}
	return c;
}

#define RX_MSG_SIZE		80
static char quisk_rx_msg[RX_MSG_SIZE + 1];

static void put_next_rx_char(void * callback_state, char ch)
{
	if (ch == '\n' || ch == '\r')
		ch = ' ';
	if (ch < 32 || ch > 126)	// printable characters
		return;
	if (strlen(quisk_rx_msg) < RX_MSG_SIZE)
		strncat(quisk_rx_msg, &ch, 1);
}

PyObject * quisk_freedv_get_rx_char(PyObject * self, PyObject * args)	// Called from the GUI thread.
{
	PyObject * txt;

	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	txt = PyString_FromString(quisk_rx_msg);
	quisk_rx_msg[0] = 0;
	return txt;
}

static void CloseFreedv(void)	// Called from the GUI thread or sound thread
{
	int i;

	for (i = 0; i < MAX_RECEIVERS; i++) {
		if (rx_channel[i].hFreedv) {
			freedv_close(rx_channel[i].hFreedv);
			rx_channel[i].hFreedv = NULL;
		}
		if (rx_channel[i].demod_in) {
			free(rx_channel[i].demod_in);
			rx_channel[i].demod_in = NULL;
		}
	}
	quisk_freedv_rx(NULL, NULL, 0, 0);
	quisk_freedv_tx(NULL, NULL, 0);
	freedv_current_mode = -1;
}

static int OpenFreedv(void)	// Called from the GUI thread or sound thread
{
	int i, n_max_modem_samples;
	struct freedv * hF;

	if ( ! hLib)
		GetAddrs();		// Get the entry points for funtions in the codec2 library
	if (DEBUG) printf("freedv_open: version %d\n", freedv_version);
	if (freedv_version < 10) {
		CloseFreedv();
		requested_mode = -1;
		return 0;	// failure
	}
    	if (requested_mode == FREEDV_MODE_700D && freedv_open_advanced) {
        	struct freedv_advanced adv;
        	adv.interleave_frames = interleave_frames;
        	hF = freedv_open_advanced(requested_mode, &adv);
    	}
	else {
        	hF = freedv_open(requested_mode);
	}
	if (hF == NULL) {
		CloseFreedv();
		requested_mode = -1;
		return 0;	// failure
	}
	rx_channel[0].hFreedv = hF;
	quisk_dvoice_freedv(&quisk_freedv_rx, &quisk_freedv_tx);
	if (quisk_tx_msg[0])
		freedv_set_callback_txt(hF, &put_next_rx_char, &get_next_tx_char, NULL);
	else
		freedv_set_callback_txt(hF, &put_next_rx_char, NULL, NULL);
	if (freedv_set_callback_protocol)
 		freedv_set_callback_protocol(hF, NULL, NULL, NULL);
	if (freedv_set_callback_data)
		freedv_set_callback_data(hF, NULL, my_datatx, NULL);
	freedv_set_squelch_en(hF, quisk_freedv_squelch);
	if (freedv_set_tx_bpf)
		freedv_set_tx_bpf(hF, quisk_set_tx_bpf);
	n_max_modem_samples = freedv_get_n_max_modem_samples(hF);
	for (i = 0; i < MAX_RECEIVERS; i++) {
		rx_channel[i].rxdata_index = 0;
		rx_channel[i].speech_available = 0;
		rx_channel[i].playing = 0;
		if (rx_channel[i].demod_in)
			free(rx_channel[i].demod_in);
		rx_channel[i].demod_in = (COMP *)malloc(sizeof(COMP) * n_max_modem_samples);
		if (i > 0) {
			rx_channel[i].hFreedv = freedv_open(requested_mode);
			if (rx_channel[i].hFreedv)
				freedv_set_squelch_en(rx_channel[i].hFreedv, quisk_freedv_squelch);
		}
	}
	if (DEBUG) printf("n_nom_modem_samples %d\n", freedv_get_n_nom_modem_samples(rx_channel[0].hFreedv));
	if (DEBUG) printf("n_speech_samples %d\n", freedv_get_n_speech_samples(rx_channel[0].hFreedv));
	if (DEBUG) printf("n_max_modem_samples %d\n", n_max_modem_samples);
	freedv_current_mode = requested_mode;
	return 1;		// success
}

void quisk_check_freedv_mode(void)
{	// see if we need to change the mode
	if (requested_mode == freedv_current_mode)
		return;
	if (DEBUG) printf("Change in mode to %d\n", requested_mode);
	CloseFreedv();
	if (requested_mode >= 0)
		OpenFreedv();
	else
		requested_mode = -1;
}

PyObject * quisk_freedv_open(PyObject * self, PyObject * args)	// Called from the GUI thread before freedv is open
{
	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	return PyInt_FromLong(OpenFreedv());
}

PyObject * quisk_freedv_close(PyObject * self, PyObject * args)	// Called from the GUI thread.
{
	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	requested_mode = -1;		// request close
	Py_INCREF (Py_None);
	return Py_None;
}

PyObject * quisk_freedv_set_options(PyObject * self, PyObject * args, PyObject * keywds)	// Called from the GUI thread.
{  // Call with keyword arguments ONLY to change parameters.  Call before quisk_freedv_open() to set an initial mode.
	int mode=-1;				// Call again to change the mode.
	int bpf=-1;
	char * ptMsg=NULL;
	static char * kwlist[] = {"mode", "tx_msg", "DEBUG", "squelch", "interleave_frames", "set_tx_bpf", NULL} ;
	struct freedv * hFreedv;

	if (!PyArg_ParseTupleAndKeywords (args, keywds, "|isiiii", kwlist, &mode, &ptMsg, &DEBUG, &quisk_freedv_squelch, &interleave_frames, &bpf))
		return NULL;
	if (ptMsg)
		strncpy(quisk_tx_msg, ptMsg, TX_MSG_SIZE);
	if (bpf != -1) {
		quisk_set_tx_bpf = bpf;
		if (freedv_set_tx_bpf && rx_channel[0].hFreedv)
			freedv_set_tx_bpf(rx_channel[0].hFreedv, quisk_set_tx_bpf);
	}
	if (mode == -1)
		;
	else if (freedv_current_mode < 0)		// not started
		requested_mode = mode;
	else if (freedv_version == 10 && mode == 0)
		requested_mode = mode;
	else if (freedv_version == 11 && mode <= 2)
		requested_mode = mode;
	else {
		hFreedv = freedv_open(mode);	// test new mode
		if (hFreedv != NULL) {
			freedv_close(hFreedv);
			requested_mode = mode;
		}
	}
	return PyInt_FromLong(requested_mode);	// Return the mode
}

PyObject * quisk_freedv_get_snr(PyObject * self, PyObject * args)	// Called from the GUI thread.
{
	float snr_est = 0.0;

	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	if (rx_channel[0].hFreedv)
		freedv_get_modem_stats(rx_channel[0].hFreedv, NULL, &snr_est);
	return PyFloat_FromDouble(snr_est);
}

PyObject * quisk_freedv_get_version(PyObject * self, PyObject * args)	// Called from the GUI thread.
{
	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	if ( ! hLib)
		GetAddrs();		// Get the entry points for funtions in the codec2 library
	return PyInt_FromLong(freedv_version);
}
