#include "OscServer.h"

#include <cstring>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

namespace
{
int gFailures = 0;

void Expect(bool condition, const char* message)
{
	if (condition)
		return;

	std::cerr << "FAIL: " << message << "\n";
	++gFailures;
}

void AppendPaddedString(std::vector<char>& packet, const std::string& text)
{
	packet.insert(packet.end(), text.begin(), text.end());
	packet.push_back('\0');
	while (packet.size() % 4 != 0)
		packet.push_back('\0');
}

void AppendInt32(std::vector<char>& packet, int value)
{
	const unsigned int bits = static_cast<unsigned int>(value);
	packet.push_back(static_cast<char>((bits >> 24) & 0xff));
	packet.push_back(static_cast<char>((bits >> 16) & 0xff));
	packet.push_back(static_cast<char>((bits >> 8) & 0xff));
	packet.push_back(static_cast<char>(bits & 0xff));
}

void AppendFloat32(std::vector<char>& packet, float value)
{
	unsigned int bits = 0;
	std::memcpy(&bits, &value, sizeof(bits));
	AppendInt32(packet, static_cast<int>(bits));
}

void AppendFloat64(std::vector<char>& packet, double value)
{
	uint64_t bits = 0;
	std::memcpy(&bits, &value, sizeof(bits));
	for (int shift = 56; shift >= 0; shift -= 8)
		packet.push_back(static_cast<char>((bits >> shift) & 0xff));
}

std::vector<char> BuildOscPacket(const std::string& address, const std::string& typeTags)
{
	std::vector<char> packet;
	AppendPaddedString(packet, address);
	AppendPaddedString(packet, typeTags);
	return packet;
}
}

struct OscServerTestAccess
{
	using Message = OscServer::OscMessage;

	static bool Decode(OscServer& server, const std::vector<char>& packet, Message& message, std::string& error)
	{
		return server.DecodeMessage(packet.data(), static_cast<int>(packet.size()), message, error);
	}

	static bool Dispatch(OscServer& server, const Message& message, std::string& error)
	{
		return server.DispatchMessage(message, error);
	}

	static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
	{
		return OscServer::TryParseBindAddress(bindAddress, address, error);
	}

	static void SetUpdateParameterCallback(
		OscServer& server,
		const std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)>& callback)
	{
		server.mCallbacks.updateParameter = callback;
	}
};

