/*
Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO

This file is part of QtSoundModem

QtSoundModem 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.

QtSoundModem 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 QtSoundModem.  If not, see http://www.gnu.org/licenses

*/

// UZ7HO Soundmodem Port by John Wiseman G8BPQ

#include "UZ7HOStuff.h"
#include "fftw3.h"
#include <time.h>
#include "ecc.h"				// RS Constants
#include "hidapi.h"
#include <fcntl.h>
#include <errno.h>
#include <stdint.h>   


void platformInit();
void RsCreate();
void detector_init();
void KISS_init();
void ax25_init();
void init_raduga();
void il2p_init(int il2p_debug);
void SoundFlush();
void BufferFull(short * Samples, int nSamples);
void PollReceivedSamples();
void chk_dcd1(int snd_ch, int buf_size);
void make_core_BPF(UCHAR snd_ch, short freq, short width);
void modulator(UCHAR snd_ch, int buf_size);
void sendAckModeAcks(int snd_ch);
void PktARDOPEncode(UCHAR * Data, int Len, int Chan);
void RUHEncode(unsigned char * Data, int Len, int chan);
void sendRSID(int Chan, int dropTX);
void SetupGPIOPTT();
int stricmp(const unsigned char * pStr1, const unsigned char *pStr2);
int gpioInitialise(void);
int OpenCOMPort(char * pPort, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits);
void HAMLIBSetPTT(int PTTState);
void FLRigSetPTT(int PTTState);
void StartWatchdog();
void StopWatchdog();
void gpioWrite(unsigned gpio, unsigned level);
BOOL WriteCOMBlock(int fd, char * Block, int BytesToWrite);
void COMSetDTR(int fd);
void COMSetRTS(int fd);
void analiz_frame(int snd_ch, string * frame, char * code, boolean fecflag);
size_t write(int fd, void * buf, size_t count);
int close(int fd);
void SendMgmtPTT(int snd_ch, int PTTState);


BOOL KISSServ;
int KISSPort;

int NeedWaterfallHeaders = 0;

BOOL AGWServ;
int AGWPort;

int Number = 0;				// Number waiting to be sent

int SoundIsPlaying = 0;
int UDPSoundIsPlaying = 0;
int Capturing = 0;

int txmin = 0;
int txmax = 0;

extern unsigned short buffer[2][1200];
extern int SoundMode;
extern int needRSID[4];

extern short * DMABuffer;

unsigned short * SendtoCard(unsigned short * buf, int n);
short * SoundInit();
void DoTX(int Chan);
void UDPPollReceivedSamples();
extern void checkforCWID();


extern int SampleNo;

extern int pnt_change[5];				// Freq Changed Flag

// fftw library interface


fftwf_complex *in, *out;
fftwf_plan p;

int FFTSize = 4096;

char * Wisdom;

void initfft()
{
	fftwf_import_wisdom_from_string(Wisdom); 
	fftwf_set_timelimit(30);

#ifndef WIN32
	printf("It may take up to 30 seconds for the program to start for the first time\n");
#endif

	in = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
	out = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * 10000);
	p = fftwf_plan_dft_1d(FFTSize, in, out, FFTW_FORWARD, FFTW_PATIENT);

	Wisdom = fftwf_export_wisdom_to_string();
}

void dofft(short * inp, float * outr, float * outi)
{
	int i;
	
	fftwf_complex * fft = in;

	for (i = 0; i < FFTSize; i++)
	{
		fft[0][0] = inp[0] * 1.0f;
		fft[0][1] = 0;
		fft++;
		inp++;
	}

	fftwf_execute(p); 

	fft = out;

	for (i = 0; i < FFTSize; i++)
	{
		outr[0] = fft[0][0];
		outi[0] = fft[0][1];
		fft++;
		outi++;
		outr++;
	}
}

void freefft()
{
	fftwf_destroy_plan(p);
	fftwf_free(in);
	fftwf_free(out);
}

int nonGUIMode = 0;

void soundMain()
{
	// non platform specific initialisation

	platformInit();

	// initialise fft library

	RsCreate();				// RS code for MPSK

	detector_init();
	KISS_init();
	ax25_init();
	init_raduga();			// Set up waterfall colour table

	initfft();
	il2p_init(1);


	if (nonGUIMode)
	{
		Firstwaterfall = 0;
		Secondwaterfall = 0;
	}

	OpenPTTPort();
}


