// This file is part of Desktop App Toolkit,
// a set of libraries for developing nice desktop applications.
//
// For license and copyright information please follow this link:
// https://github.com/desktop-app/legal/blob/master/LEGAL
//
#include <catch.hpp>

#include <rpl/producer.h>
#include <rpl/event_stream.h>

using namespace rpl;

class OnDestructor {
public:
	OnDestructor(std::function<void()> callback)
	: _callback(std::move(callback)) {
	}
	~OnDestructor() {
		if (_callback) {
			_callback();
		}
	}

private:
	std::function<void()> _callback;

};

TEST_CASE("basic producer tests", "[rpl::producer]") {
	SECTION("producer next, done and lifetime end test") {
		auto lifetimeEnded = std::make_shared<bool>(false);
		auto sum = std::make_shared<int>(0);
		auto doneGenerated = std::make_shared<bool>(false);
		auto destroyed = std::make_shared<bool>(false);
		{
			auto destroyCaller = std::make_shared<OnDestructor>([=] {
				*destroyed = true;
			});
			{
				auto alive = make_producer<int>([=](auto &&consumer) {
					(void)destroyCaller;
					consumer.put_next(1);
					consumer.put_next(2);
					consumer.put_next(3);
					consumer.put_done();
					return [=] {
						(void)destroyCaller;
						*lifetimeEnded = true;
					};
				}).start([=](int value) {
					(void)destroyCaller;
					*sum += value;
				}, [=](no_error) {
					(void)destroyCaller;
				}, [=]() {
					(void)destroyCaller;
					*doneGenerated = true;
				});
			}
		}
		REQUIRE(*sum == 1 + 2 + 3);
		REQUIRE(*doneGenerated);
		REQUIRE(*lifetimeEnded);
		REQUIRE(*destroyed);
	}

	SECTION("producer error test") {
		auto errorGenerated = std::make_shared<bool>(false);
		{
			auto alive = make_producer<no_value, bool>([=](auto &&consumer) {
				consumer.put_error(true);
				return lifetime();
			}).start([=](no_value) {
			}, [=](bool error) {
				*errorGenerated = error;
			}, [=]() {
			});
		}
		REQUIRE(*errorGenerated);
	}

	SECTION("nested lifetimes test") {
		auto lifetimeEndCount = std::make_shared<int>(0);
		{
			auto lifetimes = lifetime();
			{
				auto testProducer = make_producer<no_value>([=](auto &&consumer) {
					return [=] {
						++*lifetimeEndCount;
					};
				});
				testProducer.start_copy([=](no_value) {
				}, [=](no_error) {
				}, [=] {
				}, lifetimes);
				std::move(testProducer).start([=](no_value) {
				}, [=](no_error) {
				}, [=] {
				}, lifetimes);
			}
			REQUIRE(*lifetimeEndCount == 0);
		}
		REQUIRE(*lifetimeEndCount == 2);
	}

	SECTION("nested producers test") {
		auto sum = std::make_shared<int>(0);
		auto lifetimeEndCount = std::make_shared<int>(0);
		auto saved = lifetime();
		{
			make_producer<int>([=](auto &&consumer) {
				auto inner = make_producer<int>([=](auto &&consumer) {
					consumer.put_next(1);
					consumer.put_next(2);
					consumer.put_next(3);
					return [=] {
						++*lifetimeEndCount;
					};
				});
				auto result = lifetime([=] {
					++*lifetimeEndCount;
				});
				inner.start_copy([=](int value) {
					consumer.put_next_copy(value);
				}, [=](no_error) {
				}, [=] {
				}, result);
				std::move(inner).start([=](int value) {
					consumer.put_next_copy(value);
				}, [=](no_error) {
				}, [=] {
				}, result);
				return result;
			}).start([=](int value) {
				*sum += value;
			}, [=](no_error) {
			}, [=] {
			}, saved);
		}
		REQUIRE(*sum == 1 + 2 + 3 + 1 + 2 + 3);
		REQUIRE(*lifetimeEndCount == 0);
		saved.destroy();
		REQUIRE(*lifetimeEndCount == 3);
	}

	SECTION("tuple producer test") {
		auto result = std::make_shared<int>(0);
		{
			auto alive = make_producer<std::tuple<int, double>>([=](
					auto &&consumer) {
				consumer.put_next(std::make_tuple(1, 2.));
				return lifetime();
			}).start([=](int a, double b) {
				*result = a + int(b);
			}, [=](no_error error) {
			}, [=]() {
			});
		}
		REQUIRE(*result == 3);
	}
}