namespace
{
void TestDecodeFloatMessage()
{
	OscServer server;
	std::vector<char> packet = BuildOscPacket("/VideoShaderToys/VHS/intensity", ",f");
	AppendFloat32(packet, 0.75f);

	OscServerTestAccess::Message message;
	std::string error;
	Expect(OscServerTestAccess::Decode(server, packet, message, error), "float OSC message decodes");
	Expect(message.address == "/VideoShaderToys/VHS/intensity", "float OSC address is preserved");
	Expect(message.valueJson.find("0.75") == 0, "float OSC value becomes JSON number");
}

void TestDecodeDoubleMessage()
{
	OscServer server;
	std::vector<char> packet = BuildOscPacket("/VideoShaderToys/fisheye-reproject/panDegrees", ",d");
	AppendFloat64(packet, 51.5);

	OscServerTestAccess::Message message;
	std::string error;
	Expect(OscServerTestAccess::Decode(server, packet, message, error), "double OSC message decodes");
	Expect(message.address == "/VideoShaderToys/fisheye-reproject/panDegrees", "double OSC address is preserved");
	Expect(message.valueJson.find("51.5") == 0, "double OSC value becomes JSON number");
}

void TestDecodeVectorMessage()
{
	OscServer server;
	std::vector<char> packet = BuildOscPacket("/VideoShaderToys/video-transform/pan", ",ff");
	AppendFloat32(packet, 0.25f);
	AppendFloat32(packet, -0.5f);

	OscServerTestAccess::Message message;
	std::string error;
	Expect(OscServerTestAccess::Decode(server, packet, message, error), "multi-float OSC message decodes");
	Expect(message.address == "/VideoShaderToys/video-transform/pan", "multi-float OSC address is preserved");
	Expect(message.valueJson.find("[0.25,-0.5") == 0, "multi-float OSC value becomes JSON array");
}

void TestDecodeIntStringAndBoolMessages()
{
	OscServer server;

	std::vector<char> intPacket = BuildOscPacket("/VideoShaderToys/layer-1/mode", ",i");
	AppendInt32(intPacket, 3);
	OscServerTestAccess::Message intMessage;
	std::string error;
	Expect(OscServerTestAccess::Decode(server, intPacket, intMessage, error), "int OSC message decodes");
	Expect(intMessage.valueJson == "3", "int OSC value becomes JSON number");

	std::vector<char> stringPacket = BuildOscPacket("/VideoShaderToys/layer-1/mode", ",s");
	AppendPaddedString(stringPacket, "equisolid");
	OscServerTestAccess::Message stringMessage;
	error.clear();
	Expect(OscServerTestAccess::Decode(server, stringPacket, stringMessage, error), "string OSC message decodes");
	Expect(stringMessage.valueJson == "\"equisolid\"", "string OSC value becomes JSON string");

	std::vector<char> boolPacket = BuildOscPacket("/VideoShaderToys/layer-1/enabled", ",T");
	OscServerTestAccess::Message boolMessage;
	error.clear();
	Expect(OscServerTestAccess::Decode(server, boolPacket, boolMessage, error), "boolean OSC message decodes");
	Expect(boolMessage.valueJson == "true", "true OSC typetag becomes JSON boolean");
}

void TestDispatchValidAddress()
{
	OscServer server;
	std::string layerKey;
	std::string parameterKey;
	std::string valueJson;
	OscServerTestAccess::SetUpdateParameterCallback(server, [&](const std::string& layer, const std::string& parameter, const std::string& value, std::string&)
	{
		layerKey = layer;
		parameterKey = parameter;
		valueJson = value;
		return true;
	});

	OscServerTestAccess::Message message;
	message.address = "/VideoShaderToys/VHS/intensity";
	message.valueJson = "0.5";
	std::string error;
	Expect(OscServerTestAccess::Dispatch(server, message, error), "valid OSC control address dispatches");
	Expect(layerKey == "VHS", "dispatch extracts layer key");
	Expect(parameterKey == "intensity", "dispatch extracts parameter key");
	Expect(valueJson == "0.5", "dispatch forwards JSON value");
}

void TestRejectsUnsupportedAddress()
{
	OscServer server;
	bool called = false;
	OscServerTestAccess::SetUpdateParameterCallback(server, [&](const std::string&, const std::string&, const std::string&, std::string&)
	{
		called = true;
		return true;
	});

	OscServerTestAccess::Message message;
	message.address = "/OtherApp/VHS/intensity";
	message.valueJson = "0.5";
	std::string error;
	Expect(!OscServerTestAccess::Dispatch(server, message, error), "unsupported OSC namespace is rejected");
	Expect(!called, "unsupported OSC namespace does not invoke callback");
	Expect(!error.empty(), "unsupported OSC address reports an error");
}

void TestParsesOscBindAddress()
{
	in_addr loopback = {};
	std::string error;
	Expect(OscServerTestAccess::TryParseBindAddress("127.0.0.1", loopback, error), "loopback OSC bind address parses");
	Expect(loopback.S_un.S_addr != 0, "loopback OSC bind address produces a socket address");

	in_addr wildcard = {};
	error.clear();
	Expect(OscServerTestAccess::TryParseBindAddress("0.0.0.0", wildcard, error), "wildcard OSC bind address parses");

	in_addr invalid = {};
	error.clear();
	Expect(!OscServerTestAccess::TryParseBindAddress("localhost", invalid, error), "hostname OSC bind address is rejected");
	Expect(!error.empty(), "invalid OSC bind address reports an error");
}
}

int main()
{
	TestDecodeFloatMessage();
	TestDecodeDoubleMessage();
	TestDecodeVectorMessage();
	TestDecodeIntStringAndBoolMessages();
	TestDispatchValidAddress();
	TestRejectsUnsupportedAddress();
	TestParsesOscBindAddress();

	if (gFailures != 0)
	{
		std::cerr << gFailures << " OscServer test failure(s).\n";
		return 1;
	}

	std::cout << "OscServer tests passed.\n";
	return 0;
}
