#include "stdafx.h"
#include "OscServer.h"

#include <ws2tcpip.h>

#include <array>
#include <cstring>
#include <iomanip>
#include <sstream>
#include <vector>

#pragma comment(lib, "Ws2_32.lib")

namespace
{
bool InitializeWinsock(std::string& error)
{
	WSADATA wsaData = {};
	const int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (result != 0)
	{
		error = "WSAStartup failed.";
		return false;
	}
	return true;
}

std::vector<std::string> SplitAddress(const std::string& address)
{
	std::vector<std::string> parts;
	std::size_t start = !address.empty() && address[0] == '/' ? 1 : 0;

	while (start <= address.size())
	{
		const std::size_t slash = address.find('/', start);
		const std::size_t end = slash == std::string::npos ? address.size() : slash;
		if (end > start)
			parts.push_back(address.substr(start, end - start));
		if (slash == std::string::npos)
			break;
		start = slash + 1;
	}
	return parts;
}

}

OscServer::OscServer()
	: mPort(0), mRunning(false)
{
}

OscServer::~OscServer()
{
	Stop();
}

bool OscServer::Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error)
{
	if (port == 0)
		return true;

	mCallbacks = callbacks;
	mPort = port;

	if (!InitializeWinsock(error))
		return false;

	mSocket.reset(socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
	if (!mSocket.valid())
	{
		error = "Could not create OSC UDP socket.";
		return false;
	}

	DWORD timeoutMilliseconds = 100;
	setsockopt(mSocket.get(), SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeoutMilliseconds), sizeof(timeoutMilliseconds));

	sockaddr_in address = {};
	address.sin_family = AF_INET;
	if (!TryParseBindAddress(bindAddress, address.sin_addr, error))
	{
		mSocket.reset();
		return false;
	}
	address.sin_port = htons(static_cast<u_short>(port));
	if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
	{
		error = "Could not bind OSC listener to " + bindAddress + ":" + std::to_string(port) + ".";
		mSocket.reset();
		return false;
	}

	mRunning = true;
	mThread = std::thread(&OscServer::ServerLoop, this);
	return true;
}

bool OscServer::TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
{
	if (bindAddress.empty())
	{
		error = "OSC bind address must not be empty.";
		return false;
	}

	address = {};
	if (InetPtonA(AF_INET, bindAddress.c_str(), &address) != 1)
	{
		error = "Invalid OSC bind address '" + bindAddress + "'. Use an IPv4 address such as 127.0.0.1 or 0.0.0.0.";
		return false;
	}

	return true;
}

void OscServer::Stop()
{
	mRunning = false;
	mSocket.reset();
	if (mThread.joinable())
		mThread.join();
}

void OscServer::ServerLoop()
{
	std::array<char, 4096> buffer = {};
	while (mRunning)
	{
		sockaddr_in sender = {};
		int senderLength = sizeof(sender);
		const int byteCount = recvfrom(mSocket.get(), buffer.data(), static_cast<int>(buffer.size()), 0,
			reinterpret_cast<sockaddr*>(&sender), &senderLength);
		if (byteCount <= 0)
			continue;

		OscMessage message;
		std::string error;
		if (DecodeMessage(buffer.data(), byteCount, message, error))
		{
			if (!DispatchMessage(message, error) && !error.empty())
				OutputDebugStringA(("OSC dispatch failed: " + error + "\n").c_str());
		}
		else if (!error.empty())
		{
			OutputDebugStringA(("OSC decode failed: " + error + "\n").c_str());
		}
	}
}

