// ----------------------------------------------------------------------------
//
//	rsid.cxx
//
// Copyright (C) 2008-2012
//		Dave Freese, W1HKJ
// Copyright (C) 2009-2012
//		Stelios Bounanos, M0GLD
// Copyright (C) 2012
//		John Douyere, VK2ETA
//
// This file is part of fldigi.
//
// Fldigi 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 3 of the License, or
// (at your option) any later version.
//
// Fldigi 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.
//
// You should have received a copy of the GNU General Public License
// along with fldigi.  If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------

#include <config.h>

#include <string>
#include <cmath>
#include <cstring>
#include <float.h>
#include <samplerate.h>

#include "rsid.h"
#include "filters.h"
#include "misc.h"
#include "trx.h"
#include "fl_digi.h"
#include "configuration.h"
#include "confdialog.h"
#include "qrunner.h"
#include "notify.h"
#include "debug.h"

#include "main.h"
#include "arq_io.h"
#include "data_io.h"

LOG_FILE_SOURCE(debug::LOG_MODEM);

#include "rsid_defs.cxx"

#define RSWINDOW 1

const int cRsId::Squares[] = {
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
	0, 2, 4, 6, 8,10,12,14, 9,11,13,15, 1, 3, 5, 7,
	0, 3, 6, 5,12,15,10, 9, 1, 2, 7, 4,13,14,11, 8,
	0, 4, 8,12, 9,13, 1, 5,11,15, 3, 7, 2, 6,10,14,
	0, 5,10,15,13, 8, 7, 2, 3, 6, 9,12,14,11, 4, 1,
	0, 6,12,10, 1, 7,13,11, 2, 4,14, 8, 3, 5,15, 9,
	0, 7,14, 9, 5, 2,11,12,10,13, 4, 3,15, 8, 1, 6,
	0, 8, 9, 1,11, 3, 2,10,15, 7, 6,14, 4,12,13, 5,
	0, 9,11, 2,15, 6, 4,13, 7,14,12, 5, 8, 1, 3,10,
	0,10,13, 7, 3, 9,14, 4, 6,12,11, 1, 5,15, 8, 2,
	0,11,15, 4, 7,12, 8, 3,14, 5, 1,10, 9, 2, 6,13,
	0,12, 1,13, 2,14, 3,15, 4, 8, 5, 9, 6,10, 7,11,
	0,13, 3,14, 6,11, 5, 8,12, 1,15, 2,10, 7, 9, 4,
	0,14, 5,11,10, 4,15, 1,13, 3, 8, 6, 7, 9, 2,12,
	0,15, 7, 8,14, 1, 9, 6, 5,10, 2,13,11, 4,12, 3
};

const int cRsId::indices[] = {
	2, 4, 8, 9, 11, 15, 7, 14, 5, 10, 13, 3
};

cRsId::cRsId()
{
	int error;
	src_state = src_new(progdefaults.sample_converter, 1, &error);
	if (error) {
		LOG_ERROR("src_new error %d: %s", error, src_strerror(error));
		abort();
	}
	src_data.end_of_input = 0;

	reset();

	rsfft = new g_fft<rs_fft_type>(RSID_ARRAY_SIZE);

	memset(fftwindow, 0, sizeof(fftwindow));

	if (RSWINDOW) {
		for (int i = 0; i < RSID_ARRAY_SIZE; i++)
//		fftwindow[i] = blackman ( 1.0 * i / RSID_ARRAY_SIZE );
		fftwindow[i] = hamming ( 1.0 * i / RSID_ARRAY_SIZE );
//		fftwindow[i] = hanning ( 1.0 * i / RSID_ARRAY_SIZE );
//		fftwindow[i] = 1.0;
	}

	pCodes1 = new unsigned char[rsid_ids_size1 * RSID_NSYMBOLS];
	memset(pCodes1, 0, sizeof(pCodes1) * sizeof(unsigned char));

	pCodes2 = new unsigned char[rsid_ids_size2 * RSID_NSYMBOLS];
	memset(pCodes2, 0, sizeof(pCodes2) * sizeof(unsigned char));

	// Initialization  of assigned mode/submode IDs.
	unsigned char* c;
	for (int i = 0; i < rsid_ids_size1; i++) {
		c = pCodes1 + i * RSID_NSYMBOLS;
		Encode(rsid_ids_1[i].rs, c);
	}

	for (int i = 0; i < rsid_ids_size2; i++) {
		c = pCodes2 + i * RSID_NSYMBOLS;
		Encode(rsid_ids_2[i].rs, c);
	}

#if 0
	printf("pcode 1\n");
	printf(",rs, name, mode,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14\n");
	for (int i = 0; i < rsid_ids_size1; i++) {
		printf("%d,%d,%s,%d", i, rsid_ids_1[i].rs, rsid_ids_1[i].name, rsid_ids_1[i].mode);
		for (int j = 0; j < RSID_NSYMBOLS + 1; j++)
			printf(",%d", pCodes1[i*(RSID_NSYMBOLS + 1) + j]);
		printf("\n");
	}
	printf("\npcode 2\n");
	printf(", rs, name, mode,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14\n");
	for (int i = 0; i < rsid_ids_size2; i++) {
		printf("%d,%d,%s,%d", i, rsid_ids_2[i].rs, rsid_ids_2[i].name, rsid_ids_2[i].mode);
		for (int j = 0; j < RSID_NSYMBOLS + 1; j++)
			printf(",%d", pCodes2[i*(RSID_NSYMBOLS+ 1) + j]);
		printf("\n");
	}
#endif

	nBinLow = 3;
	nBinHigh = RSID_FFT_SIZE - 32; // - RSID_NTIMES - 2

	outbuf = 0;
	symlen = 0;

	reset();

}

