#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <gnome.h>
#include <pthread.h>

#include "main.h"
#include "trx.h"
#include "snd.h"
#include "wf.h"
#include "ptt.h"
#include "misc.h"

//#include "mfsk/mfsk.h"
//#include "rtty/rtty.h"

extern void mfsk_init(struct trx *trx);
extern void rtty_init(struct trx *trx);
extern void throb_init(struct trx *trx);

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

static pthread_mutex_t trx_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t trx_cond = PTHREAD_COND_INITIALIZER;
static pthread_t trx_thread;
static struct trx trx;

#define BUFFERLEN	16384

typedef struct {
	guint rptr;
	guint wptr;
	guchar data[BUFFERLEN];
	pthread_mutex_t mutex;
} buf_t;

static buf_t rxbuffer = { 0, 0, { 0 }, PTHREAD_MUTEX_INITIALIZER };
static buf_t txbuffer = { 0, 0, { 0 }, PTHREAD_MUTEX_INITIALIZER };
static buf_t echobuffer = { 0, 0, { 0 }, PTHREAD_MUTEX_INITIALIZER };

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

#define BLOCKLEN	512

static void receive_loop(void)
{
	gfloat buf[BLOCKLEN];
	gint len;

	if (sound_open_for_read(trx.samplerate) < 0) {
		gdk_threads_enter();
		errmsg("sound_open_for_read: %s", sound_error());
		gdk_threads_leave();
		trx_set_state(TRX_STATE_ABORT);
		return;
	}
	trx.rxinit(&trx);
	wf_setstate(TRUE);

	for (;;) {
		pthread_mutex_lock(&trx_mutex);

		if (trx.state != TRX_STATE_RX) {
			pthread_mutex_unlock(&trx_mutex);
			break;
		}

		pthread_mutex_unlock(&trx_mutex);

		len = sound_read(buf, BLOCKLEN);

		if (len < 0) {
			gdk_threads_enter();
			errmsg("%s", sound_error());
			gdk_threads_leave();
			trx_set_state(TRX_STATE_ABORT);
			break;
		}

		wf_setdata(buf, len);
		trx.rxprocess(&trx, buf, len);
	}

	wf_setstate(FALSE);
	sound_close();
}

static void transmit_loop(void)
{
	if (sound_open_for_write(trx.samplerate) < 0) {
		gdk_threads_enter();
		errmsg("sound_open_for_write: %s", sound_error());
		gdk_threads_leave();
		trx_set_state(TRX_STATE_ABORT);
		return;
	}
	trx.txinit(&trx);
	set_ptt(1);

	for (;;) {
		pthread_mutex_lock(&trx_mutex);

		if (trx.state == TRX_STATE_ABORT) {
			pthread_mutex_unlock(&trx_mutex);
			break;
		}

		if (trx.state == TRX_STATE_RX || trx.state == TRX_STATE_PAUSE)
			trx.stopflag = 1;

		if (trx.state == TRX_STATE_TUNE)
			trx.tune = 1;

		pthread_mutex_unlock(&trx_mutex);

		if (trx.txprocess(&trx) < 0)
			break;
	}

	sound_close();
	set_ptt(0);

	pthread_mutex_lock(&trx_mutex);
	trx.stopflag = 0;
	trx.tune = 0;
	pthread_mutex_unlock(&trx_mutex);
}

static void *trx_loop(void *args)
{
	for (;;) {
		pthread_mutex_lock(&trx_mutex);

		if (trx.state == TRX_STATE_PAUSE || \
		    trx.state == TRX_STATE_ABORT)
			break;

		/* signal a state change (other than pause or abort) */
		pthread_cond_signal(&trx_cond);

		switch (trx.state) {
		case TRX_STATE_RX:
			pthread_mutex_unlock(&trx_mutex);
			receive_loop();
			break;

		case TRX_STATE_TX:
		case TRX_STATE_TUNE:
			pthread_mutex_unlock(&trx_mutex);
			transmit_loop();
			break;

		default:
			pthread_mutex_unlock(&trx_mutex);
			break;
		}
	}

	trx.destructor(&trx);

	trx.modem = NULL;
	trx.state = TRX_STATE_PAUSE;

	/* signal a state change */
	pthread_cond_signal(&trx_cond);

	pthread_mutex_unlock(&trx_mutex);

	/* this will exit the trx thread */
	return NULL;
}

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

