#include "StdAfx.h"

//#include "fs/FileIncludes.h"
#include "fs/FileSource.h"
#include "fs/NativeUtils.h"

#include <climits>

#include "tl/narrow_cast.h"

#include "fast_io.h"

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

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

namespace native_utils
{
extern int validateCaseSensitiveFilename(AbsPathView filepath, int fd);
#if defined (TL_PLATFORM_WINDOWS_FAMILY)
extern int validateCaseSensitiveFilename(AbsPathView filepath, HANDLE handle);
#endif
}

using FH = fast_io::native_file;

template<typename Arena>
FH& toFH(Arena& arena)
{
	static_assert(sizeof(arena) >= sizeof(FH));
	return *reinterpret_cast<FH*>(arena.data());
}
template<typename Arena>
const FH& toFH(const Arena& arena)
{
	static_assert(sizeof(arena) >= sizeof(FH));
	return *reinterpret_cast<const FH*>(arena.data());
}
template<typename Arena>
FH& constructFH(Arena& arena, FH&& f)
{
	static_assert(sizeof(arena) >= sizeof(FH));
	return *new (arena.data()) FH(std::move(f));
}
template<typename Arena>
void destructFH(Arena& arena)
{
	static_assert(sizeof(arena) >= sizeof(FH));
	toFH(arena).~FH();
}

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

FileSource::FileSource(FileSource&& other) noexcept
{
	swap(other);
}

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

FileSource& FileSource::operator=(FileSource&& other) noexcept
{
	if (this != &other)
	{
		FileSource tmp;
		tmp.swap(other);
		swap(tmp);
	}

	return *this;
}

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

FileSource::FileSource(AbsPathView filepath) noexcept
	: FileSource(filepath, Flags())
{
}

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

FileSource::FileSource(AbsPathView filepath, Flags flags) noexcept
{
	const tl::string pathStr = filepath.str();

	fast_io::open_mode mode = fast_io::open_mode::in | fast_io::open_mode::shared_delete;

	if (flags.test(Flag::SequentialHint))
		//mode |= fast_io::open_mode:;
		;
	else if (flags.test(Flag::RandomHint))
		mode |= fast_io::open_mode::random_access;

	auto& fh = constructFH(m_arena, fast_io::native_file(pathStr,  mode));
	m_isConstructed = true;
}

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

bool FileSource::isValid() const noexcept
{
	if (m_isConstructed)
	{
		auto& fh = toFH(m_arena);
		return fh.handle != nullptr;
	}
	return false;
}

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

FileSource::~FileSource() noexcept
{
	close();
	if (m_isConstructed)
		destructFH(m_arena);
}

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

void FileSource::close() noexcept
{
	if (m_optLastError.has_value())
	{
		TL_FAIL();
		return;
	}

	if (isValid())
	{
		auto& fh = toFH(m_arena);
		fh.close();
	}
}

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

void FileSource::swap(FileSource& other) noexcept
{
	tl::swap(m_isConstructed, other.m_isConstructed);
	tl::swap(m_arena, other.m_arena);
	tl::swap(m_offset, other.m_offset);
	tl::swap(m_size, other.m_size);
	tl::swap(m_optLastError, other.m_optLastError);
}

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

size_t FileSource::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()), getSize() - m_offset);
	if (sz == 0)
		return 0;

	auto it = fast_io::read(toFH(m_arena), data.begin(), data.begin() + sz);
	const int64_t sizeRead = std::distance(data.begin(), it);
	if (sizeRead < 0)
	{
		int err = errno;
		m_optLastError = tl::make_error<Error>(ErrorCode::SystemError, "Failed to read from file: ({}): {}", err,
											 strerror(err));
		TL_FAIL("{}", m_optLastError);
		return 0;
	}
	m_offset += static_cast<uint64_t>(sizeRead);

	return tl::narrow<size_t>(sizeRead);
}

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

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

	if (m_offset != offset)
		m_offset = fast_io::seek(toFH(m_arena), offset, fast_io::seekdir::beg);
}

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

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

	return m_offset;
}

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

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

	if (m_size == uint64_t(-1))
		m_size = fast_io::status(toFH(m_arena)).size;

	return m_size;
}

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

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

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

bool FileSource::isEOS() const
{
	if (!isValid())
		return true;
	return m_offset >= getSize();
}

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

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

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

}
