/*
 * 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 "MultiPartArchiveWriter.hpp"
#include "LibArchiveHelper.hpp"
#include <spdlog/fmt/fmt.h> //TODO replace with external fmt
#include <archive.h>
#include <archive_entry.h>

namespace OpenVulkano
{
	MultiPartArchiveWriter::MultiPartArchiveWriter(const std::string& dir, const std::string& fileNamePattern,
												   const ArchiveConfiguration& archiveConfiguration,
												   size_t sizeLimit, bool lazyCreation,
												   const std::shared_ptr<spdlog::logger>& logger)
			: m_fileSizeLimit(sizeLimit), m_archiveId(0), m_archiveConfig(archiveConfiguration)
			, m_dir(dir), m_fileNamePattern(fileNamePattern), m_logger(logger), m_lazyCreation(lazyCreation)
	{
		if (!m_dir.empty() && !std::filesystem::exists(m_dir))
		{
			std::filesystem::create_directories(m_dir);
		}
		if (!lazyCreation) StartNewFile();
	}

	MultiPartArchiveWriter::~MultiPartArchiveWriter()
	{
		bool deleteLast = m_writer && m_writer->GetTotalWrittenBytes() == 0;
		m_writer = nullptr;
		if (deleteLast) std::filesystem::remove(m_archives.back());
	}

	void MultiPartArchiveWriter::StartNewFile()
	{
		m_archives.push_back(m_dir / fmt::format(fmt::runtime(m_fileNamePattern), m_archiveId++));
#ifdef WIN32
		m_writer = std::make_unique<ArchiveWriter>(m_archives.back().string().c_str(), m_archiveConfig, m_logger);
#else
		m_writer = std::make_unique<ArchiveWriter>(m_archives.back().c_str(), m_archiveConfig, m_logger);
#endif
		if (m_shouldCompress) m_writer->SetShouldCompressFunction(m_shouldCompress);
	}

	void MultiPartArchiveWriter::CheckSize(size_t size)
	{
		if (!m_writer || m_fileSizeLimit < m_writer->GetTotalWrittenBytes() + size)
		{
			StartNewFile();
		}
	}

	bool MultiPartArchiveWriter::AddFile(const FileDescription& description, const void* buffer)
	{
		CheckSize(description.size);
		return m_writer->AddFile(description, buffer);
	}

	bool MultiPartArchiveWriter::AddFile(const FileDescription& description, const std::vector<std::pair<const void*, size_t>>& buffers)
	{
		CheckSize(description.size);
		return m_writer->AddFile(description, buffers);
	}

	bool MultiPartArchiveWriter::AddFile(const char* fileName, const char* inArchiveName)
	{
		std::unique_ptr<archive_entry, decltype(archive_entry_free)*> archiveEntry(archive_entry_new(), archive_entry_free);
		std::unique_ptr<archive, decltype(archive_free)*> archiveDisk(archive_read_disk_new(), archive_free);
		LibArchiveHelper::CheckError(archive_read_disk_open(archiveDisk.get(), fileName), archiveDisk.get(), m_logger);
		LibArchiveHelper::CheckError(archive_read_next_header2(archiveDisk.get(), archiveEntry.get()), archiveDisk.get(), m_logger);
		CheckSize(archive_entry_size(archiveEntry.get()));
		
		return m_writer->AddFile(fileName, inArchiveName);
	}

	ArchiveOStream MultiPartArchiveWriter::AddFileStream(const FileDescription& description)
	{ //TODO handle zip splits
		CheckSize(description.size);
		return m_writer->AddFileStream(description);
	}

	void MultiPartArchiveWriter::Move(const std::filesystem::path& newDir)
	{
		bool hasToMove = m_archives.size() > 1 || m_writer->GetTotalWrittenBytes() > 0;
		m_writer = nullptr; // Close current archive
		if (!newDir.empty() && !std::filesystem::exists(newDir))
		{
			std::filesystem::create_directories(newDir);
		}
		if (hasToMove)
		{ // Move all existing archives
			std::vector<std::filesystem::path> newArchivePaths;
			newArchivePaths.reserve(m_archives.size() + 1);
			for (const auto& file : m_archives)
			{
				newArchivePaths.push_back(newDir / file.filename());
				std::filesystem::rename(file, newArchivePaths.back());
			}
			m_archives = std::move(newArchivePaths);
		}
		else
		{
			m_archiveId = 0;
			std::filesystem::remove(m_archives.back());
			m_archives.clear();
		}
		m_dir = newDir;
		if (!m_lazyCreation) StartNewFile();
	}

	void MultiPartArchiveWriter::Split()
	{
		if (!m_writer || !m_writer->GetTotalWrittenBytes()) return; // Nothing has been written yet, no need to split file
		m_writer = nullptr;
	}

	void MultiPartArchiveWriter::SetShouldCompressFunction(const std::function<bool(const FileDescription&)>& shouldComp)
	{
		if (m_writer) m_writer->SetShouldCompressFunction(shouldComp);
		m_shouldCompress = shouldComp;
	}
}
