/*
 * 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 "ArchiveWriter.hpp"
#include "LibArchiveHelper.hpp"
#include "ArchiveStreamBufferWriter.hpp"
#include "SimpleFileTypeShouldCompressChecker.hpp"
#include <archive.h>
#include <archive_entry.h>
#include <iostream>

namespace OpenVulkano
{
	ArchiveWriter::ArchiveWriter(const char* fileName, ArchiveConfiguration archiveConfiguration, const std::shared_ptr<spdlog::logger>& logger)
			: ArchiveBase(archive_write_new(), archive_entry_new(), logger)
			, m_archiveConfig(archiveConfiguration)
			, m_shouldCompress(&SimpleFileTypeShouldCompressChecker)
	{ //TODO error handling
		ChkErr(archive_write_set_format(m_archive, archiveConfiguration.GetLibArchiveArchiveType()));
		if (archiveConfiguration.type == ArchiveType::TAR)
		{
			ChkErr(archive_write_add_filter(m_archive, archiveConfiguration.GetLibArchiveCompressionType()));
		}
		if (archiveConfiguration.compression != CompressionType::NONE &&
			archiveConfiguration.compressionLevel > 0)
		{
			std::string level = "compression-level=" + std::to_string(archiveConfiguration.compressionLevel);
			ChkErr(archive_write_set_options(m_archive, level.c_str()));
		}
		ChkErr(archive_write_open_filename(m_archive, fileName));
	}

	ArchiveWriter::~ArchiveWriter()
	{
		if (m_asBuffer) { m_asBuffer->Close(); m_asBuffer = nullptr; }
		ChkErr(archive_write_close(m_archive));
		archive_entry_free(m_archiveEntry);
	}

	bool ArchiveWriter::AddFile(const char* fileName, const char* inArchiveName)
	{
		archive_entry_clear(m_archiveEntry);
		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(), m_archiveEntry), archiveDisk.get(), m_logger);
		//archive_read_disk_entry_from_file(archiveDisk, m_archiveEntry, -1, nullptr);
		archive_entry_set_pathname(m_archiveEntry, inArchiveName);
		ChkErr(archive_write_header(m_archive, m_archiveEntry));
		long result = LibArchiveHelper::CopyArchiveData(archiveDisk.get(), m_archive);
		ChkErr(result);
		LibArchiveHelper::CheckError(result, archiveDisk.get(), m_logger);
		ChkErr(archive_write_finish_entry(m_archive));
		m_bytesWritten += archive_entry_size(m_archiveEntry);
		return true;
	}

	bool ArchiveWriter::AddFile(const FileDescription& description, const void* buffer)
	{
		WriteHeader(description);
		bool ok = ChkErr(archive_write_data(m_archive, buffer, description.size));
		ChkErr(archive_write_finish_entry(m_archive));
		return ok;
	}

	bool ArchiveWriter::AddFile(const FileDescription& description, const std::vector<std::pair<const void*, size_t>>& buffers)
	{
		WriteHeader(description);
		for(const auto& buffer : buffers)
		{
			if (buffer.first == nullptr) continue;
			if (!ChkErr(archive_write_data(m_archive, buffer.first, buffer.second)))
			{
				return false;
			}
		}
		ChkErr(archive_write_finish_entry(m_archive));
		return true;
	}

	ArchiveOStream ArchiveWriter::AddFileStream(const FileDescription& description)
	{
		WriteHeader(description);
		return ArchiveOStream(m_asBuffer = std::make_shared<ArchiveStreamBufferWriter>(this, description.size));
	}

	void ArchiveWriter::WriteHeader(const FileDescription& fileDescription)
	{
		if (m_asBuffer) { m_asBuffer->Close(); m_asBuffer = nullptr; }
		if (m_archiveConfig.type.AllowsMixedCompressionLevels())
		{
			if (m_shouldCompress(fileDescription)) SetCompressed();
			else SetUncompressed();
		}
		archive_entry_clear(m_archiveEntry);
		archive_entry_set_pathname_utf8(m_archiveEntry, fileDescription.path.c_str());
		if (fileDescription.size != FileDescription::UNKNOWN_SIZE)
		{
			archive_entry_set_size(m_archiveEntry, fileDescription.size);
		}
		else
		{
			if (!m_archiveConfig.type.AllowsUnknownFileSize())
				throw std::invalid_argument("Only ZIP files allow files of unknown size");
			archive_entry_unset_size(m_archiveEntry);
		}
		archive_entry_set_filetype(m_archiveEntry, LibArchiveHelper::GetFileType(fileDescription.type));
		archive_entry_set_perm(m_archiveEntry, static_cast<mode_t>(fileDescription.permissions));
		archive_entry_set_ctime(m_archiveEntry, fileDescription.createTime, 0);
		archive_entry_set_mtime(m_archiveEntry, fileDescription.modTime, 0);
		ChkErr(archive_write_header(m_archive, m_archiveEntry));
		m_bytesWritten += fileDescription.size;
	}

	void ArchiveWriter::SetUncompressed()
	{
		if (!m_lastCompressed) return;
		m_lastCompressed = false;
		switch (m_archiveConfig.type)
		{
			case ArchiveType::ZIP: ChkErr(archive_write_zip_set_compression_store(m_archive));
			default: ;
		}
	}

	void ArchiveWriter::SetCompressed()
	{
		if (m_lastCompressed) return;
		m_lastCompressed = true;
		switch (m_archiveConfig.type)
		{
			case ArchiveType::ZIP: ChkErr(archive_write_zip_set_compression_deflate(m_archive));
			default: ;
		}
	}
}
