#include "StdAfx.h"
#include "ZipFileSource.h"

extern "C"
{
#include "zip.h"
}
#include "fs/MemorySink.h"
#include "tl/narrow_cast.h"

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

ZipFileSource::ZipFileSource(tl::shared_ptr<ZipPack::ZipData> zipData, tl::unique_ref<zip_file> zipFile, size_t uncompressedSize) noexcept
	: m_zipData(std::move(zipData))
	, m_zipFile(std::move(zipFile))
	, m_uncompressedSize(uncompressedSize)
{
}

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

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

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

	const zip_int64_t read = fs_zip_fread(m_zipFile.get(), data.data(), sz);
	if (read < 0)
	{
		zip_error_t* error = fs_zip_file_get_error(m_zipFile.get());
		const int ze = fs_zip_error_code_zip(error);
		const int se = fs_zip_error_code_system(error);
		m_optLastError = tl::make_error<Error>(ErrorCode::SystemError, "Zip file error: {}/{} ('{}')", ze, se, zip_error_strerror(error));

		return 0;
	}

	m_offset += read;
	return read;
}

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

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

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

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

	const uint64_t newOffset = tl::clamp<uint64_t>(offset, 0u, m_uncompressedSize);
	if (newOffset != m_offset)
	{
		if (const zip_int8_t res = fs_zip_fseek(m_zipFile.get(), static_cast<int64_t>(offset), SEEK_SET); res < 0)
		{
			zip_error_t* error = fs_zip_file_get_error(m_zipFile.get());
			const int ze = fs_zip_error_code_zip(error);
			const int se = fs_zip_error_code_system(error);
			m_optLastError = tl::make_error<Error>(ErrorCode::SystemError, "Zip file seekBeg error: {}/{} ('{}')", ze, se, zip_error_strerror(error));
		}
		else
			m_offset = offset;
	}
}

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

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

	return m_offset;
}

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

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

	return m_uncompressedSize;
}

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

bool ZipFileSource::isEOS() const
{
	return m_offset >= m_uncompressedSize;
}

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

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

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