void SampleSink(int LR, short Sample)
{
	// This version is passed samples one at a time, as we don't have
	//	enough RAM in embedded systems to hold a full audio frame

	// LR - 1 == Right Chan

#ifdef TEENSY	
		int work = Sample;
		DMABuffer[Number++] = (work + 32768) >> 4; // 12 bit left justify
#else
	if (SCO)			// Single Channel Output - same to both L and R
	{
		DMABuffer[2 * Number] = Sample;
		DMABuffer[1 + 2 * Number] = Sample;
	}
	else
	{
		if (LR)				// Right
		{
			DMABuffer[1 + 2 * Number] = Sample;
			DMABuffer[2 * Number] = 0;
		}
		else
		{
			DMABuffer[2 * Number] = Sample;
			DMABuffer[1 + 2 * Number] = 0;
		}
	}
	if (using48000)
	{
		// Need to upsample to 48K. Try just duplicating sample

		uint16_t * ptr = &DMABuffer[2 * Number];

		*(&ptr[1]) = *(ptr);
		*(&ptr[2]) = *(ptr);
		*(&ptr[3]) = *(ptr);

		Number += 3;
	}
	Number++;
#endif
		if (Number >= SendSize)
		{
			// send this buffer to sound interface

			DMABuffer = SendtoCard(DMABuffer, SendSize);
			Number = 0;
		}
	

//	Last120[Last120Put++] = Sample;

//	if (Last120Put == (intN + 1))
//		Last120Put = 0;

	SampleNo++;
}


void Flush()
{
	SoundFlush(Number);
}

int ipow(int base, int exp)
{
	int result = 1;
	while (exp)
	{
		if (exp & 1)
			result *= base;
		exp >>= 1;
		base *= base;
	}

	return result;
}

int NumberOfBitsNeeded(int PowerOfTwo)
{
	int i;

	for (i = 0; i <= 16; i++)
	{
		if ((PowerOfTwo & ipow(2, i)) != 0)
			return i;

	}
	return 0;
}


int ReverseBits(int Index, int NumBits)
{
	int i, Rev = 0;

	for (i = 0; i < NumBits; i++)
	{
		Rev = (Rev * 2) | (Index & 1);
		Index = Index / 2;
	}

	return Rev;
}


void FourierTransform(int NumSamples, short * RealIn, float * RealOut, float * ImagOut, int InverseTransform)
{
	float AngleNumerator;
	unsigned char NumBits;

	int i, j, K, n, BlockSize, BlockEnd;
	float DeltaAngle, DeltaAr;
	float Alpha, Beta;
	float TR, TI, AR, AI;

	if (InverseTransform)
		AngleNumerator = -2.0f * M_PI;
	else
		AngleNumerator = 2.0f * M_PI;

	NumBits = NumberOfBitsNeeded(NumSamples);

	for (i = 0; i < NumSamples; i++)
	{
		j = ReverseBits(i, NumBits);
		RealOut[j] = RealIn[i];
		ImagOut[j] = 0.0f; // Not using i in ImageIn[i];
	}

	BlockEnd = 1;
	BlockSize = 2;

	while (BlockSize <= NumSamples)
	{
		DeltaAngle = AngleNumerator / BlockSize;
		Alpha = sinf(0.5f * DeltaAngle);
		Alpha = 2.0f * Alpha * Alpha;
		Beta = sinf(DeltaAngle);

		i = 0;

		while (i < NumSamples)
		{
			AR = 1.0f;
			AI = 0.0f;

			j = i;

			for (n = 0; n < BlockEnd; n++)
			{
				K = j + BlockEnd;
				TR = AR * RealOut[K] - AI * ImagOut[K];
				TI = AI * RealOut[K] + AR * ImagOut[K];
				RealOut[K] = RealOut[j] - TR;
				ImagOut[K] = ImagOut[j] - TI;
				RealOut[j] = RealOut[j] + TR;
				ImagOut[j] = ImagOut[j] + TI;
				DeltaAr = Alpha * AR + Beta * AI;
				AI = AI - (Alpha * AI - Beta * AR);
				AR = AR - DeltaAr;
				j = j + 1;
			}
			i = i + BlockSize;
		}
		BlockEnd = BlockSize;
		BlockSize = BlockSize * 2;
	}

	if (InverseTransform)
	{
		//	Normalize the resulting time samples...

		for (i = 0; i < NumSamples; i++)
		{
			RealOut[i] = RealOut[i] / NumSamples;
			ImagOut[i] = ImagOut[i] / NumSamples;
		}
	}
}



int LastBusyCheck = 0;

extern UCHAR CurrentLevel;

#ifdef PLOTSPECTRUM		
float dblMagSpectrum[206];
float dblMaxScale = 0.0f;
extern UCHAR Pixels[4096];
extern UCHAR * pixelPointer;
#endif

extern int blnBusyStatus;
int BusyDet = 5;

#define PLOTWATERFALL

int WaterfallActive = 1;
int SpectrumActive;

float BinSize;

extern int intLastStart;
extern int intLastStop;

void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin);


