#include <gmock/gmock.h>
#include "time/Timeline.h"
#include <functional>
#include <boost/optional/optional_io.hpp>

using namespace testing;
using std::vector;
using boost::optional;
using std::initializer_list;
using boost::none;

TEST(Timeline, constructors_initializeState) {
	auto args = {
		Timed<int>(-10_cs, 30_cs, 1),
		Timed<int>(10_cs, 40_cs, 2),
		Timed<int>(50_cs, 60_cs, 3)
	};
	auto expected = {
		Timed<int>(-10_cs, 10_cs, 1),
		Timed<int>(10_cs, 40_cs, 2),
		Timed<int>(50_cs, 60_cs, 3)
	};
	EXPECT_THAT(
		Timeline<int>(args.begin(), args.end()),
		ElementsAreArray(expected)
	);
	EXPECT_THAT(
		Timeline<int>(vector<Timed<int>>(args)),
		ElementsAreArray(expected)
	);
	EXPECT_THAT(
		Timeline<int>(args),
		ElementsAreArray(expected)
	);
}

TEST(Timeline, empty) {
	Timeline<int> empty0;
	EXPECT_TRUE(empty0.empty());
	EXPECT_THAT(empty0, IsEmpty());

	Timeline<int> empty1 {};
	EXPECT_TRUE(empty1.empty());
	EXPECT_THAT(empty1, IsEmpty());

	Timeline<int> empty2 { Timed<int>(1_cs, 1_cs, 1) };
	EXPECT_TRUE(empty2.empty());
	EXPECT_THAT(empty2, IsEmpty());

	Timeline<int> nonEmpty { Timed<int>(1_cs, 2_cs, 1) };
	EXPECT_FALSE(nonEmpty.empty());
	EXPECT_THAT(nonEmpty, Not(IsEmpty()));
}

TEST(Timeline, size) {
	Timeline<int> empty0;
	EXPECT_EQ(0, empty0.size());
	EXPECT_THAT(empty0, SizeIs(0));

	Timeline<int> empty1 {};
	EXPECT_EQ(0, empty1.size());
	EXPECT_THAT(empty1, SizeIs(0));

	Timeline<int> empty2 { Timed<int>(1_cs, 1_cs, 1) };
	EXPECT_EQ(0, empty2.size());
	EXPECT_THAT(empty2, SizeIs(0));

	Timeline<int> size1 { Timed<int>(1_cs, 10_cs, 1) };
	EXPECT_EQ(1, size1.size());
	EXPECT_THAT(size1, SizeIs(1));

	Timeline<int> size2 { Timed<int>(-10_cs, 10_cs, 1), Timed<int>(10_cs, 11_cs, 5) };
	EXPECT_EQ(2, size2.size());
	EXPECT_THAT(size2, SizeIs(2));
}

TEST(Timeline, getRange) {
	Timeline<int> empty0;
	EXPECT_EQ(TimeRange(0_cs, 0_cs), empty0.getRange());

	Timeline<int> empty1 {};
	EXPECT_EQ(TimeRange(0_cs, 0_cs), empty1.getRange());

	Timeline<int> empty2 { Timed<int>(1_cs, 1_cs, 1) };
	EXPECT_EQ(TimeRange(0_cs, 0_cs), empty2.getRange());

	Timeline<int> nonEmpty1 { Timed<int>(1_cs, 10_cs, 1) };
	EXPECT_EQ(TimeRange(1_cs, 10_cs), nonEmpty1.getRange());

	Timeline<int> nonEmpty2 { Timed<int>(-10_cs, 5_cs, 1), Timed<int>(10_cs, 11_cs, 5) };
	EXPECT_EQ(TimeRange(-10_cs, 11_cs), nonEmpty2.getRange());
}

TEST(Timeline, iterators) {
	Timeline<int> timeline { Timed<int>(-5_cs, 0_cs, 10), Timed<int>(5_cs, 15_cs, 9) };
	auto expected = { Timed<int>(-5_cs, 0_cs, 10), Timed<int>(5_cs, 15_cs, 9) };
	EXPECT_THAT(timeline, ElementsAreArray(expected));

	vector<Timed<int>> reversedActual;
	copy(timeline.rbegin(), timeline.rend(), back_inserter(reversedActual));
	vector<Timed<int>> reversedExpected;
	reverse_copy(expected.begin(), expected.end(), back_inserter(reversedExpected));
	EXPECT_THAT(reversedActual, ElementsAreArray(reversedExpected));
}

void testFind(
	const Timeline<int>& timeline,
	FindMode findMode,
	const initializer_list<Timed<int>*> expectedResults
) {
	int i = -1;
	for (Timed<int>* expectedResult : expectedResults) {
		auto it = timeline.find(centiseconds(++i), findMode);
		if (expectedResult != nullptr) {
			EXPECT_NE(it, timeline.end())
				<< "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
			if (it != timeline.end()) {
				EXPECT_EQ(*expectedResult, *it)
					<< "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
			}
		} else {
			EXPECT_EQ(timeline.end(), it)
				<< "Timeline: " << timeline << "; findMode: " << static_cast<int>(findMode) << "; i: " << i;
		}
	}
}