cRsId::~cRsId()
{
	delete [] pCodes1;
	delete [] pCodes2;

	delete [] outbuf;
	delete rsfft;
	src_delete(src_state);
}

void cRsId::reset()
{
	iPrevDistance = iPrevDistance2 = 99;
	bPrevTimeSliceValid = bPrevTimeSliceValid2 = false;
	found1 = found2 = false;
	rsid_secondary_time_out = 0;

	memset(aInputSamples, 0, (RSID_ARRAY_SIZE * 2) * sizeof(float));
	memset(aFFTcmplx, 0, RSID_ARRAY_SIZE * sizeof(rs_cpx_type));
	memset(aFFTAmpl, 0, RSID_FFT_SIZE * sizeof(rs_fft_type));
	memset(fft_buckets, 0, RSID_NTIMES * RSID_FFT_SIZE * sizeof(int));

	int error = src_reset(src_state);
	if (error)
		LOG_ERROR("src_reset error %d: %s", error, src_strerror(error));
	src_data.src_ratio = 0.0;
	inptr = RSID_FFT_SIZE;
	hamming_resolution = progdefaults.RsID_label_type;
}

void cRsId::Encode(int code, unsigned char *rsid)
{
	rsid[0] = code >> 8;
	rsid[1] = (code >> 4) & 0x0f;
	rsid[2] = code & 0x0f;
	for (int i = 3; i < RSID_NSYMBOLS; i++)
		rsid[i] = 0;
	for (int i = 0; i < 12; i++) {
		for (int j = RSID_NSYMBOLS - 1; j > 0; j--)
			rsid[j] = rsid[j - 1] ^ Squares[(rsid[j] << 4) + indices[i]];
		rsid[0] = Squares[(rsid[0] << 4) + indices[i]];
	}
}

void cRsId::CalculateBuckets(const rs_fft_type *pSpectrum, int iBegin, int iEnd)
{
	rs_fft_type Amp = 0.0, AmpMax = 0.0;
	int iBucketMax = iBegin - 2;
	int j;

	for (int i = iBegin; i < iEnd; i += 2) {
		if (iBucketMax == i - 2) {
			AmpMax = pSpectrum[i];
			iBucketMax = i;
			for (j = i + 2; j < i + RSID_NTIMES + 2; j += 2) {
				Amp = pSpectrum[j];
				if (Amp > AmpMax) {
					AmpMax = Amp;
					iBucketMax = j;
				}
			}
		}
		else {
			j = i + RSID_NTIMES;
			Amp = pSpectrum[j];
			if (Amp > AmpMax) {
				AmpMax    = Amp;
				iBucketMax = j;
			}
		}
		fft_buckets[RSID_NTIMES - 1][i] = (iBucketMax - i) >> 1;
	}
}