BOOL SMBusyDetect3(int Chan, float * dblMag, int intStart, int intStop)        // this only called while searching for leader ...once leader detected, no longer called.
{
	// First sort signals and look at highes signals:baseline ratio..

	float dblAVGSignalPerBinNarrow, dblAVGSignalPerBinWide, dblAVGBaselineNarrow, dblAVGBaselineWide;
	float dblSlowAlpha = 0.2f;
	static float dblAvgStoNNarrow[4] = { 0 }, dblAvgStoNWide[4] = {0};
	int intNarrow = 8;  // 8 x 11.72 Hz about 94 z
	int intWide = ((intStop - intStart) * 2) / 3; //* 0.66);
	int blnBusy = FALSE;
	int  BusyDet4th = BusyDet * BusyDet * BusyDet * BusyDet;

	// First sort signals and look at highest signals:baseline ratio..
	// First narrow band (~94Hz)

	SMSortSignals2(dblMag, intStart, intStop, intNarrow, &dblAVGSignalPerBinNarrow, &dblAVGBaselineNarrow);

	// Shouldn't dblAvgStoNNarrow, dblAvgStoNWide be static ??????


	if (intLastStart == intStart && intLastStop == intStop)
		dblAvgStoNNarrow[Chan] = (1 - dblSlowAlpha) * dblAvgStoNNarrow[Chan] + dblSlowAlpha * dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
	else
	{
		// This initializes the Narrow average after a bandwidth change

		dblAvgStoNNarrow[Chan] = dblAVGSignalPerBinNarrow / dblAVGBaselineNarrow;
	}

	// Wide band (66% of current bandwidth)

	SMSortSignals2(dblMag, intStart, intStop, intWide, &dblAVGSignalPerBinWide, &dblAVGBaselineWide);

	if (intLastStart == intStart && intLastStop == intStop)
		dblAvgStoNWide[Chan] = (1 - dblSlowAlpha) * dblAvgStoNWide[Chan] + dblSlowAlpha * dblAVGSignalPerBinWide / dblAVGBaselineWide;
	else
	{
		// This initializes the Wide average after a bandwidth change

		dblAvgStoNWide[Chan] = dblAVGSignalPerBinWide / dblAVGBaselineWide;
		intLastStart = intStart;
		intLastStop = intStop;
	}

	// Preliminary calibration...future a function of bandwidth and BusyDet.


	blnBusy = (dblAvgStoNNarrow[Chan] > (3 + 0.008 * BusyDet4th)) || (dblAvgStoNWide[Chan] > (5 + 0.02 * BusyDet4th));

//	if (BusyDet == 0)
//		blnBusy = FALSE;		// 0 Disables check ?? Is this the best place to do this?

//	WriteDebugLog(LOGDEBUG, "Busy %d Wide %f Narrow %f", blnBusy, dblAvgStoNWide, dblAvgStoNNarrow); 

	return blnBusy;
}

extern int compare(const void *p1, const void *p2);

void SMSortSignals2(float * dblMag, int intStartBin, int intStopBin, int intNumBins, float *  dblAVGSignalPerBin, float *  dblAVGBaselinePerBin)
{
	// puts the top intNumber of bins between intStartBin and intStopBin into dblAVGSignalPerBin, the rest into dblAvgBaselinePerBin
	// for decent accuracy intNumBins should be < 75% of intStopBin-intStartBin)

	// This version uses a native sort function which is much faster and reduces CPU loading significantly on wide bandwidths. 

	float dblSort[8192];
	float dblSum1 = 0, dblSum2 = 0;
	int numtoSort = (intStopBin - intStartBin) + 1, i;

	memcpy(dblSort, &dblMag[intStartBin], numtoSort * sizeof(float));

	qsort((void *)dblSort, numtoSort, sizeof(float), compare);

	for (i = numtoSort - 1; i >= 0; i--)
	{
		if (i >= (numtoSort - intNumBins))
			dblSum1 += dblSort[i];
		else
			dblSum2 += dblSort[i];
	}

	*dblAVGSignalPerBin = dblSum1 / intNumBins;
	*dblAVGBaselinePerBin = dblSum2 / (intStopBin - intStartBin - intNumBins - 1);
}

extern void updateDCD(int Chan, BOOL State);

void SMUpdateBusyDetector(int LR, float * Real, float *Imag)
{
	// Energy based detector for each channel.
	// Fed from FFT generated for waterfall display
	// FFT size is 4096

	float dblMag[4096];

	static BOOL blnLastBusyStatus[4];

	float dblMagAvg = 0;
	int i, chan;


	if (Now - LastBusyCheck < 100)	// ??
		return;

	LastBusyCheck = Now;

	// We need to run busy test on the frequncies used by each modem.

	for (chan = 0; chan < 4; chan++)
	{
		int Low, High, Start, End;

		if (soundChannel[chan] != (LR + 1))	// on this side of soundcard 
			continue;

		Low = tx_freq[chan] - txbpf[chan] / 2;
		High = tx_freq[chan] + txbpf[chan] / 2;

		if (Low < 100)
			continue;

		if (High > 3300)
			continue;

//		Low = tx_freq[chan] - 0.5*rx_shift[chan];
//		High = tx_freq[chan] + 0.5*rx_shift[chan];

		// BinSize is width of each fft bin in Hz

		Start = (Low / BinSize);		// First and last bins to process
		End = (High / BinSize);


		for (i = Start; i < End; i++)
		{
			dblMag[i] = powf(Real[i], 2) + powf(Imag[i], 2);	 // first pass
			dblMagAvg += dblMag[i];
		}

		blnBusyStatus = SMBusyDetect3(chan, dblMag, Start, End);

		if (blnBusyStatus && !blnLastBusyStatus[chan])
		{
//			Debugprintf("Ch %d Busy True", chan);
			updateDCD(chan, TRUE);
		}
		else if (blnLastBusyStatus[chan] && !blnBusyStatus)
		{
//			Debugprintf("Ch %d Busy False", chan);
			updateDCD(chan, FALSE);
		}
		blnLastBusyStatus[chan] = blnBusyStatus;
	}
}


