#include "StdAfx.h"
#include "fs/FileMapSink.h"
#include <mio/mmap.hpp>

#include "fs/NativeFilesystem.h"
#include "tl/narrow_cast.h"

namespace fs
{
////////////////////////////////////////////////////////////////////////////

struct FileMapSink::Impl
{
	mio::ummap_sink sink;
};

FileMapSink::FileMapSink(AbsPathView filepath,
						size_t size,
						Mode mode /*= Mode::CreateOrOpenAndClear*/,
						Flags flags /*= Flags()*/)
{
	//create and write file
	{
		auto openResult = native.openSink(filepath, mode);
		if (openResult.has_value())
		{
			const auto fs = std::move(openResult.value());
			if (size > fs->getSize())
			{
				fs->seekBeg(size - 1);
				constexpr uint8_t buf[1] = { 0 };
				fs->write( { buf, 1 });
			}
		}
	}

	std::error_code error;
	mio::ummap_sink mmap;
	mmap.map(filepath.std_str(), 0, size, error);
	if (error)
	{
		m_optLastError = tl::make_error<Error>(ErrorCode::SystemError, "{}", error.message());
		return;
	}

	m_impl = tl::make_unique<Impl>(std::move(mmap));
}

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

FileMapSink::~FileMapSink()
{
	if (m_impl)
	{
		std::error_code error;
		m_impl->sink.sync(error);
	}
}

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

bool FileMapSink::isValid() const
{
	return m_impl != nullptr;
}

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

size_t FileMapSink::write(tl::span<const uint8_t> data)
{
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return 0;
	}

	const size_t sz = tl::min(data.size(), m_impl->sink.size() - tl::narrow<size_t>(m_offset));
	if (sz > 0)
	{
		memcpy(m_impl->sink.data() + m_offset, data.data(), sz);
		m_offset += sz;
	}

	return sz;
}

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

void FileMapSink::seekBeg(uint64_t offset)
{
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return;
	}

	if (m_offset != offset)
		m_offset = tl::min(offset, static_cast<uint64_t>(m_impl->sink.size()));
}

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

void FileMapSink::seekRel(int64_t offset)
{
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return;
	}

	if (offset != 0)
	{
		int64_t newOffset = static_cast<int64_t>(tell());
		newOffset += offset;
		newOffset = tl::max<int64_t>(newOffset, 0);
		seekBeg(static_cast<uint64_t>(tl::min(newOffset, (int64_t)getSize())));
	}
}

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

uint64_t FileMapSink::tell() const
{
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return 0;
	}

	return m_offset;
}

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

uint64_t FileMapSink::getSize() const
{	
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return 0;
	}

	return m_impl->sink.size();
}

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

tl::span<uint8_t> FileMapSink::map(size_t size)
{	
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return {};
	}

	const size_t bytesToMap = tl::min(m_impl->sink.size() - tl::narrow<size_t>(m_offset), size);
	if (bytesToMap == 0)
		return {};

	const tl::span<uint8_t> memView(m_impl->sink.data() + m_offset, bytesToMap);
	seekRel(bytesToMap);
	return memView;
}

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

tl::optional<Error> FileMapSink::getLastError() const
{
	tl::optional<Error> error = m_optLastError;
	m_optLastError = tl::nullopt;
	return error;
}

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

size_t FileMapSink::getPreferredBufferSize() const
{
	return 0;
}

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

}
