/*
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 <QMessageBox>
#include "QtSoundModem.h"
#include "UZ7HOStuff.h"
#include <QTimer>

#define CONNECT(sndr, sig, rcvr, slt) connect(sndr, SIGNAL(sig), rcvr, SLOT(slt))

QList<QTcpSocket*>  _KISSSockets;
QList<QTcpSocket*>  _AGWSockets;

QTcpServer * _KISSserver;
QTcpServer * _AGWserver;

extern workerThread *t;
extern mynet m1;

extern "C" int KISSPort;
extern "C" void * initPulse();
extern "C" int SoundMode;

extern "C" int UDPClientPort;
extern "C" int UDPServerPort;
extern "C" int TXPort;

char UDPHost[64] = "";

int UDPServ = 0;				// UDP Server Active (ie broadcast sound frams as UDP packets)

QMutex mutex;

extern void saveSettings();

extern int Closing;				// Set to stop background thread

extern "C"
{
	void KISSDataReceived(void * sender, char * data, int length);
	void AGW_explode_frame(void * soket, char * data, int len);
	void KISS_add_stream(void * Socket);
	void KISS_del_socket(void * Socket);
	void AGW_add_socket(void * Socket);
	void AGW_del_socket(void * socket);
	void Debugprintf(const char * format, ...);
	int InitSound(BOOL Report);
	void soundMain();
	void MainLoop();
	void set_speed(int snd_ch, int Modem);
	void init_speed(int snd_ch);

}

extern "C" int nonGUIMode;

QTimer *timer;
QTimer *timercopy;

void mynet::start()
{
	if (SoundMode == 3)
		OpenUDP();

	if (UDPServ)
		OpenUDP();

	if (KISSServ)
	{
		_KISSserver = new(QTcpServer);

		if (_KISSserver->listen(QHostAddress::Any, KISSPort))
			connect(_KISSserver, SIGNAL(newConnection()), this, SLOT(onKISSConnection()));
		else
		{
			if (nonGUIMode)
				Debugprintf("Listen failed for KISS Port");
			else
			{
				QMessageBox msgBox;
				msgBox.setText("Listen failed for KISS Port.");
				msgBox.exec();
			}
		}
	}

	if (AGWServ)
	{
		_AGWserver = new(QTcpServer);
		if (_AGWserver->listen(QHostAddress::Any, AGWPort))
			connect(_AGWserver, SIGNAL(newConnection()), this, SLOT(onAGWConnection()));
		else
		{
			if (nonGUIMode)
				Debugprintf("Listen failed for AGW Port");
			else
			{
				QMessageBox msgBox;
				msgBox.setText("Listen failed for AGW Port.");
				msgBox.exec();
			}
		}
	}

	QObject::connect(t, SIGNAL(sendtoKISS(void *, unsigned char *, int)), this, SLOT(sendtoKISS(void *, unsigned char *, int)), Qt::QueuedConnection);


	QTimer *timer = new QTimer(this);
	connect(timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot()));
	timer->start(100);
}

void mynet::MyTimerSlot()
{
	// 100 mS Timer Event

	TimerEvent = TIMER_EVENT_ON;
}


void mynet::onAGWConnection()
{
	QTcpSocket *clientSocket = _AGWserver->nextPendingConnection();
	connect(clientSocket, SIGNAL(readyRead()), this, SLOT(onAGWReadyRead()));
	connect(clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onAGWSocketStateChanged(QAbstractSocket::SocketState)));

	_AGWSockets.push_back(clientSocket);

	AGW_add_socket(clientSocket);

	Debugprintf("AGW Connect Sock %x", clientSocket);
}



void mynet::onAGWSocketStateChanged(QAbstractSocket::SocketState socketState)
{
	if (socketState == QAbstractSocket::UnconnectedState)
	{
		QTcpSocket* sender = static_cast<QTcpSocket*>(QObject::sender());

		AGW_del_socket(sender);

		_AGWSockets.removeOne(sender);
	}
}

void mynet::onAGWReadyRead()
{
	QTcpSocket* sender = static_cast<QTcpSocket*>(QObject::sender());
	QByteArray datas = sender->readAll();

	AGW_explode_frame(sender, datas.data(), datas.length());
}


void mynet::onKISSConnection()
{
	QTcpSocket *clientSocket = _KISSserver->nextPendingConnection();
	connect(clientSocket, SIGNAL(readyRead()), this, SLOT(onKISSReadyRead()));
	connect(clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onKISSSocketStateChanged(QAbstractSocket::SocketState)));

	_KISSSockets.push_back(clientSocket);

	KISS_add_stream(clientSocket);

	Debugprintf("KISS Connect Sock %x", clientSocket);
}

void mynet::onKISSSocketStateChanged(QAbstractSocket::SocketState socketState)
{
	if (socketState == QAbstractSocket::UnconnectedState)
	{
		QTcpSocket* sender = static_cast<QTcpSocket*>(QObject::sender());

		KISS_del_socket(sender);

		_KISSSockets.removeOne(sender);
	}
}

void mynet::onKISSReadyRead()
{
	QTcpSocket* sender = static_cast<QTcpSocket*>(QObject::sender());
	QByteArray datas = sender->readAll();

	KISSDataReceived(sender, datas.data(), datas.length());
}



void mynet::displayError(QAbstractSocket::SocketError socketError)
{
	if (socketError == QTcpSocket::RemoteHostClosedError)
		return;

	qDebug() << tcpClient->errorString();

	tcpClient->close();
	tcpServer->close();
}



void mynet::sendtoKISS(void * sock, unsigned char * Msg, int Len)
{
	if (sock == NULL)
	{
		for (QTcpSocket* socket : _KISSSockets)
		{
			socket->write((char *)Msg, Len);
		}
	}
	else
	{
		QTcpSocket* socket = (QTcpSocket*)sock;
		socket->write((char *)Msg, Len);
	}
//	free(Msg);
}



QTcpSocket * HAMLIBsock;
int HAMLIBConnected = 0;
int HAMLIBConnecting = 0;

void mynet::HAMLIBdisplayError(QAbstractSocket::SocketError socketError)
{
	switch (socketError)
	{
	case QAbstractSocket::RemoteHostClosedError:
		break;

	case QAbstractSocket::HostNotFoundError:
		if (nonGUIMode)
			qDebug() << "HAMLIB host was not found. Please check the host name and port settings.";
		else
		{
			QMessageBox::information(nullptr, tr("QtSM"),
				tr("HAMLIB host was not found. Please check the "
					"host name and port settings."));
		}

		break;

	case QAbstractSocket::ConnectionRefusedError:

		qDebug() << "HAMLIB Connection Refused";
		break;

	default:

		qDebug() << "HAMLIB Connection Failed";
		break;

	}

	HAMLIBConnecting = 0;
	HAMLIBConnected = 0;
}

void mynet::HAMLIBreadyRead()
{
	unsigned char Buffer[4096];
	QTcpSocket* Socket = static_cast<QTcpSocket*>(QObject::sender());

	// read the data from the socket. Don't do anyhing with it at the moment

	Socket->read((char *)Buffer, 4095);
}

void mynet::onHAMLIBSocketStateChanged(QAbstractSocket::SocketState socketState)
{
	if (socketState == QAbstractSocket::UnconnectedState)
	{
		// Close any connections

		HAMLIBConnected = 0;
		qDebug() << "HAMLIB Connection Closed";
	}
	else if (socketState == QAbstractSocket::ConnectedState)
	{
		HAMLIBConnected = 1;
		HAMLIBConnecting = 0;
		qDebug() << "HAMLIB Connected";
	}
}


void mynet::ConnecttoHAMLIB()
{
	delete(HAMLIBsock);

	HAMLIBConnected = 0;
	HAMLIBConnecting = 1;

	HAMLIBsock = new QTcpSocket();

	connect(HAMLIBsock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(HAMLIBdisplayError(QAbstractSocket::SocketError)));
	connect(HAMLIBsock, SIGNAL(readyRead()), this, SLOT(HAMLIBreadyRead()));
	connect(HAMLIBsock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onHAMLIBSocketStateChanged(QAbstractSocket::SocketState)));

	HAMLIBsock->connectToHost(HamLibHost, HamLibPort);

	return;
}

extern "C" void HAMLIBSetPTT(int PTTState)
{
	// Won't work in non=gui mode

	emit m1.HLSetPTT(PTTState);
}

extern "C" void startTimer(int Time)
{
	// Won't work in non=gui mode

	emit m1.startTimer(Time);
}

void mynet::dostartTimer(int Time)
{
	timercopy->start(Time);
}

extern "C" void stopTimer()
{
	// Won't work in non=gui mode

	emit m1.stopTimer();
}

void mynet::dostopTimer()
{
	timercopy->stop();
}

void mynet::doHLSetPTT(int c)
{
	char Msg[16];

	if (HAMLIBsock == nullptr || HAMLIBsock->state() != QAbstractSocket::ConnectedState)
		ConnecttoHAMLIB();

	sprintf(Msg, "T %d\r\n", c);
	HAMLIBsock->write(Msg);

	HAMLIBsock->waitForBytesWritten(30000);

	QByteArray datas = HAMLIBsock->readAll();

	qDebug(datas.data());

}





extern "C" void KISSSendtoServer(void * sock, Byte * Msg, int Len)
{
	emit t->sendtoKISS(sock, Msg, Len);
}


void workerThread::run()
{
	if (SoundMode == 2)			// Pulse
	{
		if (initPulse() == nullptr)
		{
			if (nonGUIMode)
			{
				qDebug() << "PulseAudio requested but pulseaudio libraries not found\nMode set to ALSA\n";
			}
			else
			{
				QMessageBox msgBox;
				msgBox.setText("PulseAudio requested but pulseaudio libraries not found\nMode set to ALSA");
				msgBox.exec();
			}
			SoundMode = 0;
			saveSettings();
		}
	}

	soundMain();

	if (SoundMode != 3)
	{
		if (!InitSound(1))
		{
			//		QMessageBox msgBox;
			//		msgBox.setText("Open Sound Card Failed");
			//		msgBox.exec();
		}
	}

	// Initialise Modems

	init_speed(0);
	init_speed(1);
	init_speed(2);
	init_speed(3);

	//	emit t->openSockets();

	while (Closing == 0)
	{
		// Run scheduling loop

		MainLoop();

		this->msleep(10);
	}

	qDebug() << "Saving Settings";

	saveSettings();

	qDebug() << "Main Loop exited";

	qApp->exit();

};

// Audio over UDP Code.

// Code can either send audio blocks from the sound card as UDP packets or use UDP packets from 
// a suitable source (maybe another copy of QtSM) and process them instead of input frm a sound card/

// ie act as a server or client for UDP audio.

// of course we need bidirectional audio, so even when we are a client we send modem generated samples
// to the server and as a server pass received smaples to modem

// It isn't logical to run as both client and server, so probably can use just one socket


QUdpSocket * udpSocket;

qint64 udpServerSeqno= 0;
qint64 udpClientLastSeq = 0;
qint64 udpServerLastSeq = 0;
int droppedPackets = 0;
extern "C" int UDPSoundIsPlaying;

QQueue <unsigned char *> queue;


void mynet::OpenUDP()
{
	udpSocket = new QUdpSocket();

	if (UDPServ)
	{
		udpSocket->bind(QHostAddress("0.0.0.0"), UDPServerPort);
		QTimer *timer = new QTimer(this);
		timercopy = timer;
		connect(timer, SIGNAL(timeout()), this, SLOT(dropPTT()));
	}
	else
		udpSocket->bind(QHostAddress("0.0.0.0"), UDPClientPort);

	connect(udpSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));	
}

extern "C" void Flush();

void mynet::dropPTT()
{
	timercopy->stop();
	
	if (UDPSoundIsPlaying)
	{
		// Drop PTT when all sent

		Flush();
		UDPSoundIsPlaying = 0;
		Debugprintf("PTT Off");
		RadioPTT(0, 0);
	}
}

void mynet::readPendingDatagrams()
{
	while (udpSocket->hasPendingDatagrams())
	{
		QHostAddress Addr;
		quint16 rxPort;
		char copy[1501];

		// We expect datagrams of 1040 bytes containing a 16 byte header and 512 16 bit samples
		// We should get a datagram every 43 mS. We need to use a timeout to drop PTT if running as server

		if (UDPServ)
			timercopy->start(200);

		int Len = udpSocket->readDatagram(copy, 1500, &Addr, &rxPort);

		if (Len == 1040)
		{
			qint64 Seq;

			memcpy(&Seq, copy, sizeof(udpServerSeqno));

			if (Seq < udpClientLastSeq || udpClientLastSeq == 0)

				// Client or Server Restarted

				udpClientLastSeq = Seq;

			else
			{
				int Missed = Seq - udpClientLastSeq;

				if (Missed > 100)			// probably stopped in debug
					Missed = 1;

				while (--Missed)
				{
					droppedPackets++;

					// insert silence to maintain timing

					unsigned char * pkt = (unsigned char *)malloc(1024);

					memset(pkt, 0, 1024);

					mutex.lock();
					queue.append(pkt);
					mutex.unlock();

				}
			}

			udpClientLastSeq = Seq;

			unsigned char * pkt = (unsigned char *)malloc(1024);

			memcpy(pkt, &copy[16], 1024);

			mutex.lock();
			queue.append(pkt);
			mutex.unlock();
		}
	}
}

void  mynet::socketError()
{
	char errMsg[80];
	sprintf(errMsg, "%d %s", udpSocket->state(), udpSocket->errorString().toLocal8Bit().constData());
	//	qDebug() << errMsg;
	//	QMessageBox::question(NULL, "ARDOP GUI", errMsg, QMessageBox::Yes | QMessageBox::No);
}

extern "C" void sendSamplestoStdout(short * Samples, int nSamples)
{

}


extern "C" void sendSamplestoUDP(short * Samples, int nSamples, int Port)
{
	if (udpSocket == nullptr)
		return;
	
	unsigned char txBuff[1048];

	memcpy(txBuff, &udpServerSeqno, sizeof(udpServerSeqno));
	udpServerSeqno++;

	if (nSamples > 512)
		nSamples = 512;

	nSamples <<= 1;				// short to byte

	memcpy(&txBuff[16], Samples, nSamples);

	udpSocket->writeDatagram((char *)txBuff, nSamples + 16, QHostAddress(UDPHost), Port);
}

static int min = 0, max = 0, lastlevelGUI = 0, lastlevelreport = 0;

static UCHAR CurrentLevel = 0;		// Peak from current samples

extern "C" int SoundIsPlaying;
extern "C" short * SendtoCard(short * buf, int n);
extern "C" short * DMABuffer;


extern "C" void ProcessNewSamples(short * Samples, int nSamples);


extern "C" void UDPPollReceivedSamples()
{
	if (queue.isEmpty())
		return;

	short * ptr;
	short * save;

	// If we are using UDP for output (sound server) send samples to sound card.
	// If for input (virtual sound card) then pass to modem

	if (UDPServ)
	{
		// We only get packets if TX active (sound VOX) so raise PTT and start sending
		// ?? do we ignore if local modem is already sending ??

		if (SoundIsPlaying)
		{
			mutex.lock();
			save = ptr = (short *)queue.dequeue();
			mutex.unlock();
			free(save);
		}
		
		if (UDPSoundIsPlaying == 0)
		{
			// Wait for a couple of packets to reduce risk of underrun (but not too many or delay will be excessive

			if (queue.count() < 3)
				return;

			UDPSoundIsPlaying = 1;
			Debugprintf("PTT On");
			RadioPTT(0, 1);				// UDP only use one channel

			/// !! how do we drop ptt ??
		}

		while (queue.count() > 1)
		{
			short * outptr = DMABuffer;
			boolean dropPTT1 = 1;
			boolean dropPTT2 = 1;

			// We get mono samples but soundcard expects stereo
			// Sound card needs 1024 samples so send two packets

			mutex.lock();
			save = ptr = (short *)queue.dequeue();
			mutex.unlock();

			for (int n = 0; n < 512; n++)
			{
				*(outptr++) = *ptr;
				*(outptr++) = *ptr++;	// Duplicate
				if (*ptr)
					dropPTT1 = 0;		// Drop PTT if all samples zero
			}

			free(save);

			mutex.lock();
			save = ptr = (short *)queue.dequeue();
			mutex.unlock();

			for (int n = 0; n < 512; n++)
			{
				*(outptr++) = *ptr;
				*(outptr++) = *ptr++;	// Duplicate
				if (*ptr)
					dropPTT2 = 0;		// Drop PTT if all samples zero
			}

			free(save);

			if (dropPTT1 && dropPTT2)
			{
				startTimer(1);			// All zeros so no need to send
				return;
			}

			DMABuffer = SendtoCard(DMABuffer, 1024);

			if (dropPTT2)				// 2nd all zeros
				startTimer(1);	
		}
		return;
	}

	mutex.lock();
	save = ptr = (short *)queue.dequeue();
	mutex.unlock();

	// We get mono samples but modem expects stereo

	short Buff[2048];
	short * inptr = (short *)ptr;
	short * outptr = Buff;

	int i;

	for (i = 0; i < ReceiveSize; i++)
	{
		if (*(ptr) < min)
			min = *ptr;
		else if (*(ptr) > max)
			max = *ptr;
		ptr++;
	}

	CurrentLevel = ((max - min) * 75) / 32768;	// Scale to 150 max

	if ((Now - lastlevelGUI) > 2000)	// 2 Secs
	{
		lastlevelGUI = Now;

		if ((Now - lastlevelreport) > 10000)	// 10 Secs
		{
			char HostCmd[64];
			lastlevelreport = Now;

			sprintf(HostCmd, "INPUTPEAKS %d %d", min, max);
			Debugprintf("Input peaks = %d, %d", min, max);
		}
		
		min = max = 0;
	}

	for (int n = 0; n < 512; n++)
	{
		*(outptr++) = *inptr;
		*(outptr++) = *inptr++;	// Duplicate
	}

	ProcessNewSamples(Buff, 512);
	free(save);
}