TEST_CASE("basic event_streams tests", "[rpl::event_stream]") {
	SECTION("event_stream basic test") {
		auto sum = std::make_shared<int>(0);
		event_stream<int> stream;
		stream.fire(1);
		stream.fire(2);
		stream.fire(3);
		{
			auto saved = lifetime();
			stream.events().start([=](int value) {
				*sum += value;
			}, [=](no_error) {
			}, [=] {
			}, saved);
			stream.fire(11);
			stream.fire(12);
			stream.fire(13);
		}
		stream.fire(21);
		stream.fire(22);
		stream.fire(23);

		REQUIRE(11 + 12 + 13);
	}

	SECTION("event_stream add in handler test") {
		auto sum = std::make_shared<int>(0);
		event_stream<int> stream;

		{
			auto composite = lifetime();
			stream.events().start([=, &stream, &composite](int value) {
				*sum += value;
				stream.events().start([=](int value) {
					*sum += value;
				}, [=](no_error) {
				}, [=] {
				}, composite);
			}, [=](no_error) {
			}, [=] {
			}, composite);

			{
				auto inner = lifetime();
				stream.events().start([=, &stream, &inner](int value) {
					*sum += value;
					stream.events().start([=](int value) {
						*sum += value;
					}, [=](no_error) {
					}, [=] {
					}, inner);
				}, [=](no_error) {
				}, [=] {
				}, inner);

				stream.fire(1);
				stream.fire(2);
				stream.fire(3);
			}
			stream.fire(11);
			stream.fire(12);
			stream.fire(13);
		}
		stream.fire(21);
		stream.fire(22);
		stream.fire(23);

		REQUIRE(*sum ==
			(1 + 1) +
			((2 + 2) + (2 + 2)) +
			((3 + 3 + 3) + (3 + 3 + 3)) +
			(11 + 11 + 11 + 11) +
			(12 + 12 + 12 + 12 + 12) +
			(13 + 13 + 13 + 13 + 13 + 13));
	}

	SECTION("event_stream add and remove in handler test") {
		auto sum = std::make_shared<int>(0);
		event_stream<int> stream;

		{
			auto composite = lifetime();
			stream.events().start([=, &stream, &composite](int value) {
				*sum += value;
				composite.destroy();
				stream.events().start([=](int value) {
					*sum += value;
				}, [=](no_error) {
				}, [=] {
				}, composite);
			}, [=](no_error) {
			}, [=] {
			}, composite);

			{
				auto inner = lifetime();
				stream.events().start([=, &stream, &inner](int value) {
					*sum += value;
					inner.destroy();
					stream.events().start([=](int value) {
						*sum += value;
					}, [=](no_error) {
					}, [=] {
					}, inner);
				}, [=](no_error) {
				}, [=] {
				}, inner);

				stream.fire(1);
				stream.fire(2);
				stream.fire(3);
			}
			stream.fire(11);
			stream.fire(12);
			stream.fire(13);
		}
		stream.fire(21);
		stream.fire(22);
		stream.fire(23);

		REQUIRE(*sum ==
			(1 + 1) +
			(2 + 2) +
			(3 + 3) +
			(11) +
			(12) +
			(13));
	}

	SECTION("event_stream ends before handler lifetime") {
		auto sum = std::make_shared<int>(0);
		lifetime extended;
		{
			event_stream<int> stream;
			stream.events().start([=](int value) {
				*sum += value;
			}, [=](no_error) {
			}, [=] {
			}, extended);
			stream.fire(1);
			stream.fire(2);
			stream.fire(3);
		}
		REQUIRE(*sum == 1 + 2 + 3);
	}

	SECTION("event_stream move test") {
		auto sum = std::make_shared<int>(0);
		lifetime extended;
		{
			event_stream<int> stream;
			stream.events()
				| on_next([=](int value) {
					*sum += value;
				}, extended);

			stream.fire(1);
			stream.fire(2);

			auto movedStream = std::move(stream);
			movedStream.fire(3);
			movedStream.fire(4);
		}
		REQUIRE(*sum == 1 + 2 + 3 + 4);
	}
}