extern short rawSamples[2400];	// Get Frame Type need 2400 and we may add 1200
int rawSamplesLength = 0;
extern int maxrawSamplesLength;

void ProcessNewSamples(short * Samples, int nSamples)
{
	if (SoundIsPlaying == FALSE && UDPSoundIsPlaying == FALSE)
		BufferFull(Samples, nSamples);
};

void doCalib(int Chan, int Act)
{
	if (Chan == 0 && calib_mode[1])
		return;						
	
	if (Chan == 1 && calib_mode[0])
		return;

	if (Act == 0)
	{
		calib_mode[Chan] = 0;
		tx_status[Chan] = TX_SILENCE;		// Stop TX
		Flush();
		RadioPTT(Chan, 0);
		Debugprintf("Stop Calib");
	}
	else
	{
		if (calib_mode[Chan] == 0)
			SampleNo = 0;

		calib_mode[Chan] = Act;
	}
}

int Freq_Change(int Chan, int Freq)
{
	int low, high;

	low = round(rx_shift[Chan] / 2 + (RCVR[Chan] * rcvr_offset[Chan]));
	high = round(RX_Samplerate / 2 - (rx_shift[Chan] / 2 + RCVR[Chan] * rcvr_offset[Chan]));

	if (Freq < 300)
		return rx_freq[Chan];				// Dont allow change

	if (Freq < low)
		return rx_freq[Chan];				// Dont allow change

	if (Freq > high)
		return rx_freq[Chan];				// Dont allow change

	rx_freq[Chan] = Freq;
	tx_freq[Chan] = Freq;

	pnt_change[Chan] = TRUE;
	NeedWaterfallHeaders = TRUE;

	return Freq;
}

void MainLoop()
{
	// Called by background thread every 10 ms (maybe)

	// Actually we may have two cards
	
	// Original only allowed one channel per card.
	// I think we should be able to run more, ie two or more
	// modems on same soundcard channel
	
	// So All the soundcard stuff will need to be generalised

	if (UDPServ)
		UDPPollReceivedSamples();

	if (SoundMode == 3)
		UDPPollReceivedSamples();
	else
		PollReceivedSamples();


	for (int i = 0; i < 4; i++)
	{
		if (modem_mode[i] == MODE_ARDOP)
		{
			chk_dcd1(i, 512);
		}
	}
	DoTX(0);
	DoTX(1);
	DoTX(2);
	DoTX(3);

}

