#include "catch.hpp"

#include "duckdb/common/serializer/binary_deserializer.hpp"
#include "duckdb/common/serializer/binary_serializer.hpp"
#include "duckdb/common/serializer/memory_stream.hpp"

namespace duckdb {

struct Bar {
	uint32_t b;
	void Serialize(Serializer &serializer) const {
		serializer.WriteProperty<uint32_t>(1, "b", b);
	}

	static unique_ptr<Bar> Deserialize(Deserializer &deserializer) {
		auto result = make_uniq<Bar>();
		deserializer.ReadProperty<uint32_t>(1, "b", result->b);
		return result;
	}
};

struct Foo {
	int32_t a;
	unique_ptr<Bar> bar;
	int32_t c;

	void Serialize(Serializer &serializer) const {
		serializer.WriteProperty<int32_t>(1, "a", a);
		serializer.WritePropertyWithDefault<unique_ptr<Bar>>(2, "bar", bar, unique_ptr<Bar>());
		serializer.WriteProperty<int32_t>(3, "c", c);
	}

	static unique_ptr<Foo> Deserialize(Deserializer &deserializer) {
		auto result = make_uniq<Foo>();
		deserializer.ReadProperty<int32_t>(1, "a", result->a);
		deserializer.ReadPropertyWithExplicitDefault<unique_ptr<Bar>>(2, "bar", result->bar, unique_ptr<Bar>());
		deserializer.ReadProperty<int32_t>(3, "c", result->c);
		return result;
	}
};

TEST_CASE("Test default values", "[serialization]") {
	Foo foo_in;
	foo_in.a = 42;
	foo_in.bar = make_uniq<Bar>();
	foo_in.bar->b = 43;
	foo_in.c = 44;

	Allocator allocator;
	MemoryStream stream(allocator);
	SerializationOptions options;
	options.serialize_default_values = false;
	BinarySerializer::Serialize(foo_in, stream, options);
	auto pos1 = stream.GetPosition();
	stream.Rewind();
	auto foo_out_ptr = BinaryDeserializer::Deserialize<Foo>(stream);
	auto &foo_out = *foo_out_ptr.get();

	REQUIRE(foo_in.a == foo_out.a);
	REQUIRE(foo_in.bar->b == foo_out.bar->b);
	REQUIRE(foo_in.c == foo_out.c);

	// Now try with a default value
	foo_in.bar = nullptr;

	stream.Rewind();

	options.serialize_default_values = false;
	BinarySerializer::Serialize(foo_in, stream, options);
	auto pos2 = stream.GetPosition();
	stream.Rewind();

	foo_out_ptr = BinaryDeserializer::Deserialize<Foo>(stream);
	auto &foo_out2 = *foo_out_ptr.get();

	REQUIRE(foo_in.a == foo_out2.a);
	REQUIRE(foo_out2.bar == nullptr);
	REQUIRE(foo_in.c == foo_out2.c);

	// We should not have written the default value
	REQUIRE(pos1 > pos2);
}

//------------------------------------------------------
// Test deleted properties
//------------------------------------------------------

struct Complex {
	int c1;
	string c2;
	Complex(int c1, string c2) : c1(c1), c2(c2) {
	}
	Complex() : c1(0), c2("") {
	}

	void Serialize(Serializer &serializer) const {
		serializer.WriteProperty<int>(1, "c1", c1);
		serializer.WriteProperty<string>(2, "c2", c2);
	}

	static unique_ptr<Complex> Deserialize(Deserializer &deserializer) {
		auto result = make_uniq<Complex>();
		deserializer.ReadProperty<int>(1, "c1", result->c1);
		deserializer.ReadProperty<string>(2, "c2", result->c2);
		return result;
	}
};

struct FooV1 {
	int p1;
	vector<unique_ptr<Complex>> p2;
	int p3;
	unique_ptr<Complex> p4;

	void Serialize(Serializer &serializer) const {
		serializer.WriteProperty<int>(1, "p1", p1);
		serializer.WritePropertyWithDefault<vector<unique_ptr<Complex>>>(2, "p2", p2);
		serializer.WriteProperty<int>(3, "p3", p3);
		serializer.WriteProperty<unique_ptr<Complex>>(4, "p4", p4);
	}

	static unique_ptr<FooV1> Deserialize(Deserializer &deserializer) {
		auto result = make_uniq<FooV1>();
		deserializer.ReadProperty<int>(1, "p1", result->p1);
		deserializer.ReadPropertyWithDefault<vector<unique_ptr<Complex>>>(2, "p2", result->p2);
		deserializer.ReadProperty<int>(3, "p3", result->p3);
		deserializer.ReadProperty<unique_ptr<Complex>>(4, "p4", result->p4);
		return result;
	}
};

struct FooV2 {
	int p1;
	/*vector<unique_ptr<Complex>> p2;*/ // In v2, this is deleted
	int p3;
	unique_ptr<Complex> p4;
	unique_ptr<Complex> p5; // In v2, this is added

	void Serialize(Serializer &serializer) const {
		serializer.WriteProperty<int>(1, "p1", p1);
		// This field is deleted!
		/* serializer.WriteDeletedProperty<vector<unique_ptr<Complex>>>(2, "p2"); */
		serializer.WriteProperty<int>(3, "p3", p3);
		serializer.WriteProperty<unique_ptr<Complex>>(4, "p4", p4);

		// Because this is a new field, we have to provide a default value
		// to try to preserve backwards compatibility (in best case)
		serializer.WritePropertyWithDefault<unique_ptr<Complex>>(5, "p5", p5);
	}

