#include "catch.hpp"

#include <ftl/data/new_frame.hpp>

using ftl::data::Session;
using ftl::data::Frame;
using ftl::codecs::Channel;

TEST_CASE("ftl::data::Frame create get", "[Frame]") {
	SECTION("write and read integers") {
		Frame f;
		f.create<int>(Channel::Pose, 55);

		const auto &x = f.get<int>(Channel::Pose);
		REQUIRE( x == 55 );
	}

	SECTION("write and read floats") {
		Frame f;
		f.create<float>(Channel::Pose, 44.0f);

		const auto &x = f.get<float>(Channel::Pose);
		REQUIRE( x == 44.0f );
	}

	SECTION("write and read structures") {
		struct Test {
			int a=44;
			float b=33.0f;
		};
		Frame f;
		f.create<Test>(Channel::Pose, {});

		const auto &x = f.get<Test>(Channel::Pose);
		REQUIRE( x.a == 44 );
		REQUIRE( x.b == 33.0f );
	}

	SECTION("write and read fail") {
		struct Test {
			int a=44;
			float b=33.0f;
		};
		Frame f;
		f.create<Test>(Channel::Pose, {});

		bool err = false;

		try {
			int x = f.get<int>(Channel::Pose);
			REQUIRE(x);
		} catch (...) {
			err = true;
		}
		REQUIRE(err);
	}
}

TEST_CASE("ftl::data::Frame isType", "[Frame]") {
	SECTION("is int type") {
		Frame f;
		f.create<int>(Channel::Pose, 55);

		REQUIRE( f.isType<int>(Channel::Pose) );
		REQUIRE( !f.isType<float>(Channel::Pose) );
	}

	SECTION("is struct type") {
		struct Test {
			int a; int b;
		};

		Frame f;
		f.create<Test>(Channel::Pose, {3,4});

		REQUIRE( f.isType<Test>(Channel::Pose) );
		REQUIRE( !f.isType<float>(Channel::Pose) );
	}

	SECTION("missing") {
		Frame f;

		REQUIRE( !f.isType<float>(Channel::Pose) );
	}
}

TEST_CASE("ftl::data::Frame changed", "[Frame]") {
	SECTION("change on create") {
		Frame f;

		REQUIRE( !f.changed(Channel::Pose) );
		f.create<int>(Channel::Pose, 55);
		REQUIRE( f.changed(Channel::Pose) );
	}

	SECTION("no change on untouch") {
		Frame f;

		f.create<int>(Channel::Pose, 55);
		REQUIRE( f.changed(Channel::Pose) );
		f.untouch(Channel::Pose);
		REQUIRE( !f.changed(Channel::Pose) );
	}
}

TEST_CASE("ftl::data::Frame create", "[Frame]") {
	SECTION("same value on create") {
		Frame f;

		f.create<int>(Channel::Pose, 55);
		const auto &x = f.get<int>(Channel::Pose);
		REQUIRE( x == 55 );

		f.create<int>(Channel::Pose);
		const auto &y = f.get<int>(Channel::Pose);
		REQUIRE( y == 55 );
	}

	SECTION("change of type") {
		Frame f;

		f.create<int>(Channel::Pose, 55);
		auto x = f.getPtr<int>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( *x == 55 );

		f.create<float>(Channel::Pose);
		auto y = f.getPtr<float>(Channel::Pose);
		REQUIRE( y );
		REQUIRE( *y == 0.0f );
	}
}

TEST_CASE("ftl::data::Frame use of parent", "[Frame]") {
	SECTION("get from parent") {
		Session p;
		Frame f(&p);

		p.create<int>(Channel::Pose, 55);

		auto x = f.getPtr<int>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( *x == 55 );

		auto y = p.getPtr<int>(Channel::Pose);
		REQUIRE( x == y );
	}

	SECTION("has from parent") {
		Session p;
		Frame f(&p);

		p.create<int>(Channel::Pose, 55);
		REQUIRE( f.has(Channel::Pose) );
	}

	SECTION("no change in parent") {
		Session p;
		Frame f(&p);

		p.create<int>(Channel::Pose, 55);
		p.untouch(Channel::Pose);

		REQUIRE( !f.changed(Channel::Pose) );
		REQUIRE( !p.changed(Channel::Pose) );

		f.set<int>(Channel::Pose, 66);

		REQUIRE( f.changed(Channel::Pose) );
		REQUIRE( !p.changed(Channel::Pose) );

		auto x = f.getPtr<int>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( *x == 66 );

		auto y = p.getPtr<int>(Channel::Pose);
		REQUIRE( y );
		REQUIRE( *y == 55 );
	}
}

