#include "StdAfx.h"
#include "fs/IFilesystem.h"
#include "fs/zip/ZipUtils.h"
#include "fs/zip/ZipPack.h"
#include "fs/WritableFolderPack.h"
#include "fs/CopyStream.h"
#include "fs/zip/ZipWriter.h"

namespace fs
{

namespace
{
UnzipResult unzip(IFilesystem& dstFilesystem, AbsPathView filePath, tl::unique_ref<ZipPack> zipPack)
{
	MakeFolderResult makeFolderResult = dstFilesystem.makeFolder(filePath);
	if (makeFolderResult.has_error())
		return makeFolderResult.error();

	for (const EnumerateEntry& ee: zipPack->enumerateRecursively(AbsPath("/")))
	{
		AbsPath dstPath = filePath + ee.path;
		OUTCOME_TRY(dstFilesystem.makeFolder(ee.isFolder ? AbsPathView(dstPath) : dstPath.parent()));
		if (!ee.isFolder)
		{
			OUTCOME_TRY(auto source, zipPack->openStreamSource(AbsPath("/") + ee.path));
			OUTCOME_TRY(auto sink, dstFilesystem.openStreamSink(dstPath, Mode::CreateOrOpenAndClear));
			OUTCOME_TRY(copyStream(*sink, *source));
		}
	}

	return tl::success();
}
}

UnzipResult unzipSource(IFilesystem& dstFilesystem, AbsPathView filePath, tl::unique_ref<IMapSource> source)
{
	OUTCOME_TRY(auto pack, ZipPack::create(std::move(source)));
	return unzip(dstFilesystem, filePath, std::move(pack));
}


UnzipResult unzipFile(IFilesystem& dstFilesystem, AbsPathView filePath, tl::lent_ref<const IFilesystem> filesystem, AbsPathView srcFilePath)
{
	OUTCOME_TRY(auto pack, ZipPack::create(std::move(filesystem), std::move(srcFilePath)));
	return unzip(dstFilesystem, filePath, std::move(pack));
}

ZipResult zipToSink(ISink& sink, tl::lent_ref<IFilesystem> filesystem, AbsPathView path, uint8_t compressionLevel)
{
	ZipWriter writer(sink);

	OUTCOME_TRY(const bool isFile, filesystem->isFile(path));
	if (isFile)
	{
		const auto openResult = filesystem->openSource(path);
		if (openResult.has_error())
			return tl::make_error<Error>(ErrorCode::SystemError, "Cannot open file '{}' zip: {}", path, openResult.error());

		const auto addResult = writer.addFile(path.str(), compressionLevel, *openResult.value());
		if (addResult.has_error())
			return tl::make_error<Error>(ErrorCode::SystemError, "Cannot add file '{}' zip: {}", path, addResult.error());
	}
	else
	{
		for (EnumerateEntry ee: filesystem->enumerateRecursively(path))
		{
			if (!ee.isFolder)
			{
				auto localPath = path + ee.path;
				const auto openResult = filesystem->openSource(localPath);
				if (openResult.has_error())
					return tl::make_error<Error>(ErrorCode::SystemError, "Cannot open file '{}' zip: {}", localPath, openResult.error());

				const auto addResult = writer.addFile(localPath.str(), compressionLevel, *openResult.value());
				if (addResult.has_error())
					return tl::make_error<Error>(ErrorCode::SystemError, "Cannot add file '{}' zip: {}", localPath, addResult.error());
			}
		}
	}

	writer.finish();
	return tl::success();
}

ZipResult zipToFile(IFilesystem& dstFilesystem, AbsPathView dstFilePath, tl::lent_ref<IFilesystem> filesystem, AbsPathView path, uint8_t compressionLevel)
{
	OUTCOME_TRY(const auto sink, dstFilesystem.openSink(dstFilePath, Mode::CreateOrOpenAndClear));
	return zipToSink(*sink, std::move(filesystem), path, compressionLevel);
}

}