void cRsId::receive(const float* buf, size_t len)
{

	if (len == 0) return;

	int srclen = static_cast<int>(len);
	double src_ratio = RSID_SAMPLE_RATE / active_modem->get_samplerate();

	if (rsid_secondary_time_out > 0) {
		rsid_secondary_time_out -= 1.0 * len / active_modem->get_samplerate();
		if (rsid_secondary_time_out <= 0) {
			LOG_INFO("%s", "Secondary RsID timed out");
			reset();
		}
	}

	if (src_data.src_ratio != src_ratio) {
		src_data.src_ratio = src_ratio;
		src_set_ratio(src_state, src_data.src_ratio);
	}

	while (srclen > 0) {
		src_data.data_in = const_cast<float*>(buf);
		src_data.input_frames = srclen;
		src_data.data_out = &aInputSamples[inptr];
		src_data.output_frames = RSID_ARRAY_SIZE * 2 - inptr;
		src_data.input_frames_used = 0;
		int error = src_process(src_state, &src_data);
		if (unlikely(error)) {
			LOG_ERROR("src_process error %d: %s", error, src_strerror(error));
			return;
		}
		size_t gend = src_data.output_frames_gen;
		size_t used = src_data.input_frames_used;
		inptr += gend;
		buf += used;
		srclen -= used;

		while (inptr >= RSID_ARRAY_SIZE) {
			search();
			memmove(&aInputSamples[0], &aInputSamples[RSID_FFT_SAMPLES],
					(RSID_BUFFER_SIZE - RSID_FFT_SAMPLES)*sizeof(float));
			inptr -= RSID_FFT_SAMPLES;
		}
	}
}

void cRsId::search(void)
{
	if (progdefaults.rsidWideSearch) {
		nBinLow = 3;
		nBinHigh = RSID_FFT_SIZE - 32;
	}
	else {
		float centerfreq = active_modem->get_freq();
		float bpf = 1.0 * RSID_ARRAY_SIZE / RSID_SAMPLE_RATE;
		nBinLow = (int)((centerfreq  - 100.0 * 2) * bpf);
		nBinHigh = (int)((centerfreq  + 100.0 * 2) * bpf);
	}
	if (nBinLow < 3) nBinLow = 3;
	if (nBinHigh > RSID_FFT_SIZE - 32) nBinHigh = RSID_FFT_SIZE - 32;

	bool bReverse = !(wf->Reverse() ^ wf->USB());
	if (bReverse) {
		nBinLow  = RSID_FFT_SIZE - nBinHigh;
		nBinHigh = RSID_FFT_SIZE - nBinLow;
	}

	if (RSWINDOW) {
		for (int i = 0; i < RSID_ARRAY_SIZE; i++)
			aFFTcmplx[i] = cmplx(aInputSamples[i] * fftwindow[i], 0);
	} else {
		for (int i = 0; i < RSID_ARRAY_SIZE; i++)
			aFFTcmplx[i] = cmplx(aInputSamples[i], 0);
	}

	rsfft->ComplexFFT(aFFTcmplx);

	memset(aFFTAmpl, 0, sizeof(aFFTAmpl));

	static const double pscale = 4.0 / (RSID_FFT_SIZE * RSID_FFT_SIZE);

	if (unlikely(bReverse)) {
		for (int i = 0; i < RSID_FFT_SIZE; i++)
			aFFTAmpl[RSID_FFT_SIZE - 1 - i] = norm(aFFTcmplx[i]) * pscale;
	} else {
		for (int i = 0; i < RSID_FFT_SIZE; i++)
			aFFTAmpl[i] = norm(aFFTcmplx[i]) * pscale;
	}

	int bucket_low = 3;
	int bucket_high = RSID_FFT_SIZE - 32;
	if (bReverse) {
		bucket_low  = RSID_FFT_SIZE - bucket_high;
		bucket_high = RSID_FFT_SIZE - bucket_low;
	}

	memmove(fft_buckets,
			&(fft_buckets[1][0]),
			(RSID_NTIMES - 1) * RSID_FFT_SIZE * sizeof(int));
	memset(&(fft_buckets[RSID_NTIMES - 1][0]), 0, RSID_FFT_SIZE * sizeof(int));

	CalculateBuckets ( aFFTAmpl, bucket_low,  bucket_high - RSID_NTIMES);
	CalculateBuckets ( aFFTAmpl, bucket_low + 1, bucket_high - RSID_NTIMES);

	int symbol_out_1 = -1;
	int bin_out_1    = -1;
	int symbol_out_2 = -1;
	int bin_out_2    = -1;

	if (rsid_secondary_time_out <= 0) {
		found1 = search_amp(bin_out_1, symbol_out_1, pCodes1);
		if (found1) {
			if (symbol_out_1 != RSID_ESCAPE) {
				if (bReverse)
					bin_out_1 = 1024 - bin_out_1 - 31;
				apply(bin_out_1, symbol_out_1, 0);
				reset();
				return;
			} else {
				// 10 rsid_gap + 15 symbols + 2 for timing errors
				rsid_secondary_time_out = 27 * RSID_SYMLEN;
				return;
			}
		} else
			return;
	}

	found2 = search_amp(bin_out_2, symbol_out_2, pCodes2);
	if (found2) {
		if (symbol_out_2 != RSID_NONE2) {
			if (bReverse)
				bin_out_2 = 1024 - bin_out_2 - 31;
			apply(bin_out_2, symbol_out_2, 1);
		}
		reset();
	}

}

