
#include "fs/BufferedSource.h"
#include <algorithm>

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

template< typename SourceType>
BufferedSource<SourceType>::BufferedSource(SourceType source, size_t bufferSize)
	: m_source(std::move(source))
{
	if (bufferSize < 8)
		bufferSize = 8;

	m_buffer.reserve(bufferSize);
}

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

template< typename SourceType>
tl::optional<Error> BufferedSource<SourceType>::getLastError() const
{
	tl::optional<Error> error = m_optLastError;
	m_optLastError = tl::nullopt;
	return error;
}

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

template< typename SourceType>
size_t BufferedSource<SourceType>::read(tl::span<uint8_t> data)
{
	if (data.empty() || m_endOfSource)
		return 0;

	auto bufferPtr = data.data();
	size_t totalSize = 0;
	size_t pendingSize = data.size();

	while (pendingSize)
	{
		if (m_buffer.empty() || m_bufferOffset >= m_buffer.size())
		{
			m_buffer.resize_uninitialized(m_buffer.capacity());
			m_bufferStartOffset = m_source.tell();
			m_bufferOffset = 0;
			const size_t readSize = m_source.read(m_buffer);
			m_buffer.resize_uninitialized(readSize);
			if (readSize == 0)
			{
				m_optLastError = m_source.getLastError();
				m_endOfSource = !m_optLastError.has_value(); //no error? then EOS
				return totalSize;
			}
		}

		TL_ASSERT(m_buffer.size());
		TL_ASSERT(m_buffer.size() > m_bufferOffset);
		const size_t frameSize = tl::min(m_buffer.size() - m_bufferOffset, pendingSize);
		TL_ASSERT(frameSize);

		memcpy(bufferPtr, m_buffer.data() + m_bufferOffset, frameSize);
		bufferPtr += frameSize;

		TL_ASSERT(data.size() >= frameSize);
		pendingSize -= frameSize;

		totalSize += frameSize;
		TL_ASSERT(totalSize <= data.size());

		m_bufferOffset += frameSize;
	}

	m_endOfSource = tell() >= getSize();

	return totalSize;
}

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

template< typename SourceType>
void BufferedSource<SourceType>::seekBeg(uint64_t offset)
{
	offset = tl::min(offset, m_source.getSize());
	if (offset < m_bufferStartOffset || offset >= m_bufferStartOffset + m_buffer.size())
	{
		m_source.seekBeg(offset);
		m_optLastError = m_source.getLastError();
		m_buffer.clear();
		m_bufferOffset = 0;
		m_bufferStartOffset = m_source.tell();
	}
	else
		m_bufferOffset = offset - m_bufferStartOffset;

	m_endOfSource = tell() >= getSize();
}

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

template< typename SourceType>
void BufferedSource<SourceType>::seekRel(int64_t offset)
{
	if (!offset)
		return;

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

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

template< typename SourceType>
uint64_t BufferedSource<SourceType>::tell() const
{
	return m_bufferStartOffset + m_bufferOffset;
}

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

template< typename SourceType>
uint64_t BufferedSource<SourceType>::getSize() const
{
	return m_source.getSize();
}

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

template< typename SourceType>
bool BufferedSource<SourceType>::isEOS() const
{
	return m_endOfSource;
}

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

template< typename SourceType>
size_t BufferedSource<SourceType>::getPreferredBufferSize() const
{
	return m_buffer.size();
}

}

