/*
 * 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"
#include "IO/FileDescription.hpp"

using namespace OpenVulkano;

TEST_CASE("AddFile from buffer", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto archivePath = tempDir / "test_archive_from_buffer.zip";

	{
		ArchiveWriter writer(archivePath.string().c_str());
		std::string buffer = "Hello";
		FileDescription desc;
		desc.type = std::filesystem::file_type::regular;
		desc.path = "test.txt";
		desc.size = buffer.size();
		desc.permissions = (std::filesystem::perms) 0644;
		REQUIRE(writer.AddFile(desc, buffer.c_str()) == true);
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());
	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "test.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello");
}

TEST_CASE("AddFile from multiple buffers", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto archivePath = tempDir / "test_archive_from_buffers.zip";

	{
		ArchiveWriter writer(archivePath.string().c_str());
		FileDescription desc;
		desc.type = std::filesystem::file_type::regular;
		desc.path = "test.txt";
		desc.size = 10;
		desc.permissions = (std::filesystem::perms) 0644;

		const char* buffer1 = "Hello";
		const char* buffer2 = "World";
		std::vector<std::pair<const void*, size_t>> buffers = { { buffer1, std::strlen(buffer1) },
			                                                    { buffer2, std::strlen(buffer2) } };
		REQUIRE(writer.AddFile(desc, buffers) == true);
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());

	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "test.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "HelloWorld");
}

TEST_CASE("AddFile from file", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto testFilePath = tempDir / "test.txt";
	auto archivePath = tempDir / "test_archive_from_file.zip";

	std::ofstream testFile(testFilePath);
	testFile << "Hello from file";
	testFile.close();

	{
		ArchiveWriter writer(archivePath.string().c_str());
		REQUIRE(writer.AddFile(testFilePath.string().c_str(), "test.txt") == true);
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());

	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "test.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello from file");
}

TEST_CASE("AddFileStream", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto archivePath = tempDir / "test_archive_stream.zip";

	{
		ArchiveWriter writer(archivePath.string().c_str());
		std::string buffer = "Streamed data.";
		FileDescription desc;
		desc.type = std::filesystem::file_type::regular;
		desc.path = "stream.txt";
		desc.size = buffer.size();
		desc.permissions = (std::filesystem::perms) 0644;

		ArchiveOStream stream = writer.AddFileStream(desc);
		stream << buffer;
		stream.Close();
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());

	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "stream.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "Streamed data.");
}

TEST_CASE("Compression settings", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto archivePath = tempDir / "test_archive_compression.zip";

	{
		ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::GZIP, 9);
		ArchiveWriter writer(archivePath.string().c_str(), config);

		std::string buffer = "Hello";
		FileDescription desc;
		desc.type = std::filesystem::file_type::regular;
		desc.path = "compressed.txt";
		desc.size = buffer.size();
		desc.permissions = (std::filesystem::perms) 0644;
		writer.SetShouldCompressFunction([](const FileDescription&) { return true; });
		REQUIRE(writer.AddFile(desc, buffer.c_str()) == true);
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());

	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "compressed.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello");
}

TEST_CASE("Uncompressed settings", "[ArchiveWriter]")
{
	auto tempDir = AppFolders::GetAppTempDir();
	auto archivePath = tempDir / "test_archive_uncompressed.zip";

	{
		ArchiveConfiguration config(ArchiveType::ZIP, CompressionType::NONE, 0);
		ArchiveWriter writer(archivePath.string().c_str(), config);

		std::string buffer = "Hello";
		FileDescription desc;
		desc.type = std::filesystem::file_type::regular;
		desc.path = "uncompressed.txt";
		desc.size = buffer.size();
		desc.permissions = (std::filesystem::perms) 0644;

		writer.SetShouldCompressFunction([](const FileDescription&) { return false; });
		REQUIRE(writer.AddFile(desc, buffer.c_str()) == true);
	}

	ArchiveReader reader(archivePath.string().c_str());
	REQUIRE(reader.IsOpen());

	REQUIRE(reader.HasNext() == true);
	auto [fileDesc, fileData] = reader.GetNextFileAsVector().value();
	REQUIRE(fileDesc.path == "uncompressed.txt");
	REQUIRE(std::string(fileData.begin(), fileData.end()) == "Hello");
}