void cRsId::setup_mode(int iSymbol)
{
	switch (iSymbol) {
	case RSID_RTTY_ASCII_7:
		progdefaults.rtty_baud = 5;
		progdefaults.rtty_bits = 1;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_ASCII_8:
		progdefaults.rtty_baud = 5;
		progdefaults.rtty_bits = 2;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_45:
		progdefaults.rtty_baud = 1;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 3;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_50:
		progdefaults.rtty_baud = 2;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 3;
		REQ(&set_rtty_tab_widgets);
		break;
	case RSID_RTTY_75:
		progdefaults.rtty_baud = 4;
		progdefaults.rtty_bits = 0;
		progdefaults.rtty_shift = 9;
		REQ(&set_rtty_tab_widgets);
		break;
// DominoEX / FEC
	case RSID_DOMINOEX_4: case RSID_DOMINOEX_5: case RSID_DOMINOEX_8:
	case RSID_DOMINOEX_11: case RSID_DOMINOEX_16: case RSID_DOMINOEX_22:
		progdefaults.DOMINOEX_FEC = false;
		REQ(&set_dominoex_tab_widgets);
		break;
	case RSID_DOMINOEX_4_FEC: case RSID_DOMINOEX_5_FEC: case RSID_DOMINOEX_8_FEC:
	case RSID_DOMINOEX_11_FEC: case RSID_DOMINOEX_16_FEC: case RSID_DOMINOEX_22_FEC:
		progdefaults.DOMINOEX_FEC = true;
		REQ(&set_dominoex_tab_widgets);
		break;
// olivia parameters
	case RSID_OLIVIA_4_125:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 0;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_250:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 1;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_500:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_1000:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_4_2000:
		progdefaults.oliviatones = 1;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_125:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 0;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_250:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 1;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_500:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_1000:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_8_2000:
		progdefaults.oliviatones = 2;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_500:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 2;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_1000:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_16_2000:
		progdefaults.oliviatones = 3;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_32_1000:
		progdefaults.oliviatones = 4;
		progdefaults.oliviabw = 3;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_32_2000:
		progdefaults.oliviatones = 4;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
	case RSID_OLIVIA_64_2000:
		progdefaults.oliviatones = 5;
		progdefaults.oliviabw = 4;
		REQ(&set_olivia_tab_widgets);
		break;
// contestia parameters
	case RSID_CONTESTIA_4_125:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 0;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_250:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 1;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_500:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_1000:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_4_2000:
		progdefaults.contestiatones = 1;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_125:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 0;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_250:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 1;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_500:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_1000:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_8_2000:
		progdefaults.contestiatones = 2;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_500:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_1000:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_16_2000:
		progdefaults.contestiatones = 3;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_32_1000:
		progdefaults.contestiatones = 4;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_32_2000:
		progdefaults.contestiatones = 4;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_500:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 2;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_1000:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 3;
		REQ(&set_contestia_tab_widgets);
		break;
	case RSID_CONTESTIA_64_2000:
		progdefaults.contestiatones = 5;
		progdefaults.contestiabw = 4;
		REQ(&set_contestia_tab_widgets);
		break;
	default:
		break;
	} // switch (iSymbol)
}

