#include "StdAfx.h"
#include "fs/MemorySource.h"
#include "fs/MemorySink.h"
#include "tl/narrow_cast.h"

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

MemorySource::MemorySource(tl::span<const uint8_t> data)
{
	m_data.resize_uninitialized(data.size());
	if (!data.empty())
		memcpy(m_data.data(), data.data(), data.size());
}

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

MemorySource::MemorySource(MemorySink& sink)
{
	sink.swap(*this);
}

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

MemorySource::MemorySource(const tl::memory_buffer& memoryBuffer)
{
	TL_ASSERT(!memoryBuffer.empty());
	m_data = memoryBuffer;
}

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

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

	const size_t sz = tl::min(data.size(), m_data.size() - m_offset);
	if (sz == 0)
		return 0;

	memcpy(data.data(), m_data.data() + m_offset, sz);
	m_offset += sz;
	TL_ASSERT(m_offset <= getSize());
	return sz;
}

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

void MemorySource::seekBeg(uint64_t offset)
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return;
	}

	size_t realOffset;
	if constexpr (sizeof(uint64_t) > sizeof(size_t))
		realOffset = tl::narrow<size_t>(offset);
	else
		realOffset = offset;

	m_offset = tl::min(realOffset, m_data.size());
}

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

void MemorySource::seekRel(int64_t offset)
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return;
	}

	if (offset)
	{
		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 MemorySource::tell() const
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return 0;
	}

	return m_offset;
}

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

uint64_t MemorySource::getSize() const
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return 0;
	}

	return m_data.size();
}

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

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

	const size_t remainingBytes = m_data.size() - m_offset;
	const size_t bytesToMap = tl::min(size, remainingBytes);

	if (bytesToMap == 0)
		return {};

	const tl::span<const uint8_t> mView(&m_data[m_offset], bytesToMap);
	m_offset += bytesToMap;

	TL_ASSERT(m_offset <= getSize());
	
	return mView;
}

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

void MemorySource::swap(MemorySink& sink)
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return;
	}

	sink.swap(*this);
}

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

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

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

bool MemorySource::isEOS() const
{
	return m_offset >= getSize();
}

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

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

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