#include "Std140Buffer.h"

#include <cstring>
#include <iostream>
#include <vector>

namespace
{
int gFailures = 0;

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

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

float ReadFloat(const std::vector<unsigned char>& buffer, std::size_t offset)
{
	float value = 0.0f;
	std::memcpy(&value, buffer.data() + offset, sizeof(float));
	return value;
}

int ReadInt(const std::vector<unsigned char>& buffer, std::size_t offset)
{
	int value = 0;
	std::memcpy(&value, buffer.data() + offset, sizeof(int));
	return value;
}

void TestScalarPacking()
{
	std::vector<unsigned char> buffer;
	AppendStd140Float(buffer, 1.25f);
	AppendStd140Int(buffer, 7);

	Expect(buffer.size() == 8, "scalar values pack tightly on 4-byte alignment");
	Expect(ReadFloat(buffer, 0) == 1.25f, "float value is written at offset 0");
	Expect(ReadInt(buffer, 4) == 7, "int value is written at offset 4");
}

void TestVectorAlignment()
{
	std::vector<unsigned char> buffer;
	AppendStd140Float(buffer, 1.0f);
	AppendStd140Vec2(buffer, 2.0f, 3.0f);
	AppendStd140Vec4(buffer, 4.0f, 5.0f, 6.0f, 7.0f);

	Expect(buffer.size() == 32, "vec2 aligns to 8 bytes and vec4 aligns to 16 bytes");
	Expect(ReadFloat(buffer, 8) == 2.0f, "vec2 x value is aligned");
	Expect(ReadFloat(buffer, 12) == 3.0f, "vec2 y value follows x");
	Expect(ReadFloat(buffer, 16) == 4.0f, "vec4 x value is aligned");
	Expect(ReadFloat(buffer, 28) == 7.0f, "vec4 w value is written");
}

void TestGlobalParamStylePacking()
{
	std::vector<unsigned char> buffer;
	AppendStd140Float(buffer, 10.0f);				// time
	AppendStd140Vec2(buffer, 1920.0f, 1080.0f);		// input resolution
	AppendStd140Vec2(buffer, 1280.0f, 720.0f);		// output resolution
	AppendStd140Float(buffer, 45296.0f);			// UTC time of day
	AppendStd140Float(buffer, 36000.0f);			// UTC offset
	AppendStd140Float(buffer, 0.75f);				// startup random
	AppendStd140Float(buffer, 42.0f);				// frame count
	AppendStd140Float(buffer, 0.5f);				// mix
	AppendStd140Float(buffer, 1.0f);				// bypass
	AppendStd140Int(buffer, 3);						// source history length
	AppendStd140Int(buffer, 2);						// temporal history length
	AppendStd140Float(buffer, 0.25f);				// float parameter
	AppendStd140Vec2(buffer, 4.0f, 5.0f);			// vec2 parameter
	AppendStd140Vec4(buffer, 0.1f, 0.2f, 0.3f, 1.0f); // color parameter
	AppendStd140Int(buffer, 1);						// boolean parameter
	AppendStd140Int(buffer, 2);						// enum parameter
	buffer.resize(AlignStd140(buffer.size(), 16), 0);

	Expect(buffer.size() == 112, "global parameter style block is padded to a 16-byte boundary");
	Expect(ReadFloat(buffer, 0) == 10.0f, "time is at the start of the block");
	Expect(ReadFloat(buffer, 8) == 1920.0f, "first vec2 aligns after scalar padding");
	Expect(ReadFloat(buffer, 16) == 1280.0f, "second vec2 follows first vec2");
	Expect(ReadFloat(buffer, 24) == 45296.0f, "UTC time follows output resolution");
	Expect(ReadFloat(buffer, 28) == 36000.0f, "UTC offset follows UTC time");
	Expect(ReadFloat(buffer, 32) == 0.75f, "startup random follows UTC offset");
	Expect(ReadInt(buffer, 48) == 3, "history length scalar remains tightly packed");
	Expect(ReadFloat(buffer, 64) == 4.0f, "vec2 shader parameter aligns to 8 bytes");
	Expect(ReadFloat(buffer, 80) == 0.1f, "color parameter aligns to 16 bytes");
	Expect(ReadInt(buffer, 96) == 1, "boolean parameter follows vec4");
	Expect(ReadInt(buffer, 100) == 2, "enum parameter follows boolean");
}
}

int main()
{
	TestScalarPacking();
	TestVectorAlignment();
	TestGlobalParamStylePacking();

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

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