#include <gtest/gtest.h>

#include <QDir>
#include <QTemporaryDir>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

#include "codec/timecodemetadata.h"
#include "node/project/footage/footage.h"
#include "node/project/footage/footagedescription.h"

TEST(TimecodeMetadata, ParsesNonDropFrameTimecode)
{
	const olive::TimecodeMetadata::SourceTime parsed =
		olive::TimecodeMetadata::FromTimecodeString(
			QStringLiteral("01:02:03:12"), olive::core::rational(1, 24));

	ASSERT_TRUE(parsed.valid);
	EXPECT_EQ(parsed.source, QStringLiteral("timecode"));
	EXPECT_EQ(parsed.time, olive::core::rational(1 * 3600 + 2 * 60 + 3, 1) +
							   olive::core::rational(12, 24));
}

TEST(TimecodeMetadata, ParsesDropFrameTimecode)
{
	const olive::TimecodeMetadata::SourceTime parsed =
		olive::TimecodeMetadata::FromTimecodeString(
			QStringLiteral("00:01:00;02"), olive::core::rational(1001, 30000));

	ASSERT_TRUE(parsed.valid);
	EXPECT_EQ(parsed.source, QStringLiteral("timecode"));
	EXPECT_GT(parsed.time, olive::core::rational(59));
	EXPECT_LT(parsed.time, olive::core::rational(61));
}

TEST(TimecodeMetadata, ParsesBwfTimeReference)
{
	const olive::TimecodeMetadata::SourceTime parsed =
		olive::TimecodeMetadata::FromBwfTimeReference(
			QStringLiteral("96000"), 48000);

	ASSERT_TRUE(parsed.valid);
	EXPECT_EQ(parsed.source, QStringLiteral("bwf_time_reference"));
	EXPECT_EQ(parsed.time, olive::core::rational(2));
}

TEST(TimecodeMetadata, ParsesLargeBwfTimeReferenceWithoutTruncation)
{
	const olive::TimecodeMetadata::SourceTime parsed =
		olive::TimecodeMetadata::FromBwfTimeReference(
			QStringLiteral("4294967296"), 48000);

	ASSERT_TRUE(parsed.valid);
	EXPECT_GT(parsed.time, olive::core::rational(89478));
	EXPECT_LT(parsed.time, olive::core::rational(89479));
}

TEST(TimecodeMetadata, RejectsInvalidMetadata)
{
	EXPECT_FALSE(olive::TimecodeMetadata::FromTimecodeString(
					 QString(), olive::core::rational(1, 24))
					 .valid);
	EXPECT_FALSE(olive::TimecodeMetadata::FromBwfTimeReference(
					 QStringLiteral("not-a-number"), 48000)
					 .valid);
	EXPECT_FALSE(olive::TimecodeMetadata::FromBwfTimeReference(
					 QStringLiteral("123"), 0)
					 .valid);
	EXPECT_FALSE(olive::TimecodeMetadata::FromTimecodeString(
					 QStringLiteral("not-a-timecode"),
					 olive::core::rational(1, 24))
					 .valid);
}

TEST(TimecodeMetadata, FromBwfTimeReferenceZeroSampleRateIsInvalid)
{
	EXPECT_FALSE(olive::TimecodeMetadata::FromBwfTimeReference(
					 QStringLiteral("0"), 0)
					 .valid);
}

TEST(TimecodeMetadata, FootageDescriptionWithoutSourceStartTime)
{
	olive::FootageDescription desc(QStringLiteral("ffmpeg"));
	EXPECT_FALSE(desc.HasSourceStartTime());
}

TEST(TimecodeMetadata, FootageDescriptionCachesSourceStartTime)
{
	QTemporaryDir dir;
	ASSERT_TRUE(dir.isValid());
	const QString path =
		QDir(dir.path()).filePath(QStringLiteral("footage-cache.xml"));

	olive::FootageDescription desc(QStringLiteral("ffmpeg"));
	desc.SetSourceStartTime(olive::core::rational(96000, 48000),
							QStringLiteral("bwf_time_reference"));
	ASSERT_TRUE(desc.Save(path));

	olive::FootageDescription loaded;
	ASSERT_TRUE(loaded.Load(path));
	ASSERT_TRUE(loaded.HasSourceStartTime());
	EXPECT_EQ(loaded.source_start_time(), olive::core::rational(2));
	EXPECT_EQ(loaded.source_start_time_source(),
			  QStringLiteral("bwf_time_reference"));
}

TEST(TimecodeMetadata, FootagePersistsSourceStartTime)
{
	QString xml;
	QXmlStreamWriter writer(&xml);
	writer.writeStartDocument();
	writer.writeStartElement(QStringLiteral("custom"));
	writer.writeTextElement(QStringLiteral("timestamp"), QStringLiteral("0"));
	writer.writeStartElement(QStringLiteral("sourcestarttime"));
	writer.writeAttribute(QStringLiteral("source"),
						  QStringLiteral("timecode"));
	writer.writeCharacters(QStringLiteral("3600/1"));
	writer.writeEndElement();
	writer.writeEndElement();
	writer.writeEndDocument();

	QXmlStreamReader reader(xml);
	ASSERT_TRUE(reader.readNextStartElement());
	ASSERT_EQ(reader.name(), QStringLiteral("custom"));

	olive::Footage footage;
	ASSERT_TRUE(footage.LoadCustom(&reader, nullptr));
	ASSERT_TRUE(footage.HasSourceStartTime());
	EXPECT_EQ(footage.source_start_time(), olive::core::rational(3600));
	EXPECT_EQ(footage.source_start_time_source(), QStringLiteral("timecode"));
}