TEST_CASE("basic piping tests", "[rpl::producer]") {

	SECTION("on_*") {
		auto sum = std::make_shared<int>(0);
		auto dones = std::make_shared<int>(0);
		{
			auto alive = lifetime();
			make_producer<int, int>([=](auto &&consumer) {
				consumer.put_next(1);
				consumer.put_done();
				return lifetime();
			}) | on_next([=](int value) {
				*sum += value;
			}, alive);

			make_producer<int, int>([=](auto &&consumer) {
				consumer.put_next(11);
				consumer.put_error(111);
				return lifetime();
			}) | on_error([=](int value) {
				*sum += value;
			}, alive);

			make_producer<int, int>([=](auto &&consumer) {
				consumer.put_next(1111);
				consumer.put_done();
				return lifetime();
			}) | on_done([=]() {
				*dones += 1;
			}, alive);

			make_producer<int, int>([=](auto &&consumer) {
				consumer.put_next(11111);
				consumer.put_next(11112);
				consumer.put_next(11113);
				consumer.put_error(11114);
				return lifetime();
			}) | on_next_error([=](int value) {
				*sum += value;
			}, [=](int value) {
				*sum += value;
			}, alive);
		}

		auto alive = lifetime();
		make_producer<int, int>([=](auto &&consumer) {
			consumer.put_next(111111);
			consumer.put_next(111112);
			consumer.put_next(111113);
			consumer.put_done();
			return lifetime();
		}) | on_next_done([=](int value) {
			*sum += value;
		}, [=]() {
			*dones += 11;
		}, alive);

		make_producer<int, int>([=](auto &&consumer) {
			consumer.put_error(1111111);
			return lifetime();
		}) | on_error_done([=](int value) {
			*sum += value;
		}, [=]() {
			*dones = 0;
		}, alive);

		make_producer<int, int>([=](auto &&consumer) {
			consumer.put_next(11111111);
			consumer.put_next(11111112);
			consumer.put_next(11111113);
			consumer.put_error(11111114);
			return lifetime();
		}) | on_next_error_done([=](int value) {
			*sum += value;
		}, [=](int value) {
			*sum += value;
		}, [=]() {
			*dones = 0;
		}, alive);

		REQUIRE(*sum ==
			1 +
			111 +
			11111 + 11112 + 11113 + 11114 +
			111111 + 111112 + 111113 +
			1111111 +
			11111111 + 11111112 + 11111113 + 11111114);
		REQUIRE(*dones == 1 + 11);
	}

	SECTION("on_next should copy its callback") {
		auto sum = std::make_shared<int>(0);
		{
			auto next = [=](int value) {
				REQUIRE(sum != nullptr);
				*sum += value;
			};

			for (int i = 0; i != 3; ++i) {
				auto alive = lifetime();
				make_producer<int, int>([=](auto &&consumer) {
					consumer.put_next(1);
					consumer.put_done();
					return lifetime();
				}) | on_next(next, alive);
			}
		}
		REQUIRE(*sum == 3);
	}
}