gint trx_init(void)
{
	pthread_mutex_lock(&trx_mutex);

	if (trx.modem || trx.state == TRX_STATE_PAUSE || trx.state == TRX_STATE_ABORT) {
		pthread_mutex_unlock(&trx_mutex);
		return -1;
	}

	switch (trx.mode) {
	case MODE_MFSK16:
		mfsk_init(&trx);
		break;

	case MODE_MFSK8:
		break;

	case MODE_RTTY:
		rtty_init(&trx);
		break;

	case MODE_THROB1:
	case MODE_THROB2:
	case MODE_THROB4:
		throb_init(&trx);
		break;
	}

	if (trx.modem == NULL) {
		errmsg("Modem initialization failed!");
		pthread_mutex_unlock(&trx_mutex);
		return -1;
	}

	trx.state = TRX_STATE_RX;
	trx.stopflag = 0;

	pthread_mutex_unlock(&trx_mutex);

	if (pthread_create(&trx_thread, NULL, trx_loop, NULL) < 0) {
		errmsg("pthread_create: %m");
		trx.state = TRX_STATE_PAUSE;
		return -1;
	}

	return 0;
}

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

static char *modenames[] = {
	"MFSK16",
	"MFSK8",
	"RTTY",
	"THROB1",
	"THROB2",
	"THROB4"
};

void trx_set_mode(trx_mode_t mode)
{
	pthread_mutex_lock(&trx_mutex);
	trx.mode = mode;
	pthread_mutex_unlock(&trx_mutex);
}

trx_mode_t trx_get_mode(void)
{
	return trx.mode;
}

char *trx_get_mode_name(void)
{
	return modenames[trx.mode];
}

void trx_set_state(trx_state_t state)
{
	pthread_mutex_lock(&trx_mutex);

	if (trx.state == state) {
		pthread_mutex_unlock(&trx_mutex);
		return;
	}
	trx.state = state;

	pthread_mutex_unlock(&trx_mutex);

	/* re-initialize the trx - a no-op if already running or paused */
	trx_init();
}

void trx_set_state_wait(trx_state_t state)
{
	pthread_mutex_lock(&trx_mutex);

	if (trx.state == state) {
		pthread_mutex_unlock(&trx_mutex);
		return;
	}

	trx.state = state;

	if (trx.modem) {
		/* now wait for the main trx loop to respond */
		pthread_cond_wait(&trx_cond, &trx_mutex);
	}

	pthread_mutex_unlock(&trx_mutex);

	/* re-initialize the trx - a no-op if already running or paused */
	trx_init();
}

trx_state_t trx_get_state(void)
{
	return trx.state;
}

void trx_set_freq(gfloat freq)
{
	freq = clamp(freq, 500.0, 2500.0 - trx.bandwidth);

	pthread_mutex_lock(&trx_mutex);
	trx.frequency = freq;
	pthread_mutex_unlock(&trx_mutex);
}

gfloat trx_get_freq(void)
{
	return trx.frequency;
}

gfloat trx_get_bandwidth(void)
{
	return trx.bandwidth;
}

void trx_set_afc(gboolean on)
{
	pthread_mutex_lock(&trx_mutex);
	trx.afcon = on;
	pthread_mutex_unlock(&trx_mutex);
}

void trx_set_squelch(gboolean on)
{
	pthread_mutex_lock(&trx_mutex);
	trx.squelchon = on;
	pthread_mutex_unlock(&trx_mutex);
}

void trx_set_reverse(gboolean on)
{
	pthread_mutex_lock(&trx_mutex);
	trx.reverse = on;
	pthread_mutex_unlock(&trx_mutex);
}

void trx_set_metric(gfloat metric)
{
	pthread_mutex_lock(&trx_mutex);
	trx.metric = metric;
	pthread_mutex_unlock(&trx_mutex);
}