void cRsId::apply(int iBin, int iSymbol, int extended)
{
	ENSURE_THREAD(TRX_TID);

	double rsidfreq = 0, currfreq = 0;
	int n, mbin = NUM_MODES;

	int tblsize;
	const RSIDs *p_rsid;

	if (extended) {
		tblsize = rsid_ids_size2;
		p_rsid = rsid_ids_2;
	}
	else {
		tblsize = rsid_ids_size1;
		p_rsid = rsid_ids_1;
	}

	currfreq = active_modem->get_freq();
	rsidfreq = (iBin + RSID_NSYMBOLS - 0.5) * RSID_SAMPLE_RATE / 2048.0;

	for (n = 0; n < tblsize; n++) {
		if (p_rsid[n].rs == iSymbol) {
			mbin = p_rsid[n].mode;
			break;
		}
	}

	if (mbin == NUM_MODES) {
		char msg[50];
		if (n < tblsize) // RSID known but unimplemented
			snprintf(msg, sizeof(msg), "RSID: %s unimplemented",
				p_rsid[n].name);
		else // RSID unknown; shouldn't  happen
			snprintf(msg, sizeof(msg), "RSID: code %d unknown", iSymbol);
		put_status(msg, 4.0);
		LOG_VERBOSE("%s", msg);
		return;
	}

	if (progdefaults.rsid_rx_modes.test(mbin)) {
		LOG_VERBOSE("RSID: %s @ %0.1f Hz",
			p_rsid[n].name, rsidfreq);
	}
	else {
		LOG_DEBUG("Ignoring RSID: %s @ %0.1f Hz",
			p_rsid[n].name, rsidfreq);
		return;
	}

	if (mailclient || mailserver)
		REQ(pskmail_notify_rsid, mbin);

	if (progdefaults.rsid_auto_disable)
		REQ(toggleRSID);

	if (!progdefaults.disable_rsid_warning_dialog_box)
		REQ(notify_rsid, mbin, rsidfreq);

	if (progdefaults.rsid_notify_only) {
		if (data_io_enabled == KISS_IO) {
			bcast_rsid_kiss_frame(rsidfreq, mbin, (int) active_modem->get_txfreq(),
								  active_modem->get_mode(), RSID_KISS_NOTIFY);
		}
		return;
	}

	if (progdefaults.rsid_mark) // mark current modem & freq
		REQ(note_qrg, false, "\nBefore RSID: ", "\n",
			active_modem->get_mode(), 0LL, currfreq);

	if(active_modem) // Currently only effects Olivia, Contestia and MT63.
		active_modem->rx_flush();

	setup_mode(iSymbol);

	if (progdefaults.rsid_squelch)
		REQ(init_modem_squelch, mbin, progdefaults.disable_rsid_freq_change ? currfreq : rsidfreq);
	else
		REQ(init_modem, mbin, progdefaults.disable_rsid_freq_change ? currfreq : rsidfreq);

}

inline int cRsId::HammingDistance(int iBucket, unsigned char *p2)
{
	int dist = 0;
	for (int i = 0, j = 1; i < RSID_NSYMBOLS; i++, j += 2) {
		if (fft_buckets[j][iBucket] != p2[i]) {
			++dist;
			if (dist > hamming_resolution)
				break;
		}
	}
	return dist;
}

bool cRsId::search_amp( int &bin_out, int &symbol_out, unsigned char *pcode)
{
	int i, j;
	int iDistanceMin = 1000;  // infinity
	int iDistance    = 1000;
	int iBin         = -1;
	int iSymbol      = -1;

	int tblsize;
	const RSIDs *prsid;

	if (pcode == pCodes1) {
		tblsize = rsid_ids_size1;
		prsid = rsid_ids_1;
	} else {
		tblsize = rsid_ids_size2;
		prsid = rsid_ids_2;
	}

	unsigned char *pc = 0;
	for (i = 0; i < tblsize; i++) {
		pc = pcode + i * RSID_NSYMBOLS;
		for (j = nBinLow; j < nBinHigh - RSID_NTIMES; j++) {
			iDistance = HammingDistance(j, pc);
			if (iDistance < iDistanceMin) {
				iDistanceMin = iDistance;
				iSymbol  	 = prsid[i].rs;
				iBin		 = j;
				if (iDistanceMin == 0) break;
			}
		}
	}

	if (iDistanceMin <= hamming_resolution) {
		symbol_out	= iSymbol;
		bin_out		= iBin;
		return true;
	}

	return false;
}

//=============================================================================
// transmit rsid code for current mode
//=============================================================================