int ARDOPSendToCard(int Chan, int Len)
{
	// Send Next Block of samples to the soundcard


	short * in = &ARDOPTXBuffer[Chan][ARDOPTXPtr[Chan]];		// Enough to hold whole frame of samples
	short * out = DMABuffer;

	int LR = modemtoSoundLR[Chan];

	int i;

	for (i = 0; i < Len; i++)
	{
		if (SCO)			// Single Channel Output - same to both L and R
		{
			*out++ = *in;
			*out++ = *in++;
		}
		else
		{
			if (LR)				// Right
			{
				*out++ = 0;
				*out++ = *in++;
			}
			else
			{
				*out++ = *in++;
				*out++ = 0;
			}
		}
	}

	DMABuffer = SendtoCard(DMABuffer, Len);

	ARDOPTXPtr[Chan] += Len;

	// See if end of buffer

	if (ARDOPTXPtr[Chan] > ARDOPTXLen[Chan])
		return 1;

	return 0;
}
void DoTX(int Chan)
{
	// This kicks off a send sequence or calibrate

//	printtick("dotx");

	if (calib_mode[Chan])
	{
		// Maybe new calib or continuation

		if (pnt_change[Chan])
		{
			make_core_BPF(Chan, rx_freq[Chan], bpf[Chan]);
			make_core_TXBPF(Chan, tx_freq[Chan], txbpf[Chan]);
			pnt_change[Chan] = FALSE;
		}
		
		// Note this may block in SendtoCard

		modulator(Chan, tx_bufsize);
		return;
	}

	// I think we have to detect NO_DATA here and drop PTT and return to SILENCE

	if (tx_status[Chan] == TX_NO_DATA)
	{
		Flush();
		Debugprintf("TX Complete %d", SampleNo);
		RadioPTT(Chan, 0);
		Continuation[Chan] = 0;

		tx_status[Chan] = TX_SILENCE;

		// We should now send any ackmode acks as the channel is now free for dest to reply

		sendAckModeAcks(Chan);
	}

	if (tx_status[Chan] != TX_SILENCE)
	{
		// Continue the send

		if (modem_mode[Chan] == MODE_ARDOP || modem_mode[Chan] == MODE_RUH)
		{
//			if (SeeIfCardBusy())
//				return 0;

			if (ARDOPSendToCard(Chan, SendSize) == 1)
			{
				// End of TX

				Number = 0;
				Flush();

				// See if more to send. If so, don't drop PTT

				if (all_frame_buf[Chan].Count)
				{
					SoundIsPlaying = TRUE;
					Number = 0;

					Continuation[Chan] = 1;

					Debugprintf("TX Continuing");

					string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
					string * tx_data;

					if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
					{
						// Save copy then copy data up 3 bytes

						Add(&KISS_acked[Chan], duplicateString(myTemp));

						mydelete(myTemp, 0, 3);
						myTemp->Length -= sizeof(void *);
					}
					else
					{
						// Just remove control 

						mydelete(myTemp, 0, 1);
					}

					tx_data = duplicateString(myTemp);		// so can free original below

					Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp

					AGW_AX25_frame_analiz(Chan, FALSE, tx_data);

					put_frame(Chan, tx_data, "", TRUE, FALSE);

					if (modem_mode[Chan] == MODE_ARDOP)
						PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);
					else
						RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);

					freeString(tx_data);

					// Samples are now in DMABuffer = Send first block

					DMABuffer = SoundInit();

					ARDOPSendToCard(Chan, SendSize);
					tx_status[Chan] = TX_FRAME;
					return;
				}

				Debugprintf("TX Complete %d", SampleNo); 
				RadioPTT(Chan, 0);
				Continuation[Chan] = 0;

				tx_status[Chan] = TX_SILENCE;

				// We should now send any ackmode acks as the channel is now free for dest to reply
			}

			return;
		}

		modulator(Chan, tx_bufsize); 
		return;
	}

	if (SoundIsPlaying || UDPSoundIsPlaying)
		return;

	// Not doing anything so see if we have anything new to send

	// See if frequency has changed

	if (pnt_change[Chan])
	{
		make_core_BPF(Chan, rx_freq[Chan], bpf[Chan]);
		make_core_TXBPF(Chan, tx_freq[Chan], txbpf[Chan]);
		pnt_change[Chan] = FALSE;
	}

	// See if we need an RSID

	if (needRSID[Chan])
	{
		needRSID[Chan] = 0;

		// Note this may block in SampleSink

		Debugprintf("Sending RSID");
		sendRSID(Chan, all_frame_buf[Chan].Count == 0);
		return;
	}

	if (all_frame_buf[Chan].Count == 0)
		return;

	// Start a new send. modulator should handle TXD etc

	checkforCWID();		// See if need to start CWID timer in afteractivity mode

	Debugprintf("TX Start");
	SampleNo = 0;

	SoundIsPlaying = TRUE;
	RadioPTT(Chan, 1);
	Number = 0;

	if (modem_mode[Chan] == MODE_ARDOP)
	{
		// I think ARDOP will have to generate a whole frame of samples
		// then send them out a bit at a time to avoid stopping here for
		// possibly 10's of seconds

		// Can do this here as unlike normal ardop we don't need to run on Teensy
		// to 12000 sample rate we need either 24K or 48K per second, depending on
		// where we do the stereo mux. 

		// Slowest rate is 50 baud, so a 255 byte packet would take about a minute
		// allowing for RS overhead. Not really realistic put perhaps should be possible.
		// RAM isn't an issue so maybe allocate 2 MB. 

		// ?? Should we allow two ARDOP modems - could make sense if we can run sound
		// card channels independently

		string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
		string * tx_data;

		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
		{
			// Save copy then copy data up 3 bytes

			Add(&KISS_acked[Chan], duplicateString(myTemp));

			mydelete(myTemp, 0, 3);
			myTemp->Length -= sizeof(void *);
		}
		else
		{
			// Just remove control 

			mydelete(myTemp, 0, 1);
		}

		tx_data = duplicateString(myTemp);		// so can free original below

		Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp

		AGW_AX25_frame_analiz(Chan, FALSE, tx_data);

		put_frame(Chan, tx_data, "", TRUE, FALSE);

		PktARDOPEncode(tx_data->Data, tx_data->Length - 2, Chan);

		freeString(tx_data);

		// Samples are now in DMABuffer = Send first block

		ARDOPSendToCard(Chan, SendSize);
		tx_status[Chan] = TX_FRAME;

	}
	else if (modem_mode[Chan] == MODE_RUH)
	{
		// Same as for ARDOP. Generate a whole frame of samples 
		// then send them out a bit at a time to avoid stopping here

		// We allow two RUH modems

		string * myTemp = Strings(&all_frame_buf[Chan], 0);			// get message
		string * tx_data;

		if ((myTemp->Data[0] & 0x0f) == 12)			// ACKMODE
		{
			// Save copy then copy data up 3 bytes

			Add(&KISS_acked[Chan], duplicateString(myTemp));

			mydelete(myTemp, 0, 3);
			myTemp->Length -= sizeof(void *);
		}
		else
		{
			// Just remove control 

			mydelete(myTemp, 0, 1);
		}

		tx_data = duplicateString(myTemp);		// so can free original below

		Delete(&all_frame_buf[Chan], 0);			// This will invalidate temp

		AGW_AX25_frame_analiz(Chan, FALSE, tx_data);

		put_frame(Chan, tx_data, "", TRUE, FALSE);

		RUHEncode(tx_data->Data, tx_data->Length - 2, Chan);

		freeString(tx_data);

		// Samples are now in DMABuffer = Send first block

		ARDOPSendToCard(Chan, SendSize);
		tx_status[Chan] = TX_FRAME;

	}
	else
		modulator(Chan, tx_bufsize);

	return;
}

