/***
 *
 *	Copyright (c) 1996-2002, Valve LLC. All rights reserved.
 *
 *	This product contains software technology licensed from Id
 *	Software, Inc. ("Id Technology").  Id Technology (c) 1996 Id Software, Inc.
 *	All Rights Reserved.
 *
 *   Use, distribution, and modification of this source code and/or resulting
 *   object code is restricted to non-commercial enhancements to products from
 *   Valve LLC.  All other use, distribution, or modification is prohibited
 *   without written permission from Valve LLC.
 *
 ****/

#pragma once

/**
 *	@file
 *
 *	Functions, types and globals to load and use the GoldSource engine filesystem interface to read and write files.
 *	See the VDC for information on which search paths exist to be used as path IDs:
 *	https://developer.valvesoftware.com/wiki/GoldSource_SteamPipe_Directories
 */

#include <cstddef>
#include <ctime>
#include <string>
#include <vector>

#include "Platform.h"
#include "FileSystem.h"

#ifdef WIN32
constexpr char DefaultPathSeparatorChar = '\\';
constexpr char AlternatePathSeparatorChar = '/';
#else
constexpr char DefaultPathSeparatorChar = '/';
constexpr char AlternatePathSeparatorChar = '\\';
#endif

inline IFileSystem* g_pFileSystem = nullptr;

bool FileSystem_LoadFileSystem();
void FileSystem_FreeFileSystem();

std::string FileSystem_GetGameDirectory();

/**
 *	@brief Replaces occurrences of ::AlternatePathSeparatorChar with ::DefaultPathSeparatorChar.
 */
void FileSystem_FixSlashes(std::string& fileName);

/**
 *	@brief Returns the last modification time of the given file.
 *	Filenames are relative to the game directory.
 */
time_t FileSystem_GetFileTime(const char* fileName);

/**
 *	@brief Compares the file time of the given files located in the mod directory.
 *	@details Needed because IFileSystem::GetFileTime() does not provide a path ID parameter.
 *	@param filename1 First file to compare.
 *	@param filename2 Second file to compare.
 *	@param[out] iCompare Stores the result of the comparison.
 *		-@c 0 if equal
 *		-@c -1 if @p filename2 is newer than @p filename1
 *		-@c 1 if @p filename1 is newer than @p filename2
 *	@return @c true if filetimes were retrieved, false otherwise.
 */
bool FileSystem_CompareFileTime(const char* filename1, const char* filename2, int* iCompare);

enum class FileContentFormat
{
	Binary = 0,
	Text = 1
};

/**
 *	@brief Loads a file from disk into a buffer.
 *
 *	@details If the returned buffer contains text data and @p format is @c FileContentFormat::Text it is safe to cast the data pointer to char*:
 *	@code{.cpp}
 *	auto text = reinterpret_cast<char*>(buffer.data());
 *	@endcode
 *
 *	@param fileName Name of the file to load.
 *	@param format If @c FileContentFormat::Text, a null terminator will be appended.
 *	@param pathID If not null, only looks for the file in this search path.
 *	@return If the file was successfully loaded the contents of the buffer,
 *		with a zero byte (null terminator) appended to it if @p format is @c FileContentFormat::Text.
 *		If the file could not be loaded an empty buffer is returned.
 */
std::vector<std::byte> FileSystem_LoadFileIntoBuffer(const char* fileName, FileContentFormat format, const char* pathID = nullptr);

/**
 *	@brief Writes a text file to disk.
 *	@param fileName Name of the file to write to.
 *	@param text Null-terminated text to write. The null terminator is not written to disk.
 *	@param pathID If not null, writes to a writable location assigned to the given search path.
 *		Otherwise the first writable location will be used (in practice this will be the mod directory).
 *		If no writable location exists no file will be written to.
 *	@return True if the file was written, false if an error occurred.
 */
bool FileSystem_WriteTextToFile(const char* fileName, const char* text, const char* pathID = nullptr);

/**
 *	@brief Helper class to automatically close the file handle associated with a file.
 */
class FSFile
{
public:
	FSFile() noexcept = default;
	FSFile(const char* fileName, const char* options, const char* pathID = nullptr);

	FSFile(FSFile&& other) noexcept
		: m_Handle(other.m_Handle)
	{
		other.m_Handle = FILESYSTEM_INVALID_HANDLE;
	}

	FSFile& operator=(FSFile&& other) noexcept
	{
		if (this != &other)
		{
			Close();
			m_Handle = other.m_Handle;
			other.m_Handle = FILESYSTEM_INVALID_HANDLE;
		}

		return *this;
	}

	FSFile(const FSFile&) = delete;
	FSFile& operator=(const FSFile&) = delete;

	~FSFile();

	constexpr bool IsOpen() const { return m_Handle != FILESYSTEM_INVALID_HANDLE; }

	std::size_t Size() const { return static_cast<std::size_t>(g_pFileSystem->Size(m_Handle)); }

	bool Open(const char* filename, const char* options, const char* pathID = nullptr);
	void Close();

	void Seek(int pos, FileSystemSeek_t seekType);

	std::size_t Tell() const;

	int Read(void* dest, int size);

	int Write(const void* input, int size);

	template <typename... Args>
	int Printf(const char* format, Args&&... args)
	{
		return g_pFileSystem->FPrintf(m_Handle, format, std::forward<Args>(args)...);
	}

	constexpr operator bool() const { return IsOpen(); }

private:
	FileHandle_t m_Handle = FILESYSTEM_INVALID_HANDLE;
};

inline FSFile::FSFile(const char* filename, const char* options, const char* pathID)
{
	Open(filename, options, pathID);
}

inline FSFile::~FSFile()
{
	Close();
}

inline bool FSFile::Open(const char* filename, const char* options, const char* pathID)
{
	Close();

	m_Handle = g_pFileSystem->Open(filename, options, pathID);

	return IsOpen();
}

inline void FSFile::Close()
{
	if (IsOpen())
	{
		g_pFileSystem->Close(m_Handle);
		m_Handle = FILESYSTEM_INVALID_HANDLE;
	}
}

inline void FSFile::Seek(int pos, FileSystemSeek_t seekType)
{
	if (IsOpen())
	{
		g_pFileSystem->Seek(m_Handle, pos, seekType);
	}
}

inline std::size_t FSFile::Tell() const
{
	return static_cast<std::size_t>(g_pFileSystem->Tell(m_Handle));
}

inline int FSFile::Read(void* dest, int size)
{
	return g_pFileSystem->Read(dest, size, m_Handle);
}

inline int FSFile::Write(const void* input, int size)
{
	return g_pFileSystem->Write(input, size, m_Handle);
}