bool cRsId::assigned(trx_mode mode) {

	rmode = RSID_NONE;
	rmode2 = RSID_NONE2;

	switch (mode) {
	case MODE_RTTY :
		if (progdefaults.rtty_baud == 5 && progdefaults.rtty_bits == 1 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_ASCII_7;
		else if (progdefaults.rtty_baud == 5 && progdefaults.rtty_bits == 1 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_ASCII_8;
		else if (progdefaults.rtty_baud == 1 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 3)
			rmode = RSID_RTTY_45;
		else if (progdefaults.rtty_baud == 2 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 3)
			rmode = RSID_RTTY_50;
		else if (progdefaults.rtty_baud == 4 && progdefaults.rtty_bits == 0 && progdefaults.rtty_shift == 9)
			rmode = RSID_RTTY_75;
		else
			return false;
		return true;
		break;

	case MODE_OLIVIA:
	case MODE_OLIVIA_4_250:
	case MODE_OLIVIA_8_250:
	case MODE_OLIVIA_4_500:
	case MODE_OLIVIA_8_500:
	case MODE_OLIVIA_16_500:
	case MODE_OLIVIA_8_1000:
	case MODE_OLIVIA_16_1000:
	case MODE_OLIVIA_32_1000:
	case MODE_OLIVIA_64_2000:
		if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 0)
			rmode = RSID_OLIVIA_4_125;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 1)
			rmode = RSID_OLIVIA_4_250;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_4_500;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_4_1000;
		else if (progdefaults.oliviatones == 1 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_4_2000;

		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 0)
			rmode = RSID_OLIVIA_8_125;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 1)
			rmode = RSID_OLIVIA_8_250;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_8_500;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_8_1000;
		else if (progdefaults.oliviatones == 2 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_8_2000;

		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 2)
			rmode = RSID_OLIVIA_16_500;
		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_16_1000;
		else if (progdefaults.oliviatones == 3 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_16_2000;

		else if (progdefaults.oliviatones == 4 && progdefaults.oliviabw == 3)
			rmode = RSID_OLIVIA_32_1000;
		else if (progdefaults.oliviatones == 4 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_32_2000;

		else if (progdefaults.oliviatones == 5 && progdefaults.oliviabw == 4)
			rmode = RSID_OLIVIA_64_2000;

		else
			return false;
		return true;
		break;

	case MODE_CONTESTIA:

		if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 0)
			rmode = RSID_CONTESTIA_4_125;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 1)
			rmode = RSID_CONTESTIA_4_250;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_4_500;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_4_1000;
		else if (progdefaults.contestiatones == 1 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_4_2000;

		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 0)
			rmode = RSID_CONTESTIA_8_125;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 1)
			rmode = RSID_CONTESTIA_8_250;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_8_500;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_8_1000;
		else if (progdefaults.contestiatones == 2 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_8_2000;

		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_16_500;
		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_16_1000;
		else if (progdefaults.contestiatones == 3 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_16_2000;

		else if (progdefaults.contestiatones == 4 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_32_1000;
		else if (progdefaults.contestiatones == 4 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_32_2000;

		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 2)
			rmode = RSID_CONTESTIA_64_500;
		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 3)
			rmode = RSID_CONTESTIA_64_1000;
		else if (progdefaults.contestiatones == 5 && progdefaults.contestiabw == 4)
			rmode = RSID_CONTESTIA_64_2000;

		else
			return false;
		return true;
		break;

	case MODE_DOMINOEX4:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_4_FEC;
		break;
	case MODE_DOMINOEX5:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_5_FEC;
		break;
	case MODE_DOMINOEX8:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_8_FEC;
		break;
	case MODE_DOMINOEX11:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_11_FEC;
		break;
	case MODE_DOMINOEX16:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_16_FEC;
		break;
	case MODE_DOMINOEX22:
		if (progdefaults.DOMINOEX_FEC)
			rmode = RSID_DOMINOEX_22_FEC;
		break;

	case MODE_MT63_500S:
		rmode = RSID_MT63_500_ST;
		break;
	case MODE_MT63_500L:
		rmode = RSID_MT63_500_LG;
		break;
	case MODE_MT63_1000S:
		rmode = RSID_MT63_1000_ST;
		break;
	case MODE_MT63_1000L:
		rmode = RSID_MT63_1000_LG;
		break;
	case MODE_MT63_2000S:
		rmode = RSID_MT63_2000_ST;
		break;
	case MODE_MT63_2000L:
		rmode = RSID_MT63_2000_LG;
		break;
	}

// if rmode is still unset, look it up
// Try secondary table first
	if (rmode == RSID_NONE) {
		for (size_t i = 0; i < sizeof(rsid_ids_2)/sizeof(*rsid_ids_2); i++) {
			if (mode == rsid_ids_2[i].mode) {
				rmode = RSID_ESCAPE;
				rmode2 = rsid_ids_2[i].rs;
				break;
			}
		}
		if (rmode2 == RSID_NONE2) {
			for (size_t i = 0; i < sizeof(rsid_ids_1)/sizeof(*rsid_ids_1); i++) {
				if (mode == rsid_ids_1[i].mode) {
					rmode = rsid_ids_1[i].rs;
					break;
				}
			}
		}
	}
	if (rmode == RSID_NONE) {
		LOG_DEBUG("%s mode is not assigned an RSID", mode_info[mode].sname);
		return false;
	}
	return true;
}

