#define _WIN32_WINNT _WIN32_WINNT_WIN8
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "OS2L.h"

//////////////////////////////////////////////////////////////////////////

void OS2L::onButton(const char *name, const char *page, bool state)
{
	// to implement

	// NOTE: you might want to call sendFeedback(name, page, onOrOff) to keep the buttons on in the audio software after it has been released
}

void OS2L::onCommand(int cmdid, double value)
{
	// to implement
}

void OS2L::onBeat(double bpm, int pos, bool change, double strength)
{
	// to implement
}

//////////////////////////////////////////////////////////////////////////

#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")

#include "dns_sd.h"
#pragma comment(lib, "dnssd.lib")

#include <string>
#include <map>

#define OS2L_PORT_MIN	8010
#define OS2L_PORT_MAX	8060

OS2L::OS2L()
{
	bonjourRef = NULL;
	bonjourSock = 0;
	connectionSock = 0;
	
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
}

bool OS2L::init()
{
	if (bonjourSock)
		return true;
	bonjourSock = socket(AF_INET, SOCK_STREAM, 0);
	if (bonjourSock == INVALID_SOCKET)
	{
		bonjourSock = 0;
		return false;
	}
	SOCKADDR_IN saddr;
	memset(&saddr, 0, sizeof(saddr));
	saddr.sin_family = AF_INET;
	((LPIN_ADDR)&saddr.sin_addr)->s_addr = INADDR_ANY;
	int port = OS2L_PORT_MIN;
	while (1)
	{
		saddr.sin_port = htons((u_short)port);
		if (bind(bonjourSock, (sockaddr*)&saddr, sizeof(saddr)) >= 0)
			break;
		port++;
		if (port > OS2L_PORT_MAX)
			return false;
	}
	listen(bonjourSock, 2);
	CreateThread(NULL, 0, &staticThread, this, 0, NULL);
	if (DNSServiceRegister((DNSServiceRef*)&bonjourRef, 0, 0, NULL, "_os2l._tcp.", NULL, NULL, htons((u_short)port), 0, NULL, NULL, NULL) != kDNSServiceErr_NoError)
	{
		// Bonjour is not installed.
		// You should ask the user to go to http://support.apple.com/kb/DL999 and download and install Bonjour
		return false; 
	}
	return true;
}

void OS2L::close()
{
	SOCKET bs = bonjourSock;
	if (bs)
	{
		bonjourSock = 0;
		closesocket(bs);
	}
	if (connectionSock)
	{
		closesocket(connectionSock);
		connectionSock = 0;
	}
	if (bonjourRef)
	{
		DNSServiceRefDeallocate((DNSServiceRef)bonjourRef);
		bonjourRef = NULL;
	}
}

std::string jsonEscape(const std::string &src)
{
	size_t len = src.length();
	std::string dst;
	dst.reserve(len);
	size_t last = 0;
	size_t pos;
	for (pos = 0; pos < len; pos++)
	{
		unsigned char c = (unsigned char)src[pos];
		if (c == '"' || c == '\\' || c < ' ')
		{
			if (last < pos)
			{
				dst.insert(dst.end(), src.begin() + last, src.begin() + pos);
				last = pos;
			}
			dst.push_back('\\');
			if (c < ' ')
			{
				char st[8];
				sprintf_s(st, 7, "u%.4X", c);
				dst.append(st);
				++last;
			}
		}
	}
	if (last < pos)
		dst.insert(dst.end(), src.begin() + last, src.begin() + pos);
	return dst;
}

std::string jsonUnescape(const char *&src)
{
	std::string dst;
	size_t last = 0;
	size_t pos = 0;
	while (1)
	{
		char c = src[pos];
		if (c && c != '\\' && c != '"')
		{
			pos++;
			continue;
		}
		if (pos > last)
			dst.append(src + last, pos - last);
		if (c != '\\')
		{
			src += pos;
			return dst;
		}
		pos++;
		c = src[pos++];
		if (c == '\\' || c == '"')
			dst.push_back(c);
		else if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'))
		{
			unsigned int v;
			if (c >= '0' && c <= '9')
				v = c - '0';
			else
				v = c - 'A' + 10;
			for (size_t i = 1; i < 4; i++)
			{
				v *= 16;
				c = src[pos];
				if (c >= '0' && c <= '9')
					v += c - '0';
				else if (c >= 'A' && c <= 'F')
					v += c - 'A' + 10;
				else
					break;
				pos++;
			}
			if (v <= 0x007F)
			{
				dst.push_back(v);
			}
			else if (v <= 0x07FF)
			{
				dst.push_back(v / 64 + 192);
				dst.push_back(v % 64 + 128);
			}
			else if ((0x0800 <= v && v <= 0xD7FF) || (0xE000 <= v && v <= 0xFFFF))
			{
				dst.push_back(v / 4096 + 224);
				dst.push_back((v % 4096) / 64 + 128);
				dst.push_back(v % 64 + 128);
			}
			else
			{
				dst.push_back(v / 262144 + 240);
				dst.push_back((v % 262144) / 4096 + 128);
				dst.push_back((v % 4096) / 64 + 128);
				dst.push_back(v % 64 + 128);
			}
		}
		else
		{
			static const char *delims = "rnbtf\r\n\b\t\f";
			const char *ptr = strchr(delims, c);
			if (ptr && ptr < delims + 5)
				dst.push_back(ptr[5]);
			else
			{
				dst.push_back('\\');
				dst.push_back(c);
			}
		}
		last = pos;
	}

	return dst;
}