bool OscServer::DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const
{
	int offset = 0;
	if (!ReadPaddedString(data, byteCount, offset, message.address) || message.address.empty() || message.address[0] != '/')
	{
		error = "Invalid OSC address.";
		return false;
	}

	std::string typeTags;
	if (!ReadPaddedString(data, byteCount, offset, typeTags) || typeTags.empty() || typeTags[0] != ',')
	{
		error = "Invalid OSC type tag string.";
		return false;
	}

	if (typeTags.size() < 2)
	{
		error = "OSC message has no parameter value.";
		return false;
	}

	std::vector<std::string> values;
	for (std::size_t index = 1; index < typeTags.size(); ++index)
	{
		std::string valueJson;
		if (!DecodeArgument(data, byteCount, offset, typeTags[index], valueJson))
		{
			error = "Unsupported or malformed OSC value type.";
			return false;
		}
		values.push_back(valueJson);
	}

	if (values.size() == 1)
	{
		message.valueJson = values.front();
		return true;
	}

	std::ostringstream arrayJson;
	arrayJson << "[";
	for (std::size_t index = 0; index < values.size(); ++index)
	{
		if (index > 0)
			arrayJson << ",";
		arrayJson << values[index];
	}
	arrayJson << "]";
	message.valueJson = arrayJson.str();
	return true;
}

bool OscServer::DispatchMessage(const OscMessage& message, std::string& error) const
{
	const std::vector<std::string> parts = SplitAddress(message.address);
	if (parts.size() != 3 || parts[0] != "VideoShaderToys")
	{
		error = "Unsupported OSC address: " + message.address;
		return false;
	}

	return mCallbacks.updateParameter &&
		mCallbacks.updateParameter(parts[1], parts[2], message.valueJson, error);
}

bool OscServer::DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson)
{
	if (valueType == 'f')
	{
		double value = 0.0;
		if (!ReadFloat32(data, byteCount, offset, value))
			return false;
		std::ostringstream stream;
		stream << std::setprecision(9) << value;
		valueJson = stream.str();
		return true;
	}

	if (valueType == 'd')
	{
		double value = 0.0;
		if (!ReadFloat64(data, byteCount, offset, value))
			return false;
		std::ostringstream stream;
		stream << std::setprecision(17) << value;
		valueJson = stream.str();
		return true;
	}

	if (valueType == 'i')
	{
		int value = 0;
		if (!ReadInt32(data, byteCount, offset, value))
			return false;
		valueJson = std::to_string(value);
		return true;
	}

	if (valueType == 's')
	{
		std::string value;
		if (!ReadPaddedString(data, byteCount, offset, value))
			return false;
		valueJson = BuildJsonString(value);
		return true;
	}

	if (valueType == 'T' || valueType == 'F')
	{
		valueJson = valueType == 'T' ? "true" : "false";
		return true;
	}

	return false;
}

bool OscServer::ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value)
{
	if (offset < 0 || offset >= byteCount)
		return false;

	const int start = offset;
	while (offset < byteCount && data[offset] != '\0')
		++offset;
	if (offset >= byteCount)
		return false;

	value.assign(data + start, data + offset);
	++offset;
	while (offset % 4 != 0)
		++offset;
	return offset <= byteCount;
}

bool OscServer::ReadInt32(const char* data, int byteCount, int& offset, int& value)
{
	if (offset + 4 > byteCount)
		return false;
	const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data + offset);
	value = static_cast<int>((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]);
	offset += 4;
	return true;
}

bool OscServer::ReadFloat32(const char* data, int byteCount, int& offset, double& value)
{
	int bits = 0;
	if (!ReadInt32(data, byteCount, offset, bits))
		return false;

	float floatValue = 0.0f;
	const unsigned int unsignedBits = static_cast<unsigned int>(bits);
	std::memcpy(&floatValue, &unsignedBits, sizeof(floatValue));
	value = static_cast<double>(floatValue);
	return true;
}

bool OscServer::ReadFloat64(const char* data, int byteCount, int& offset, double& value)
{
	if (offset + 8 > byteCount)
		return false;

	const unsigned char* bytes = reinterpret_cast<const unsigned char*>(data + offset);
	uint64_t bits = 0;
	for (int index = 0; index < 8; ++index)
		bits = (bits << 8) | static_cast<uint64_t>(bytes[index]);

	std::memcpy(&value, &bits, sizeof(value));
	offset += 8;
	return true;
}

std::string OscServer::BuildJsonString(const std::string& value)
{
	std::ostringstream stream;
	stream << '"';
	for (char ch : value)
	{
		if (ch == '"' || ch == '\\')
			stream << '\\';
		stream << ch;
	}
	stream << '"';
	return stream.str();
}
