#ifndef UDPBASE_H
#define UDPBASE_H

#include <QObject>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QHostInfo>
#include <QTimer>
#include <QMutex>
#include <QDateTime>
#include <QByteArray>
#include <QVector>
#include <QMap>
#include <QUuid>

// Allow easy endian-ness conversions
#include <QtEndian>

// Needed for audio
#include <QBuffer>
#include <QThread>

#include <QDebug>

#include "packettypes.h"

struct udpPreferences {
	QString ipAddress;
	quint16 controlLANPort;
	quint16 serialLANPort;
	quint16 audioLANPort;
	QString username;
	QString password;
	QString clientName;
	quint8 waterfallFormat;
	bool halfDuplex;
};

struct networkAudioLevels {
    bool haveTxLevels = false;
    bool haveRxLevels = false;
    quint8 rxAudioRMS = 0;
    quint8 txAudioRMS = 0;
    quint8 rxAudioPeak = 0;
    quint8 txAudioPeak = 0;
};

struct networkStatus {
	quint8 rxAudioBufferPercent;
	quint8 txAudioBufferPercent;
	quint8 rxAudioLevel;
	quint8 txAudioLevel;
	quint16 rxLatency;
	quint16 txLatency;
	bool rxUnderrun;
	bool txUnderrun;
	bool rxOverrun;
	bool txOverrun;
	quint16 rxCurrentLatency;
	quint16 txCurrentLatency;
	quint32 packetsSent = 0;
	quint32 packetsLost = 0;
	quint16 rtt = 0;
	quint32 networkLatency = 0;
    qint64 timeDifference = 0;
	QString message;
};


// Parent class that contains all common items.
class udpBase : public QObject
{


public:
	~udpBase();

	void init(quint16 local);

	void reconnect();

	void dataReceived(QByteArray r);
	void sendPing();
	void sendRetransmitRange(quint16 first, quint16 second, quint16 third, quint16 fourth);

	void sendControl(bool tracked, quint8 id, quint16 seq);

	void printHex(const QByteArray& pdata);
	void printHex(const QByteArray& pdata, bool printVert, bool printHoriz);

    qint64 getTimeDifference() { return timeDifference;}

	//QTime timeStarted;

	QUdpSocket* udp = Q_NULLPTR;
	uint32_t myId = 0;
	uint32_t remoteId = 0;
	uint16_t authSeq = 0x30;
	uint16_t sendSeqB = 0;
	uint16_t sendSeq = 1;
	uint16_t lastReceivedSeq = 1;
	uint16_t pkt0SendSeq = 0;
	uint16_t periodicSeq = 0;
	quint64 latency = 0;

	QString username = "";
	QString password = "";
	QHostAddress radioIP;
	QHostAddress localIP;
	bool isAuthenticated = false;
	quint16 localPort = 0;
	quint16 port = 0;
	bool periodicRunning = false;
	bool sentPacketConnect2 = false;
	QTime	lastReceived = QTime::currentTime();
	QMutex udpMutex;
	QMutex txBufferMutex;
	QMutex rxBufferMutex;
	QMutex missingMutex;

	struct SEQBUFENTRY {
		QTime	timeSent;
		uint16_t seqNum;
		QByteArray data;
		quint8 retransmitCount;
	};

	QMap<quint16, QTime> rxSeqBuf;
	QMap<quint16, SEQBUFENTRY> txSeqBuf;
	QMap<quint16, int> rxMissing;

	void sendTrackedPacket(QByteArray d);
	void purgeOldEntries();

	QTimer* areYouThereTimer = Q_NULLPTR; // Send are-you-there packets every second until a response is received.
	QTimer* pingTimer = Q_NULLPTR; // Start sending pings immediately.
	QTimer* idleTimer = Q_NULLPTR; // Start watchdog once we are connected.

	QTimer* watchdogTimer = Q_NULLPTR;
	QTimer* retransmitTimer = Q_NULLPTR;

	QDateTime lastPingSentTime;
	uint16_t pingSendSeq = 0;

	quint16 areYouThereCounter = 0;

	quint32 packetsSent = 0;
	quint32 packetsLost = 0;

	quint16 seqPrefix = 0;
	QString connectionType = "";
	int congestion = 0;

protected:
    QTime startTime;
    QTime radioTime;
    qint64 timeOffset;
    qint64 timeDifference = 0;


private:
	void sendRetransmitRequest();

};


/// <summary>
/// passcode function used to generate secure (ish) code
/// </summary>
/// <param name="str"></param>
/// <returns>pointer to encoded username or password</returns>
static inline void passcode(QString in, QByteArray& out)
{
	const quint8 sequence[] =
	{
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0x47,0x5d,0x4c,0x42,0x66,0x20,0x23,0x46,0x4e,0x57,0x45,0x3d,0x67,0x76,0x60,0x41,0x62,0x39,0x59,0x2d,0x68,0x7e,
		0x7c,0x65,0x7d,0x49,0x29,0x72,0x73,0x78,0x21,0x6e,0x5a,0x5e,0x4a,0x3e,0x71,0x2c,0x2a,0x54,0x3c,0x3a,0x63,0x4f,
		0x43,0x75,0x27,0x79,0x5b,0x35,0x70,0x48,0x6b,0x56,0x6f,0x34,0x32,0x6c,0x30,0x61,0x6d,0x7b,0x2f,0x4b,0x64,0x38,
		0x2b,0x2e,0x50,0x40,0x3f,0x55,0x33,0x37,0x25,0x77,0x24,0x26,0x74,0x6a,0x28,0x53,0x4d,0x69,0x22,0x5c,0x44,0x31,
		0x36,0x58,0x3b,0x7a,0x51,0x5f,0x52,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

	};

	QByteArray ba = in.toLocal8Bit();
	uchar* ascii = (uchar*)ba.constData();
	for (int i = 0; i < in.length() && i < 16; i++)
	{
		int p = ascii[i] + i;
		if (p > 126)
		{
			p = 32 + p % 127;
		}
		out.append(sequence[p]);
	}
	return;
}

/// <summary>
/// returns a QByteArray of a null terminated string
/// </summary>
/// <param name="c"></param>
/// <param name="s"></param>
/// <returns></returns>
static inline QByteArray parseNullTerminatedString(QByteArray c, int s)
{
	//QString res = "";
	QByteArray res;
	for (int i = s; i < c.length(); i++)
	{
		if (c[i] != '\0')
		{
			res.append(c[i]);
		}
		else
		{
			break;
		}
	}
	return res;
}

#endif