void RX2TX(int snd_ch)
{
	if (snd_status[snd_ch] == SND_IDLE)
	{
		DoTX(snd_ch);
	}
}

// PTT Stuff

int hPTTDevice = 0;
char PTTPort[80] = "";			// Port for Hardware PTT - may be same as control port.
int PTTBAUD = 19200;
int PTTMode = PTTRTS;			// PTT Control Flags.

char PTTOnString[128] = "";
char PTTOffString[128] = "";

UCHAR PTTOnCmd[64];
UCHAR PTTOnCmdLen = 0;

UCHAR PTTOffCmd[64];
UCHAR PTTOffCmdLen = 0;

int pttGPIOPin = 17;			// Default
int pttGPIOPinR = 17;
BOOL pttGPIOInvert = FALSE;
BOOL useGPIO = FALSE;
BOOL gotGPIO = FALSE;

int HamLibPort = 4532;
char HamLibHost[32] = "127.0.0.1";

int FLRigPort = 12345;
char FLRigHost[32] = "127.0.0.1";

char CM108Addr[80] = "";

int VID = 0;
int PID = 0;

// CM108 Code

char * CM108Device = NULL;

void DecodeCM108(char * ptr)
{
	// Called if Device Name or PTT = Param is CM108

#ifdef WIN32

	// Next Param is VID and PID - 0xd8c:0x8 or Full device name
	// On Windows device name is very long and difficult to find, so 
	//	easier to use VID/PID, but allow device in case more than one needed

	char * next;
	long VID = 0, PID = 0;
	char product[256] = "Unknown";

	struct hid_device_info *devs, *cur_dev;
	const char *path_to_open = NULL;
	hid_device *handle = NULL;

	if (strlen(ptr) > 16)
		CM108Device = _strdup(ptr);
	else
	{
		VID = strtol(ptr, &next, 0);
		if (next)
			PID = strtol(++next, &next, 0);

		// Look for Device

		devs = hid_enumerate((unsigned short)VID, (unsigned short)PID);
		cur_dev = devs;

		while (cur_dev)
		{
			if (cur_dev->product_string)
				wcstombs(product, cur_dev->product_string, 255);
			
			Debugprintf("HID Device %s VID %X PID %X", product, cur_dev->vendor_id, cur_dev->product_id);
			if (cur_dev->vendor_id == VID && cur_dev->product_id == PID)
			{
				path_to_open = cur_dev->path;
				break;
			}
			cur_dev = cur_dev->next;
		}

		if (path_to_open)
		{
			handle = hid_open_path(path_to_open);

			if (handle)
			{
				hid_close(handle);
				CM108Device = _strdup(path_to_open);
			}
			else
			{
				Debugprintf("Unable to open CM108 device %x %x", VID, PID);
			}
		}
		else
			Debugprintf("Couldn't find CM108 device %x %x", VID, PID);

		hid_free_enumeration(devs);
	}
#else

	// Linux - Next Param HID Device, eg /dev/hidraw0

	CM108Device = _strdup(ptr);
#endif
}

char * strlop(char * buf, char delim)
{
	// Terminate buf at delim, and return rest of string

	char * ptr = strchr(buf, delim);

	if (ptr == NULL) return NULL;

	*(ptr)++ = 0;
	return ptr;
}