TEST(Timeline, find) {
	Timed<int> a = Timed<int>(1_cs, 2_cs, 1);
	Timed<int> b = Timed<int>(2_cs, 5_cs, 2);
	Timed<int> c = Timed<int>(7_cs, 9_cs, 3);
	const Timeline<int> timeline { a, b, c };

	testFind(timeline, FindMode::SampleLeft, { nullptr, nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr });
	testFind(timeline, FindMode::SampleRight, { nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr, nullptr });
	testFind(timeline, FindMode::SearchLeft, { nullptr, nullptr, &a, &b, &b, &b, &b, &b, &c, &c, &c });
	testFind(timeline, FindMode::SearchRight, { &a, &a, &b, &b, &b, &c, &c, &c, &c, nullptr, nullptr });
}

TEST(Timeline, get) {
	Timed<int> a = Timed<int>(1_cs, 2_cs, 1);
	Timed<int> b = Timed<int>(2_cs, 5_cs, 2);
	Timed<int> c = Timed<int>(7_cs, 9_cs, 3);
	Timeline<int> timeline { a, b, c };

	initializer_list<Timed<int>*> expectedResults =
		{ nullptr, &a, &b, &b, &b, nullptr, nullptr, &c, &c, nullptr, nullptr };
	int i = -1;
	for (Timed<int>* expectedResult : expectedResults) {
		optional<const Timed<int>&> value = timeline.get(centiseconds(++i));
		if (expectedResult != nullptr) {
			EXPECT_TRUE(value) << "i: " << i;
			if (value) {
				EXPECT_EQ(*expectedResult, *value) << "i: " << i;
			}
		} else {
			EXPECT_FALSE(value) << "i: " << i;
		}
	}
}

TEST(Timeline, clear) {
	const Timeline<int> original { { 1_cs, 2_cs, 1 }, { 2_cs, 5_cs, 2 }, { 7_cs, 9_cs, 3 } };
	
	{
		auto timeline = original;
		timeline.clear(-10_cs, 10_cs);
		EXPECT_THAT(timeline, IsEmpty());
	}
	
	{
		auto timeline = original;
		timeline.clear(1_cs, 2_cs);
		Timeline<int> expected { { 2_cs, 5_cs, 2 }, { 7_cs, 9_cs, 3 } };
		EXPECT_EQ(expected, timeline);
	}
	
	{
		auto timeline = original;
		timeline.clear(3_cs, 4_cs);
		Timeline<int> expected { { 1_cs, 2_cs, 1 }, { 2_cs, 3_cs, 2 }, { 4_cs, 5_cs, 2 }, { 7_cs, 9_cs, 3 } };
		EXPECT_EQ(expected, timeline);
	}
	
	{
		auto timeline = original;
		timeline.clear(6_cs, 8_cs);
		Timeline<int> expected { { 1_cs, 2_cs, 1 }, { 2_cs, 5_cs, 2 }, { 8_cs, 9_cs, 3 } };
		EXPECT_EQ(expected, timeline);
	}
	
	{
		auto timeline = original;
		timeline.clear(8_cs, 10_cs);
		Timeline<int> expected { { 1_cs, 2_cs, 1 }, { 2_cs, 5_cs, 2 }, { 7_cs, 8_cs, 3 } };
		EXPECT_EQ(expected, timeline);
	}
}

void testSetter(const std::function<void(const Timed<int>&, Timeline<int>&)>& set) {
	Timeline<int> timeline;
	vector<optional<int>> expectedValues(20, none);
	auto newElements = {
		Timed<int>(1_cs, 2_cs, 4),
		Timed<int>(3_cs, 6_cs, 4),
		Timed<int>(7_cs, 9_cs, 5),
		Timed<int>(9_cs, 10_cs, 6),
		Timed<int>(2_cs, 3_cs, 4),
		Timed<int>(0_cs, 1_cs, 7),
		Timed<int>(-10_cs, 1_cs, 8),
		Timed<int>(-10_cs, 0_cs, 9),
		Timed<int>(-10_cs, -1_cs, 10),
		Timed<int>(9_cs, 20_cs, 11),
		Timed<int>(10_cs, 20_cs, 12),
		Timed<int>(11_cs, 20_cs, 13),
		Timed<int>(4_cs, 6_cs, 14),
		Timed<int>(4_cs, 6_cs, 15),
		Timed<int>(8_cs, 10_cs, 15),
		Timed<int>(6_cs, 8_cs, 15),
		Timed<int>(6_cs, 8_cs, 16)
	};
	int newElementIndex = -1;
	for (const auto& newElement : newElements) {
		++newElementIndex;
		// Set element in timeline
		set(newElement, timeline);

		// Update expected value for every index
		const centiseconds elementStart = max(newElement.getStart(), 0_cs);
		centiseconds elementEnd = newElement.getEnd();
		for (centiseconds t = elementStart; t < elementEnd; ++t) {
			expectedValues[t.count()] = newElement.getValue();
		}

		// Check timeline via indexer
		for (centiseconds t = 0_cs; t < 10_cs; ++t) {
			optional<int> actual = timeline[t];
			EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
		}

		// Check timeline via iterators
		for (const auto& element : timeline) {
			// No element should have zero-length
			EXPECT_LT(0_cs, element.getDuration());

			// Element should match expected values
			for (centiseconds t = std::max(centiseconds::zero(), element.getStart()); t < element.getEnd(); ++t) {
				optional<int> expectedValue = expectedValues[t.count()];
				EXPECT_TRUE(expectedValue)
					<< "Index " << t.count() << " should not have a value, but is within element " << element << ". "
					<< "newElementIndex: " << newElementIndex;
				if (expectedValue) {
					EXPECT_EQ(*expectedValue, element.getValue());
				}
			}
		}
	}
}