	static unique_ptr<FooV2> Deserialize(Deserializer &deserializer) {
		auto result = make_uniq<FooV2>();
		deserializer.ReadProperty(1, "p1", result->p1);
		deserializer.ReadDeletedProperty<vector<unique_ptr<Complex>>>(2, "p2");
		deserializer.ReadProperty(3, "p3", result->p3);
		deserializer.ReadProperty(4, "p4", result->p4);
		deserializer.ReadPropertyWithDefault<unique_ptr<Complex>>(5, "p5", result->p5);
		return result;
	}
};

TEST_CASE("Test deleted values", "[serialization]") {
	FooV1 v1_in = {1, {}, 6, make_uniq<Complex>(1, "foo")};
	v1_in.p2.push_back(make_uniq<Complex>(2, "3"));
	v1_in.p2.push_back(make_uniq<Complex>(4, "5"));

	FooV2 v2_in = {1, 3, make_uniq<Complex>(1, "foo"), nullptr};

	Allocator allocator;
	MemoryStream stream(allocator);
	SerializationOptions options;
	options.serialize_default_values = false;
	// First of, sanity check that foov1 <-> foov1 works
	BinarySerializer::Serialize(v1_in, stream, options);
	{
		stream.Rewind();
		auto v1_out_ptr = BinaryDeserializer::Deserialize<FooV1>(stream);
		auto &v1_out = *v1_out_ptr.get();
		REQUIRE(v1_in.p1 == v1_out.p1);
		REQUIRE(v1_in.p2.size() == v1_out.p2.size());
		REQUIRE(v1_in.p2[0]->c1 == v1_out.p2[0]->c1);
		REQUIRE(v1_in.p2[0]->c2 == v1_out.p2[0]->c2);
		REQUIRE(v1_in.p2[1]->c1 == v1_out.p2[1]->c1);
		REQUIRE(v1_in.p2[1]->c2 == v1_out.p2[1]->c2);
		REQUIRE(v1_in.p3 == v1_out.p3);
		REQUIRE(v1_in.p4->c1 == v1_out.p4->c1);
		REQUIRE(v1_in.p4->c2 == v1_out.p4->c2);
	}

	stream.Rewind();

	// Also check that foov2 <-> foov2 works
	options.serialize_default_values = false;
	BinarySerializer::Serialize(v2_in, stream, options);
	{
		stream.Rewind();
		auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
		auto &v2_out = *v2_out_ptr.get();
		REQUIRE(v2_in.p1 == v2_out.p1);
		REQUIRE(v2_in.p3 == v2_out.p3);
		REQUIRE(v2_in.p4->c1 == v2_out.p4->c1);
		REQUIRE(v2_in.p4->c2 == v2_out.p4->c2);
		REQUIRE(v2_in.p5 == v2_out.p5);
	}

	// Check that foov1 -> foov2 works (backwards compatible)
	stream.Rewind();
	options.serialize_default_values = false;
	BinarySerializer::Serialize(v1_in, stream, options);
	{
		stream.Rewind();
		auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
		auto &v2_out = *v2_out_ptr.get();
		REQUIRE(v1_in.p1 == v2_out.p1);
		REQUIRE(v1_in.p3 == v2_out.p3);
		REQUIRE(v1_in.p4->c1 == v2_out.p4->c1);
		REQUIRE(v1_in.p4->c2 == v2_out.p4->c2);
		REQUIRE(v2_out.p5 == nullptr);
	}

	// Check that foov2 -> foov1 works (forwards compatible)
	// This should be ok, since the property we deleted was optional (had a default value)
	stream.Rewind();
	options.serialize_default_values = false;
	BinarySerializer::Serialize(v2_in, stream, options);
	{
		stream.Rewind();
		auto v1_out_ptr = BinaryDeserializer::Deserialize<FooV1>(stream);
		auto &v1_out = *v1_out_ptr.get();
		REQUIRE(v2_in.p1 == v1_out.p1);
		REQUIRE(v2_in.p3 == v1_out.p3);
		REQUIRE(v2_in.p4->c1 == v1_out.p4->c1);
		REQUIRE(v2_in.p4->c2 == v1_out.p4->c2);
		REQUIRE(v1_out.p2.empty());
	}

	// If we change the new value in foov2 to something thats not the default, we break forwards compatibility.
	// But thats life. Tough shit.
	stream.Rewind();
	v2_in.p5 = make_uniq<Complex>(2, "foo");
	options.serialize_default_values = false;
	BinarySerializer::Serialize(v2_in, stream, options);
	{
		stream.Rewind();
		REQUIRE_THROWS(BinaryDeserializer::Deserialize<FooV1>(stream));
	}

	// However, the new value should be read correctly!
	stream.Rewind();
	options.serialize_default_values = false;
	BinarySerializer::Serialize(v2_in, stream, options);
	{
		stream.Rewind();
		auto v2_out_ptr = BinaryDeserializer::Deserialize<FooV2>(stream);
		auto &v2_out = *v2_out_ptr.get();
		REQUIRE(v2_in.p1 == v2_out.p1);
		REQUIRE(v2_in.p3 == v2_out.p3);
		REQUIRE(v2_in.p4->c1 == v2_out.p4->c1);
		REQUIRE(v2_in.p4->c2 == v2_out.p4->c2);
		REQUIRE(v2_out.p5->c1 == 2);
		REQUIRE(v2_out.p5->c2 == "foo");
	}
}

} // namespace duckdb
