/*
 * Copyright 2015 - 2025 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#undef NDEBUG

#include <map>
#include <ox/claw/format.hpp>
#include <ox/claw/read.hpp>
#include <ox/claw/write.hpp>
#include <ox/mc/mc.hpp>
#include <ox/model/model.hpp>
#include <ox/std/std.hpp>

union TestUnion {
	static constexpr auto TypeName = "TestUnion";
	static constexpr auto TypeVersion = 1;
	bool Bool;
	uint32_t Int = 5;
	char *String;
};

struct TestStructNest {
	static constexpr auto TypeName = "TestStructNest";
	static constexpr auto TypeVersion = 1;
	bool Bool = false;
	uint32_t Int = 0;
	ox::IString<32> String = "";
};

struct TestStruct {
	static constexpr auto TypeName = "TestStruct";
	static constexpr auto TypeVersion = 1;
	bool Bool = false;
	int32_t Int = 0;
	int32_t Int1 = 0;
	int32_t Int2 = 0;
	int32_t Int3 = 0;
	int32_t Int4 = 0;
	int32_t Int5 = 0;
	int32_t Int6 = 0;
	int32_t Int7 = 0;
	int32_t Int8 = 0;
	int unionIdx = 1;
	TestUnion Union;
	ox::IString<32> String = "";
	uint32_t List[4] = {0, 0, 0, 0};
	TestStructNest EmptyStruct;
	TestStructNest Struct;

	~TestStruct() {
		if (unionIdx == 2) {
			ox::safeDelete(Union.String);
		}
	}

};

template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestUnion> auto *obj) {
	OX_RETURN_ERROR(io->template setTypeInfo<TestUnion>());
	OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
	OX_RETURN_ERROR(io->field("Int", &obj->Int));
	OX_RETURN_ERROR(io->fieldCString("String", &obj->String));
	return ox::Error(0);
}

template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestStructNest> auto *obj) {
	OX_RETURN_ERROR(io->template setTypeInfo<TestStructNest>());
	OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
	OX_RETURN_ERROR(io->field("Int", &obj->Int));
	OX_RETURN_ERROR(io->field("String", &obj->String));
	return ox::Error(0);
}

template<typename T>
constexpr ox::Error model(T *io, ox::CommonPtrWith<TestStruct> auto *obj) {
	OX_RETURN_ERROR(io->template setTypeInfo<TestStruct>());
	OX_RETURN_ERROR(io->field("Bool", &obj->Bool));
	OX_RETURN_ERROR(io->field("Int", &obj->Int));
	OX_RETURN_ERROR(io->field("Int1", &obj->Int1));
	OX_RETURN_ERROR(io->field("Int2", &obj->Int2));
	OX_RETURN_ERROR(io->field("Int3", &obj->Int3));
	OX_RETURN_ERROR(io->field("Int4", &obj->Int4));
	OX_RETURN_ERROR(io->field("Int5", &obj->Int5));
	OX_RETURN_ERROR(io->field("Int6", &obj->Int6));
	OX_RETURN_ERROR(io->field("Int7", &obj->Int7));
	OX_RETURN_ERROR(io->field("Int8", &obj->Int8));
	int unionIdx = 0;
	if constexpr(T::opType() != ox::OpType::Reflect) {
		unionIdx = obj->unionIdx;
	}
	OX_RETURN_ERROR(io->field("Union", ox::UnionView{&obj->Union, unionIdx}));
	OX_RETURN_ERROR(io->field("String", &obj->String));
	OX_RETURN_ERROR(io->field("List", obj->List, 4));
	OX_RETURN_ERROR(io->field("EmptyStruct", &obj->EmptyStruct));
	OX_RETURN_ERROR(io->field("Struct", &obj->Struct));
	return ox::Error(0);
}

static std::map<ox::StringView, ox::Error(*)()> tests = {
	{
		{
			"ClawHeaderReader",
			[] {
				constexpr auto hdr = ox::StringLiteral("O1;com.drinkingtea.ox.claw.test.Header;2;");
				auto [ch, err] = ox::readClawHeader({hdr.c_str(), hdr.size() + 1});
				oxAssert(err, "Error parsing header");
				oxAssert(ch.fmt == ox::ClawFormat::Organic, "Format wrong");
				oxAssert(ch.typeName == "com.drinkingtea.ox.claw.test.Header", "Type name wrong");
				oxAssert(ch.typeVersion == 2, "Type version wrong");
				return ox::Error(0);
			}
		},
		{
			"ClawHeaderReader2",
			[] {
				constexpr auto hdr = ox::StringLiteral("M2;com.drinkingtea.ox.claw.test.Header2;3;");
				auto [ch, err] = ox::readClawHeader({hdr.c_str(), hdr.size() + 1});
				oxAssert(err, "Error parsing header");
				oxAssert(ch.fmt == ox::ClawFormat::Metal, "Format wrong");
				oxAssert(ch.typeName == "com.drinkingtea.ox.claw.test.Header2", "Type name wrong");
				oxAssert(ch.typeVersion == 3, "Type version wrong");
				return ox::Error(0);
			}
		},
		{
			"ClawHeaderReadTypeId",
			[] {
				constexpr auto hdr = ox::StringLiteral("M2;com.drinkingtea.ox.claw.test.Header2;3;awefawf");
				constexpr auto expected = ox::StringLiteral("com.drinkingtea.ox.claw.test.Header2;3");
				OX_REQUIRE(actual, ox::readClawTypeId({hdr.data(), hdr.size() + 1}));
				ox::expect(actual, expected);
				return ox::Error{};
			}
		},
		{
			"ClawWriter",
			[] {
				// This test doesn't confirm much, but it does show that the writer
				// doesn't segfault
				TestStruct ts;
				OX_RETURN_ERROR(ox::writeClaw(ts, ox::ClawFormat::Metal));
				return ox::Error(0);
			}
		},
		{
			"ClawReader",
			[] {
				TestStruct testIn, testOut;
				testIn.Bool = true;
				testIn.Int = 42;
				testIn.Union.Int = 42;
				testIn.String = "Test String 1";
				testIn.List[0] = 1;
				testIn.List[1] = 2;
				testIn.List[2] = 3;
				testIn.List[3] = 4;
				testIn.Struct.Bool = false;
				testIn.Struct.Int = 300;
				testIn.Struct.String = "Test String 2";
				const auto [buff, err] = ox::writeMC(testIn);
				oxAssert(err, "writeClaw failed");
				oxAssert(ox::readMC(buff, testOut), "readClaw failed");
				//std::cout << testIn.Union.Int << "|" << testOut.Union.Int << "|\n";
				oxAssert(testIn.Bool               == testOut.Bool, "Bool value mismatch");
				oxAssert(testIn.Int                == testOut.Int, "Int value mismatch");
				oxAssert(testIn.Int1               == testOut.Int1, "Int1 value mismatch");
				oxAssert(testIn.Int2               == testOut.Int2, "Int2 value mismatch");
				oxAssert(testIn.Int3               == testOut.Int3, "Int3 value mismatch");
				oxAssert(testIn.Int4               == testOut.Int4, "Int4 value mismatch");
				oxAssert(testIn.Int5               == testOut.Int5, "Int5 value mismatch");
				oxAssert(testIn.Int6               == testOut.Int6, "Int6 value mismatch");
				oxAssert(testIn.Int7               == testOut.Int7, "Int7 value mismatch");
				oxAssert(testIn.Int8               == testOut.Int8, "Int8 value mismatch");
				oxAssert(testIn.Union.Int          == testOut.Union.Int, "Union.Int value mismatch");
				oxAssert(testIn.String             == testOut.String, "String value mismatch");
				oxAssert(testIn.List[0]            == testOut.List[0], "List[0] value mismatch");
				oxAssert(testIn.List[1]            == testOut.List[1], "List[1] value mismatch");
				oxAssert(testIn.List[2]            == testOut.List[2], "List[2] value mismatch");
				oxAssert(testIn.List[3]            == testOut.List[3], "List[3] value mismatch");
				oxAssert(testIn.EmptyStruct.Bool   == testOut.EmptyStruct.Bool, "EmptyStruct.Bool value mismatch");
				oxAssert(testIn.EmptyStruct.Int    == testOut.EmptyStruct.Int, "EmptyStruct.Int value mismatch");
				oxAssert(testIn.EmptyStruct.String == testOut.EmptyStruct.String, "EmptyStruct.String value mismatch");
				oxAssert(testIn.Struct.Int         == testOut.Struct.Int, "Struct.Int value mismatch");
				oxAssert(testIn.Struct.String      == testOut.Struct.String, "Struct.String value mismatch");
				oxAssert(testIn.Struct.Bool        == testOut.Struct.Bool, "Struct.Bool value mismatch");

				return ox::Error(0);
			}
		},
	}
};

int main(int argc, const char **args) {
	if (argc < 2) {
		oxError("Must specify test to run");
	}
	auto const testName = args[1];
	auto const func = tests.find(testName);
	if (func != tests.end()) {
		oxAssert(func->second(), "Test returned Error");
		return 0;
	}
	return -1;
}