TEST_CASE("ftl::data::Frame flush", "[Frame]") {
	SECTION("event on flush") {
		Frame f;

		int event = 0;
		f.on(Channel::Pose, [&event](Frame &frame, Channel c) {
			event++;
			return true;
		});

		f.create<int>(Channel::Pose, 55);
		REQUIRE( event == 0 );

		f.flush();
		REQUIRE( event == 1 );
	}

	SECTION("parent event on flush") {
		Session p;
		Frame f(&p);

		int event = 0;
		p.on(Channel::Pose, [&event](Frame &frame, Channel c) {
			event++;
			return true;
		});

		f.create<int>(Channel::Pose, 55);
		REQUIRE( event == 0 );

		f.flush();
		REQUIRE( event == 1 );
	}

	SECTION("parent change on flush") {
		Session p;
		Frame f(&p);

		p.create<int>(Channel::Pose, 55);
		p.flush();

		f.set<int>(Channel::Pose, 66);
		auto x = p.getPtr<int>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( *x == 55 );
		
		f.flush();
		x = p.getPtr<int>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( *x == 66 );
	}

	SECTION("untouched on flush") {
		Frame f;

		f.create<int>(Channel::Pose, 55);
		REQUIRE( f.changed(Channel::Pose) );

		f.flush();
		REQUIRE( !f.changed(Channel::Pose) );
	}
}

TEST_CASE("ftl::data::Frame register", "[Frame]") {
	SECTION("register typed channel and valid create") {
		Frame f;

		Frame::registerChannel(Channel::Colour, ftl::data::make_channel<float>("colour", ftl::data::ChannelMode::PERSISTENT));
		REQUIRE( f.create<float>(Channel::Colour, 5.0f) == 5.0f );

		Frame::clearRegistry();
	}

	SECTION("register typed channel and invalid create") {
		Frame f;

		Frame::registerChannel(Channel::Colour, ftl::data::make_channel<float>("colour", ftl::data::ChannelMode::PERSISTENT));

		bool err = false;
		try {
			f.create<int>(Channel::Colour, 5);
		} catch(const std::exception &e) {
			err = true;
		}
		REQUIRE( err );

		Frame::clearRegistry();
	}
}

// ==== Complex type overload test =============================================

struct TestA {
	int a=55;
};

struct TestB {
	int b=99;
};

struct TestC {
	TestA a;
	TestB b;
};

template <>
TestA &ftl::data::Frame::create<TestA>(ftl::codecs::Channel c) {
	return create<TestC>(c).a;
}

template <>
TestA &ftl::data::Frame::create<TestA>(ftl::codecs::Channel c, const TestA &a) {
	TestC cc;
	cc.a = a;
	return create<TestC>(c, cc).a;
}

template <>
TestB &ftl::data::Frame::create<TestB>(ftl::codecs::Channel c, const TestB &b) {
	TestC cc;
	cc.b = b;
	return create<TestC>(c, cc).b;
}

template <>
const TestA *ftl::data::Frame::getPtr<TestA>(ftl::codecs::Channel c) const noexcept {
	auto *ptr = getPtr<TestC>(c);
	return (ptr) ? &ptr->a : nullptr;
}

template <>
const TestB *ftl::data::Frame::getPtr<TestB>(ftl::codecs::Channel c) const noexcept {
	auto *ptr = getPtr<TestC>(c);
	return (ptr) ? &ptr->b : nullptr;
}

TEST_CASE("ftl::data::Frame Complex Overload", "[Frame]") {
	SECTION("Create and get first type with default") {
		Frame f;
		f.create<TestA>(Channel::Pose);
		
		auto *x = f.getPtr<TestA>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( x->a == 55 );

		auto *y = f.getPtr<TestB>(Channel::Pose);
		REQUIRE( y );
		REQUIRE( y->b == 99 );
	}

	SECTION("Create and get first type with value") {
		Frame f;
		f.create<TestA>(Channel::Pose, {77});
		
		auto *x = f.getPtr<TestA>(Channel::Pose);
		REQUIRE( x );
		REQUIRE( x->a == 77 );

		auto *y = f.getPtr<TestB>(Channel::Pose);
		REQUIRE( y );
		REQUIRE( y->b == 99 );
	}
}