#include "EggDuino.h"

#ifdef ESP32
#include <WebSocketsServer.h>

namespace
{
constexpr uint16_t kWifiProtocolPort = 1337;
constexpr uint8_t kInvalidWifiClientId = 0xFF;
constexpr size_t kWifiRxQueueSize = 1024;

struct WifiRxByte
{
	uint8_t value;
};

WebSocketsServer g_wifiProtocolSocket(kWifiProtocolPort);
WifiRxByte g_wifiRxQueue[kWifiRxQueueSize];
size_t g_wifiRxHead = 0;
size_t g_wifiRxTail = 0;
bool g_wifiRxOverflow = false;
bool g_wifiProtocolStarted = false;
uint8_t g_wifiProtocolClientId = kInvalidWifiClientId;

bool queueWifiByte(uint8_t value)
{
	const size_t nextHead = (g_wifiRxHead + 1) % kWifiRxQueueSize;
	if (nextHead == g_wifiRxTail)
	{
		g_wifiRxOverflow = true;
		return false;
	}

	g_wifiRxQueue[g_wifiRxHead].value = value;
	g_wifiRxHead = nextHead;
	return true;
}

bool dequeueWifiByte(WifiRxByte *byte)
{
	if ((byte == NULL) || (g_wifiRxHead == g_wifiRxTail))
	{
		return false;
	}

	*byte = g_wifiRxQueue[g_wifiRxTail];
	g_wifiRxTail = (g_wifiRxTail + 1) % kWifiRxQueueSize;
	return true;
}

void handleWifiSocketEvent(uint8_t clientId, WStype_t type, uint8_t *payload, size_t length)
{
	(void)payload;

	switch (type)
	{
	case WStype_CONNECTED:
		if (g_wifiProtocolClientId == kInvalidWifiClientId)
		{
			g_wifiProtocolClientId = clientId;
		}
		Log(String("WiFi protocol client connected: ") + clientId);
		break;

	case WStype_DISCONNECTED:
		if (g_wifiProtocolClientId == clientId)
		{
			g_wifiProtocolClientId = kInvalidWifiClientId;
		}
		Log(String("WiFi protocol client disconnected: ") + clientId);
		break;

	case WStype_TEXT:
	case WStype_BIN:
		if (g_wifiProtocolClientId == kInvalidWifiClientId)
		{
			g_wifiProtocolClientId = clientId;
		}
		if (clientId != g_wifiProtocolClientId)
		{
			break;
		}
		for (size_t i = 0; i < length; ++i)
		{
			queueWifiByte(payload[i]);
		}
		break;

	default:
		break;
	}
}
} // namespace

void startWifiProtocolInterface()
{
	if (g_wifiProtocolStarted)
	{
		return;
	}

	if (WiFi.status() != WL_CONNECTED)
	{
		return;
	}

	g_wifiProtocolSocket.begin();
	g_wifiProtocolSocket.onEvent(handleWifiSocketEvent);
	g_wifiProtocolStarted = true;

	Log(String("WiFi EggBot protocol ws://") + WiFi.localIP().toString() + ":" + kWifiProtocolPort);
}

void handleWifiProtocolInterface()
{
	if (!g_wifiProtocolStarted)
	{
		if (WiFi.status() == WL_CONNECTED)
		{
			startWifiProtocolInterface();
		}
		return;
	}

	g_wifiProtocolSocket.loop();

	if (g_wifiRxOverflow)
	{
		g_wifiRxOverflow = false;
		Log("WiFi protocol RX queue overflow");
	}

	WifiRxByte byte = {0};
	while (dequeueWifiByte(&byte))
	{
		setActiveProtocolContext(&g_WifiCmd, PROTOCOL_TRANSPORT_WIFI);
		g_WifiCmd.readChar(static_cast<char>(byte.value));
	}
}

bool wifiProtocolWrite(const char *message)
{
	if (!g_wifiProtocolStarted || (message == NULL))
	{
		return false;
	}

	if ((g_wifiProtocolClientId == kInvalidWifiClientId) || !g_wifiProtocolSocket.clientIsConnected(g_wifiProtocolClientId))
	{
		return false;
	}

	return g_wifiProtocolSocket.sendTXT(g_wifiProtocolClientId, message);
}

#endif
