#include "StdAfx.h"
#include "fs/FileMapSource.h"

#include "Logger.h"
#include <system_error>

#include "tl/narrow_cast.h"
#include "mio/mmap.hpp"

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

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

struct FileMapSource::Impl
{
	mio::ummap_source source;
};

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

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

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

FileMapSource::FileMapSource(AbsPathView filepath, uint64_t start, size_t size)
{
	std::error_code error;
	mio::ummap_source mmap;
	mmap.map(filepath.std_str(), start, size, error);
	if (error)
	{
		m_optLastError = tl::make_error<Error>(ErrorCode::SystemError, "{}", error.message());
		return;
	}

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

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

FileMapSource::~FileMapSource()
{
	m_impl = {};
}

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

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

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

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

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

	return static_cast<size_t>(sz);
}

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

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

	m_offset = tl::min(offset, getSize());
}

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

void FileMapSource::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 FileMapSource::tell() const
{
	if (m_optLastError.has_value() || !isValid())
	{
		TL_FAIL();
		return 0;
	}

	return m_offset;
}

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

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

	return m_impl->source.size();
}

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

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

	const uint64_t remainingBytes = getSize() - m_offset;
	const uint64_t bytesToMap = tl::min(remainingBytes, static_cast<uint64_t>(size));
	if (bytesToMap == 0)
		return {};

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

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

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

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

bool FileMapSource::isEOS() const
{
	if (!isValid())
		return true;

	return m_offset >= getSize();
}

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

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

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