void cRsId::send(bool preRSID)
{
	trx_mode mode = active_modem->get_mode();

	if (!progdefaults.rsid_tx_modes.test(mode)) {
		LOG_DEBUG("Mode %s excluded, not sending RSID", mode_info[mode].sname);
		return;
	}

	if (!progdefaults.rsid_post && !preRSID)
		return;

	if (!assigned(mode)) return;

	unsigned char rsid[RSID_NSYMBOLS];
	double sr;
	size_t len;
	int iTone;
	double freq, phaseincr;
	double fr;
	double phase;

	Encode(rmode, rsid);
	sr = active_modem->get_samplerate();
	len = (size_t)floor(RSID_SYMLEN * sr);
	if (unlikely(len != symlen)) {
		symlen = len;
		delete [] outbuf;
		outbuf = new double[symlen];
	}

// transmit 5 symbol periods of silence at beginning of rsid
	memset(outbuf, 0, symlen * sizeof(*outbuf));
	for (int i = 0; i < 5; i++)
		active_modem->ModulateXmtr(outbuf, symlen);

// transmit sequence of 15 symbols (tones)
	fr = 1.0 * active_modem->get_txfreq() - (RSID_SAMPLE_RATE * 7 / 1024);
	phase = 0.0;

	for (int i = 0; i < 15; i++) {
		iTone = rsid[i];
		if (active_modem->get_reverse())
		iTone = 15 - iTone;
		freq = fr + iTone * RSID_SAMPLE_RATE / 1024;
		phaseincr = 2.0 * M_PI * freq / sr;

		for (size_t j = 0; j < symlen; j++) {
			phase += phaseincr;
			if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI;
			outbuf[j] = sin(phase);
		}
		active_modem->ModulateXmtr(outbuf, symlen);
	}

	if (rmode == RSID_ESCAPE && rmode2 != RSID_NONE2) {
// transmit 10 symbol periods of silence between rsid sequences
		memset(outbuf, 0, symlen * sizeof(*outbuf));
		for (int i = 0; i < 10; i++)
			active_modem->ModulateXmtr(outbuf, symlen);

		Encode(rmode2, rsid);
		sr = active_modem->get_samplerate();
		len = (size_t)floor(RSID_SYMLEN * sr);
		if (unlikely(len != symlen)) {
			symlen = len;
			delete [] outbuf;
			outbuf = new double[symlen];
		}
// transmit sequence of 15 symbols (tones)
		fr = 1.0 * active_modem->get_txfreq() - (RSID_SAMPLE_RATE * 7 / 1024);
		phase = 0.0;

		for (int i = 0; i < 15; i++) {
			iTone = rsid[i];
			if (active_modem->get_reverse())
			iTone = 15 - iTone;
			freq = fr + iTone * RSID_SAMPLE_RATE / 1024;
			phaseincr = 2.0 * M_PI * freq / sr;

			for (size_t j = 0; j < symlen; j++) {
				phase += phaseincr;
				if (phase > 2.0 * M_PI) phase -= 2.0 * M_PI;
				outbuf[j] = sin(phase);
			}
			active_modem->ModulateXmtr(outbuf, symlen);
		}
	}

	// 5 symbol periods of silence at end of transmission
	// and between RsID and the data signal
	int nperiods = 5;
	memset(outbuf, 0, symlen * sizeof(*outbuf));
	for (int i = 0; i < nperiods; i++)
		active_modem->ModulateXmtr(outbuf, symlen);

}

