#include "StdAfx.h"

#include "fs/StreamSourceToMapSourceAdapter.h"

#include <algorithm>

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

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

static constexpr size_t k_chunkSize = 10 * 1024;

StreamSourceToMapSourceAdapter::StreamSourceToMapSourceAdapter(tl::unique_ref<IStreamSource> srcSource)
	: m_srcSource(std::move(srcSource))
{
}

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

tl::optional<Error> StreamSourceToMapSourceAdapter::getLastError() const
{
	return m_optLastError;
}

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

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

	if (data.empty())
		return 0;

	if (!m_readAll) 
	{
		// do we have enough data in the buffer?
		if (m_offset + data.size() > m_data.size()) // nope! read some more
		{
			const size_t toRead = tl::narrow<size_t>(m_offset + data.size() - m_data.size());

			const size_t dataSize = m_data.size();
			m_data.resize_uninitialized(dataSize + toRead);
			const uint64_t read = m_srcSource->read({ m_data.data() + dataSize, toRead });
			if (read < toRead || m_srcSource->isEOS())
			{
				m_data.resize_uninitialized(dataSize + tl::narrow<size_t>(read));
				m_data.shrink_to_fit();
				m_readAll = true;
			}
			m_optLastError = m_srcSource->getLastError();
		}
	}

	TL_ASSERT(m_data.size() >= m_offset);

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

	m_offset += sz;
	return sz;
}

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

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

	if (offset < m_data.size())
		m_offset = offset;
	else
	{
		readAll();
		m_offset = tl::min(offset, static_cast<uint64_t>(m_data.size()));
	}
}

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

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

	uint64_t newOffset = m_offset;
	if (offset > 0)
		newOffset += (uint64_t)offset;
	else
		newOffset -= (uint64_t)(-offset);

	seekBeg(newOffset);
}

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

uint64_t StreamSourceToMapSourceAdapter::tell() const
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return 0;
	}

	return m_offset;
}

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

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

	readAll();
	if (m_optLastError != tl::nullopt)
		return 0;

	return m_data.size();
}

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

bool StreamSourceToMapSourceAdapter::isEOS() const
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return true;
	}

	// we're not even at the end of our data.size, so definitely not EOS
	if (m_offset < m_data.size())
		return false;

	// if we finished the local buffer, check the src source
	TL_ASSERT(m_offset == m_data.size());
	return m_srcSource->isEOS();
}

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

size_t StreamSourceToMapSourceAdapter::getPreferredBufferSize() const
{
	return m_srcSource->getPreferredBufferSize();
}

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

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

	if (!m_readAll)
		readAll();

	if (m_optLastError.has_value())
		return {};

	TL_ASSERT(m_data.size() >= m_offset);

	const uint64_t offset = m_offset;
	const size_t maxSize = tl::min<size_t>(m_data.size() - tl::narrow<size_t>(m_offset), size);

	m_offset += maxSize;

	return {m_data.data() + offset, maxSize};
}

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

void StreamSourceToMapSourceAdapter::readAll() const
{
	if (!m_readAll)
	{
		if (auto readResult = fs::readStream(m_data, *m_srcSource); readResult.has_error())
		{
			m_optLastError = readResult.error();
			m_data.clear();
		}

		m_data.shrink_to_fit();
		m_readAll = true;
	}
}

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