TEST(Timeline, set) {
	testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
		timeline.set(element);
	});
	testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
		timeline.set(element.getTimeRange(), element.getValue());
	});
	testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
		timeline.set(element.getStart(), element.getEnd(), element.getValue());
	});
}

TEST(Timeline, indexer_get) {
	Timeline<int> timeline { { 1_cs, 2_cs, 1 }, { 2_cs, 4_cs, 2 }, { 6_cs, 9_cs, 3 } };
	vector<optional<int>> expectedValues { none, 1, 2, 2, none, none, 3, 3, 3 };
	for (centiseconds t = 0_cs; t < 9_cs; ++t) {
		{
			optional<int> actual = timeline[t];
			EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
		}
		{
			optional<int> actual = timeline[t];
			EXPECT_EQ(expectedValues[t.count()], actual ? optional<int>(*actual) : none);
		}
		if (expectedValues[t.count()]) {
			{
				const int& actual = timeline[t];
				EXPECT_EQ(*expectedValues[t.count()], actual);
			}
			{
				int actual = timeline[t];
				EXPECT_EQ(*expectedValues[t.count()], actual);
			}
		}
	}
}

TEST(Timeline, indexer_set) {
	testSetter([](const Timed<int>& element, Timeline<int>& timeline) {
		for (centiseconds t = element.getStart(); t < element.getEnd(); ++t) {
			timeline[t] = element.getValue();
		}
	});
}

TEST(Timeline, joinAdjacent) {
	Timeline<int> timeline {
		{ 1_cs, 2_cs, 1 },
		{ 2_cs, 4_cs, 2 },
		{ 3_cs, 6_cs, 2 },
		{ 6_cs, 7_cs, 2 },
		// Gap
		{ 8_cs, 10_cs, 2 },
		{ 11_cs, 12_cs, 3 }
	};
	EXPECT_EQ(6, timeline.size());
	timeline.joinAdjacent();
	EXPECT_EQ(4, timeline.size());
	
	Timed<int> expectedJoined[] = {
		{ 1_cs, 2_cs, 1 },
		{ 2_cs, 7_cs, 2 },
		// Gap
		{ 8_cs, 10_cs, 2 },
		{ 11_cs, 12_cs, 3 }
	};
	EXPECT_THAT(timeline, ElementsAreArray(expectedJoined));
}

TEST(Timeline, autoJoin) {
	JoiningTimeline<int> timeline {
		{ 1_cs, 2_cs, 1 },
		{ 2_cs, 4_cs, 2 },
		{ 3_cs, 6_cs, 2 },
		{ 6_cs, 7_cs, 2 },
		// Gap
		{ 8_cs, 10_cs, 2 },
		{ 11_cs, 12_cs, 3 }
	};
	Timed<int> expectedJoined[] = {
		{ 1_cs, 2_cs, 1 },
		{ 2_cs, 7_cs, 2 },
		// Gap
		{ 8_cs, 10_cs, 2 },
		{ 11_cs, 12_cs, 3 }
	};
	EXPECT_EQ(4, timeline.size());
	EXPECT_THAT(timeline, ElementsAreArray(expectedJoined));
}

TEST(Timeline, shift) {
	Timeline<int> timeline { { 1_cs, 2_cs, 1 }, { 2_cs, 5_cs, 2 }, { 7_cs, 9_cs, 3 } };
	Timeline<int> expected { { 3_cs, 4_cs, 1 }, { 4_cs, 7_cs, 2 }, { 9_cs, 11_cs, 3 } };
	timeline.shift(2_cs);
	EXPECT_EQ(expected, timeline);
}

TEST(Timeline, equality) {
	vector<Timeline<int>> timelines = {
		Timeline<int> {},
		Timeline<int> { { 1_cs, 2_cs, 0 } },
		Timeline<int> { { 1_cs, 2_cs, 1 } },
		Timeline<int> { { -10_cs, 0_cs, 0 } }
	};

	for (size_t i = 0; i < timelines.size(); ++i) {
		for (size_t j = 0; j < timelines.size(); ++j) {
			if (i == j) {
				EXPECT_EQ(timelines[i], Timeline<int>(timelines[j])) << "i: " << i << ", j: " << j;
			} else {
				EXPECT_NE(timelines[i], timelines[j]) << "i: " << i << ", j: " << j;
			}
		}
	}
}