bool skipSpaces(const char *&buffer)
{
	static char *spaces = " \r\n\t";
	while (strchr(spaces, *buffer))
		buffer++;
	return *buffer != 0;
}

bool parseJSON(const char *&buffer, std::map<std::string, std::string> &json)
{
	if (!*buffer)
		return false;
	if (!skipSpaces(buffer))
		return false;
	if (*(buffer++) != '{')
		return false;
	json.clear();
	while (1)
	{
		if (!skipSpaces(buffer))
			return false;
		if (*(buffer++) != '"')
			return false;
		std::string name = jsonUnescape(buffer);
		if (*(buffer++) != '"')
			return false;
		if (!skipSpaces(buffer))
			return false;
		if (*(buffer++) != ':')
			return false;
		if (!skipSpaces(buffer))
			return false;
		if (*buffer == '"')
		{
			buffer++;
			json[name] = jsonUnescape(buffer);
			if (*(buffer++) != '"')
				return false;
		}
		else if (strncmp(buffer, "true", 4) == 0)
		{
			json[name] = "true";
			buffer += 4;
		}
		else if (strncmp(buffer, "false", 5) == 0)
		{
			json[name] = "false";
			buffer += 5;
		}
		else if (strncmp(buffer, "null", 5) == 0)
		{
			json[name] = "null";
			buffer += 5;
		}
		else
		{
			size_t len = 0;
			while ((buffer[len] >= '0' && buffer[len] <= '9') || buffer[len] == '.' || (len == 0 && buffer[len] == '-'))
				len++;
			if (!len)
				return false;
			json[name] = std::string(buffer, len);
			buffer += len;
		}
		if (!skipSpaces(buffer))
			return false;
		if (*buffer == '}')
		{
			buffer++;
			return true;
		}
		if (*(buffer++) != ',')
			return false;
	}
}

DWORD WINAPI OS2L::staticThread(LPVOID lpParameter)
{
	return ((OS2L*)lpParameter)->thread();
}

DWORD OS2L::thread()
{
	while (bonjourSock)
	{
		fd_set fds;
		FD_ZERO(&fds);
		if (connectionSock)
			FD_SET(connectionSock, &fds);
		else
			FD_SET(bonjourSock, &fds);
		timeval timeout = { 0,200000 };
		int s = select(1, &fds, NULL, NULL, &timeout);
		if (!bonjourSock)
			break;
		if (s <= 0)
			continue;
		if (!connectionSock)
		{
			connectionSock = accept(bonjourSock, NULL, 0);
			if (connectionSock == INVALID_SOCKET)
				connectionSock = 0;
			continue;
		}
		char buffer[4096];
		int l = recv(connectionSock, buffer, 4095, 0);
		if (l <= 0)
		{
			closesocket(connectionSock);
			connectionSock = 0;
			continue;
		}
		buffer[l] = 0;
		const char *ptr = buffer;
		std::map<std::string, std::string> json;
		while (parseJSON(ptr, json))
		{
			if (json["evt"] == "beat")
				onBeat(atof(json["bpm"].c_str()), atoi(json["pos"].c_str()), json["change"] == "true", atof(json["strength"].c_str()));
			else if (json["evt"] == "cmd")
				onCommand(atoi(json["id"].c_str()), atof(json["param"].c_str()));
			else if (json["evt"] == "btn")
				onButton(json["name"].c_str(), json["page"].c_str(), json["state"] != "off");
		}
	}
	return 0;
}

void OS2L::sendFeedback(const char *name, const char *page, bool state)
{
	if (!connectionSock || !name)
		return;
	char buffer[4096];
	std::string _name = jsonEscape(name);
	if (!page || !*page)
		sprintf_s(buffer, 4095, "{\"evt\":\"feedback\",\"name\":\"%s\",\"state\":\"%s\"}", _name.c_str(), state ? "on" : "off");
	else
	{
		std::string _page = jsonEscape(page);
		sprintf_s(buffer, 4095, "{\"evt\":\"feedback\",\"name\":\"%s\",\"page\":\"%s\",\"state\":\"%s\"}", _name.c_str(), _page.c_str(), state ? "on" : "off");
	}
	send(connectionSock, buffer, strlen(buffer), 0);
}

void OS2L::sendFeedback(int cmdid, double value)
{
	if (!connectionSock)
		return;
	char buffer[4096];
	sprintf_s(buffer, 4095, "{\"evt\":\"feedback\",\"id\":%u,\"param\":%.2f}", cmdid, value);
	send(connectionSock, buffer, strlen(buffer), 0);
}
