#include "StdAfx.h"
#include "fs/CopyStream.h"
#include "fs/IStreamSink.h"
#include "fs/IStreamSource.h"
#include <tl/functional.h>

#include "fs/IFilesystem.h"

namespace fs
{

static constexpr size_t k_defaultBufferSize = 100 * 1024;

CopyStreamResult copyStream(IStreamSink& sink, IStreamSource& source, size_t bufferSize)
{
	return copyStream(sink, source, nullptr, bufferSize);
}

CopyStreamResult copyStream(IStreamSink& sink, IStreamSource& source, const ProcessDataCallback& dataCallback, size_t bufferSize)
{
	if (bufferSize == 0)
		bufferSize = source.getPreferredBufferSize();

	if (bufferSize == 0)
		bufferSize = sink.getPreferredBufferSize();

	if (bufferSize == 0)
		bufferSize = k_defaultBufferSize;

	tl::memory_buffer buffer;
	buffer.resize_uninitialized(bufferSize);

	uint64_t size = 0;
	do
	{
		const uint64_t read = source.read(buffer);
		tl::optional<Error> optLastError = source.getLastError();
		if (optLastError.has_value())
			return *optLastError;

		if (read == 0)
			return size;

		if (dataCallback)
			dataCallback({ buffer.data(), static_cast<size_t>(read) });

		const uint64_t written = sink.write({ buffer.data(), static_cast<size_t>(read) });
		if (written != read)
			return tl::make_error<Error>(ErrorCode::SystemError, "Failed to write {} bytes. Only {} went through", read, written);

		size += written;

		optLastError = sink.getLastError();
		if (optLastError.has_value())
			return *optLastError;
	} while (!source.isEOS());

	return size;
}

//////////////////////////////////////////////////////////////////////////

CopyStreamResult copyFile(IStreamSink& sink, const IFilesystem& filesystem, AbsPathView filePath, size_t bufferSize)
{
	OUTCOME_TRY(const auto source, filesystem.openStreamSource(filePath, IFilesystem::SourceFlags(IFilesystem::SourceFlag::SequentialHint, IFilesystem::SourceFlag::Unbuffered)));
	return copyStream(sink, *source, bufferSize);
}

//////////////////////////////////////////////////////////////////////////

CopyStreamResult copyFile(IStreamSink& sink, const IFilesystem& filesystem, AbsPathView filePath, const ProcessDataCallback& dataCallback, size_t bufferSize)
{
	OUTCOME_TRY(const auto source, filesystem.openStreamSource(filePath, IFilesystem::SourceFlags(IFilesystem::SourceFlag::SequentialHint, IFilesystem::SourceFlag::Unbuffered)));
	return copyStream(sink, *source, dataCallback, bufferSize);
}

CopyStreamResult copyFile(IFilesystem& dstFilesystem, AbsPathView dstFilePath, const IFilesystem& srcFilesystem, AbsPathView srcFilePath, size_t bufferSize)
{
	OUTCOME_TRY(const auto sink, dstFilesystem.openStreamSink(dstFilePath, fs::Mode::CreateOrOpenAndClear));
	OUTCOME_TRY(const auto source, srcFilesystem.openStreamSource(srcFilePath, IFilesystem::SourceFlags(IFilesystem::SourceFlag::SequentialHint, IFilesystem::SourceFlag::Unbuffered)));
	return copyStream(*sink, *source, nullptr, bufferSize);
}

//////////////////////////////////////////////////////////////////////////
}