gfloat trx_get_metric(void)
{
	return trx.metric;
}

void trx_set_sync(gfloat sync)
{
	sync = clamp(sync, 0.0, 1.0);

	pthread_mutex_lock(&trx_mutex);
	trx.syncpos = sync;
	pthread_mutex_unlock(&trx_mutex);
}

gfloat trx_get_sync(void)
{
	gfloat sync;

	pthread_mutex_lock(&trx_mutex);
	switch (trx.mode) {
	case MODE_THROB1:
	case MODE_THROB2:
	case MODE_THROB4:
		sync = trx.syncpos;
		break;
	default:
		sync = -1.0;
		break;
	}
	pthread_mutex_unlock(&trx_mutex);

	return sync;
}

void trx_put_syncscope(guchar *data, gint len)
{
	unsigned int i, j;

	pthread_mutex_lock(&trx_mutex);

	for (i = 0; i < len; i++) {
		j = i * SYNCSCOPE_WIDTH / len;
		trx.syncscope[j] = data[i];
	}

	pthread_mutex_unlock(&trx_mutex);
}

void trx_get_syncscope(guchar *data)
{
	pthread_mutex_lock(&trx_mutex);
	memcpy(data, trx.syncscope, SYNCSCOPE_WIDTH);
	pthread_mutex_unlock(&trx_mutex);
}

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

static inline int buffer_len(buf_t *b)
{
	gint len = b->wptr - b->rptr;

	return (len < 0) ? (len + BUFFERLEN) : len;
}

static inline int write_data(buf_t *b, gint data)
{
	if (buffer_len(b) == BUFFERLEN - 1)
		return -1;

	b->data[b->wptr] = data;
	b->wptr = (b->wptr + 1) % BUFFERLEN;
	return 0;
}

static inline int delete_data(buf_t *b)
{
	if (buffer_len(b) == 0)
		return -1;
	b->wptr = (b->wptr - 1) % BUFFERLEN;
	return 0;
}

static inline int read_data(buf_t *b)
{
	gint data;

	if (buffer_len(b) == 0)
		return -1;
	data = b->data[b->rptr];
	b->rptr = (b->rptr + 1) % BUFFERLEN;
	return data;
}

static inline int peek_data(buf_t *b)
{
	if (buffer_len(b) == 0)
		return -1;
	return b->data[b->rptr];
}

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

void trx_put_tx_char(gint c)
{
	pthread_mutex_lock(&txbuffer.mutex);

	/* backspace */
	if (c == 8) {
		if (buffer_len(&txbuffer) > 0 && peek_data(&txbuffer) != 8)
			delete_data(&txbuffer);
		else if (trx.state == TRX_STATE_TX)
			write_data(&txbuffer, 8);

		pthread_mutex_unlock(&txbuffer.mutex);
		return;
	}

	write_data(&txbuffer, (c == '\n') ? '\r' : c);

	pthread_mutex_unlock(&txbuffer.mutex);
}

gint trx_get_tx_char(void)
{
	gint c;

	pthread_mutex_lock(&txbuffer.mutex);
	c = read_data(&txbuffer);
	pthread_mutex_unlock(&txbuffer.mutex);
	return c;
}

void trx_put_echo_char(gint c)
{
	pthread_mutex_lock(&echobuffer.mutex);
	write_data(&echobuffer, c);
	pthread_mutex_unlock(&echobuffer.mutex);
}

gint trx_get_echo_char(void)
{
	gint c;

	pthread_mutex_lock(&echobuffer.mutex);
	c = read_data(&echobuffer);
	pthread_mutex_unlock(&echobuffer.mutex);
	return c;
}

void trx_put_rx_char(gint c)
{
	pthread_mutex_lock(&rxbuffer.mutex);
	write_data(&rxbuffer, c);
	pthread_mutex_unlock(&rxbuffer.mutex);
}

gint trx_get_rx_char(void)
{
	gint c;

	pthread_mutex_lock(&rxbuffer.mutex);
	c = read_data(&rxbuffer);
	pthread_mutex_unlock(&rxbuffer.mutex);
	return c;
}

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