void OpenPTTPort()
{
	PTTMode &= ~PTTCM108;
	PTTMode &= ~PTTHAMLIB;

	if (PTTPort[0] && strcmp(PTTPort, "None") != 0)
	{
		if (PTTMode == PTTCAT)
		{
			// convert config strings from Hex

			char * ptr1 = PTTOffString;
			UCHAR * ptr2 = PTTOffCmd;
			char c;
			int val;

			while (c = *(ptr1++))
			{
				val = c - 0x30;
				if (val > 15) val -= 7;
				val <<= 4;
				c = *(ptr1++) - 0x30;
				if (c > 15) c -= 7;
				val |= c;
				*(ptr2++) = val;
			}

			PTTOffCmdLen = ptr2 - PTTOffCmd;

			ptr1 = PTTOnString;
			ptr2 = PTTOnCmd;

			while (c = *(ptr1++))
			{
				val = c - 0x30;
				if (val > 15) val -= 7;
				val <<= 4;
				c = *(ptr1++) - 0x30;
				if (c > 15) c -= 7;
				val |= c;
				*(ptr2++) = val;
			}

			PTTOnCmdLen = ptr2 - PTTOnCmd;
		}

		if (stricmp(PTTPort, "GPIO") == 0)
		{
			// Initialise GPIO for PTT if available

#ifdef __ARM_ARCH

			if (gpioInitialise() == 0)
			{
				printf("GPIO interface for PTT available\n");
				gotGPIO = TRUE;

				SetupGPIOPTT();
			}
			else
				printf("Couldn't initialise GPIO interface for PTT\n");

#else
			printf("GPIO interface for PTT not available on this platform\n");
#endif

		}
		else if (stricmp(PTTPort, "CM108") == 0)
		{
			DecodeCM108(CM108Addr);
			PTTMode |= PTTCM108;
		}

		else if (stricmp(PTTPort, "HAMLIB") == 0)
		{
			PTTMode |= PTTHAMLIB;
			HAMLIBSetPTT(0);			// to open port
			return;
		}
		else if (stricmp(PTTPort, "FLRIG") == 0)
		{
			PTTMode |= PTTFLRIG;
			FLRigSetPTT(0);			// to open port
			return;
		}

		else		//  Not GPIO
		{
			hPTTDevice = OpenCOMPort(PTTPort, PTTBAUD, FALSE, FALSE, FALSE, 0);
		}
	}
}

void ClosePTTPort()
{
	if (hPTTDevice)
		CloseCOMPort(hPTTDevice);
	
	hPTTDevice = 0;
}
void CM108_set_ptt(int PTTState)
{
	char io[5];
	hid_device *handle;
	int n;

	io[0] = 0;
	io[1] = 0;
	io[2] = 1 << (3 - 1);
	io[3] = PTTState << (3 - 1);
	io[4] = 0;

	if (CM108Device == NULL)
		return;

#ifdef WIN32
	handle = hid_open_path(CM108Device);

	if (!handle) {
		printf("unable to open device\n");
		return;
	}

	n = hid_write(handle, io, 5);
	if (n < 0)
	{
		printf("Unable to write()\n");
		printf("Error: %ls\n", hid_error(handle));
	}

	hid_close(handle);

#else

	int fd;

	fd = open(CM108Device, O_WRONLY);

	if (fd == -1)
	{
		printf("Could not open %s for write, errno=%d\n", CM108Device, errno);
		return;
	}

	io[0] = 0;
	io[1] = 0;
	io[2] = 1 << (3 - 1);
	io[3] = PTTState << (3 - 1);
	io[4] = 0;

	n = write(fd, io, 5);
	if (n != 5)
	{
		printf("Write to %s failed, n=%d, errno=%d\n", CM108Device, n, errno);
	}

	close(fd);
#endif
	return;

}

float amplitudes[4] = { 32000, 32000, 32000, 32000 };
extern float amplitude;
void startpttOnTimer();
extern void UpdatePTTStats(int Chan, int State);

void RadioPTT(int snd_ch, BOOL PTTState)
{
	snd_status[snd_ch] = PTTState; // SND_IDLE = 0 SND_TX = 1 

	if (PTTState)
	{
		txmax = txmin = 0;
		amplitude = amplitudes[snd_ch];
		StartWatchdog();
	}
	else
	{
		Debugprintf("Output peaks = %d, %d, amp %f", txmin, txmax, amplitude);
		amplitudes[snd_ch] = amplitude;
		StopWatchdog();
	}

	if ((PTTMode & PTTHOST))
	{
		// Send PTT ON/OFF to any mgmt connections

		SendMgmtPTT(snd_ch, PTTState);
	}

	UpdatePTTStats(snd_ch, PTTState);

#ifdef __ARM_ARCH
	if (useGPIO)
	{
		if (DualPTT && modemtoSoundLR[snd_ch] == 1)
			gpioWrite(pttGPIOPinR, (pttGPIOInvert ? (1 - PTTState) : (PTTState)));
		else
			gpioWrite(pttGPIOPin, (pttGPIOInvert ? (1 - PTTState) : (PTTState)));
		startpttOnTimer();
		return;
	}

#endif

	if ((PTTMode & PTTCM108))
	{
		CM108_set_ptt(PTTState);
		startpttOnTimer();
		return;
	}
	
	if ((PTTMode & PTTHAMLIB))
	{
		HAMLIBSetPTT(PTTState);
		startpttOnTimer();
		return;
	}

	if ((PTTMode & PTTFLRIG))
	{
		FLRigSetPTT(PTTState);
		startpttOnTimer();
		return;
	}

	if (hPTTDevice == 0)
	{
		startpttOnTimer();
		return;
	}
	if ((PTTMode & PTTCAT))
	{
		if (PTTState)
			WriteCOMBlock(hPTTDevice, PTTOnCmd, PTTOnCmdLen);
		else
			WriteCOMBlock(hPTTDevice, PTTOffCmd, PTTOffCmdLen);

		return;
	}

	if (DualPTT && modemtoSoundLR[snd_ch] == 1)		// use DTR
	{
		if (PTTState)
			COMSetDTR(hPTTDevice);
		else
			COMClearDTR(hPTTDevice);
	}
	else
	{
		if ((PTTMode & PTTRTS))
		{
			if (PTTState)
				COMSetRTS(hPTTDevice);
			else
				COMClearRTS(hPTTDevice);
		}
	}

				
	startpttOnTimer();

}

char ShortDT[] = "HH:MM:SS";

char * ShortDateTime()
{
	struct tm * tm;
	time_t NOW = time(NULL);

	tm = gmtime(&NOW);

	sprintf(ShortDT, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
	return ShortDT;
}


// Reed Solomon Stuff


int NPAR = -1;	// Number of Parity Bytes - used in RS Code

int xMaxErrors = 0;

int RSEncode(UCHAR * bytToRS, UCHAR * RSBytes, int DataLen, int RSLen)
{
	// This just returns the Parity Bytes. I don't see the point
	// in copying the message about

	unsigned char Padded[256];		// The padded Data

	int Length = DataLen + RSLen;	// Final Length of packet
	int PadLength = 255 - Length;	// Padding bytes needed for shortened RS codes

	//	subroutine to do the RS encode. For full length and shortend RS codes up to 8 bit symbols (mm = 8)

	if (NPAR != RSLen)		// Changed RS Len, so recalc constants;
	{
		NPAR = RSLen;
		xMaxErrors = NPAR / 2;
		initialize_ecc();
	}

	// Copy the supplied data to end of data array.

	memset(Padded, 0, PadLength);
	memcpy(&Padded[PadLength], bytToRS, DataLen);

	encode_data(Padded, 255 - RSLen, RSBytes);

	return RSLen;
}

//	Main RS decode function

extern int index_of[];
extern int recd[];
extern int Corrected[256];
extern int tt;		//  number of errors that can be corrected 
extern int kk;		// Info Symbols

extern BOOL blnErrorsCorrected;


BOOL RSDecode(UCHAR * bytRcv, int Length, int CheckLen, BOOL * blnRSOK)
{


	// Using a modified version of Henry Minsky's code

	//Copyright Henry Minsky (hqm@alum.mit.edu) 1991-2009

	// Rick's Implementation processes the byte array in reverse. and also 
	//	has the check bytes in the opposite order. I've modified the encoder
	//	to allow for this, but so far haven't found a way to mske the decoder
	//	work, so I have to reverse the data and checksum to decode G8BPQ Nov 2015

	//	returns TRUE if was ok or correction succeeded, FALSE if correction impossible

	UCHAR intTemp[256];				// WOrk Area to pass to Decoder		
	int i;
	UCHAR * ptr2 = intTemp;
	UCHAR * ptr1 = &bytRcv[Length - CheckLen - 1]; // Last Byte of Data

	int DataLen = Length - CheckLen;
	int PadLength = 255 - Length;		// Padding bytes needed for shortened RS codes

	*blnRSOK = FALSE;

	if (Length > 255 || Length < (1 + CheckLen))		//Too long or too short 
		return FALSE;

	if (NPAR != CheckLen)		// Changed RS Len, so recalc constants;
	{
		NPAR = CheckLen;
		xMaxErrors = NPAR / 2;

		initialize_ecc();
	}


	//	We reverse the data while zero padding it to speed things up

	//	We Need (Data Reversed) (Zero Padding) (Checkbytes Reversed)

	// Reverse Data

	for (i = 0; i < DataLen; i++)
	{
		*(ptr2++) = *(ptr1--);
	}

	//	Clear padding

	memset(ptr2, 0, PadLength);

	ptr2 += PadLength;

	// Error Bits

	ptr1 = &bytRcv[Length - 1];			// End of check bytes

	for (i = 0; i < CheckLen; i++)
	{
		*(ptr2++) = *(ptr1--);
	}

	decode_data(intTemp, 255);

	// check if syndrome is all zeros 

	if (check_syndrome() == 0)
	{
		// RS ok, so no need to correct

		*blnRSOK = TRUE;
		return TRUE;		// No Need to Correct
	}

	if (correct_errors_erasures(intTemp, 255, 0, 0) == 0) // Dont support erasures at the momnet

		// Uncorrectable

		return FALSE;

	// Data has been corrected, so need to reverse again

	ptr1 = &intTemp[DataLen - 1];
	ptr2 = bytRcv; // Last Byte of Data

	for (i = 0; i < DataLen; i++)
	{
		*(ptr2++) = *(ptr1--);
	}

	// ?? Do we need to return the check bytes ??

	// Yes, so we can redo RS Check on supposedly connected frame

	ptr1 = &intTemp[254];	// End of Check Bytes

	for (i = 0; i < CheckLen; i++)
	{
		*(ptr2++) = *(ptr1--);
	}

	return TRUE;
}

extern TStringList detect_list[5];
extern TStringList detect_list_c[5];

void ProcessPktFrame(int snd_ch, UCHAR * Data, int frameLen)
{
	string * pkt = newString();

	stringAdd(pkt, Data, frameLen + 2);			// 2 for crc (not actually there)

	analiz_frame(snd_ch, pkt, "ARDOP", 1);

}