// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025 - 2026  Medusa Slockbower
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================

#ifndef FENNEC_FILESYSTEM_FILE_H
#define FENNEC_FILESYSTEM_FILE_H

#include <fennec/filesystem/path.h>
#include <fennec/format/format.h>

#include <fennec/string/cstring.h>
#include <fennec/string/string.h>
#include <fennec/string/wstring.h>

namespace fennec
{

///
/// \brief Mode flags for opening a file
///
/// fmode_binary and fmode_wide are independent of the other modes
///
/// \details Valid Flag Combinations
/// <table width="100%" class="fieldtable" id="table_fennec_LANGPROC_io_fmode">
/// <tr><th style="vertical-align: top">Flags
///     <th style="vertical-align: top">Description
///
/// <tr><td style="vertical-align: top">\emph{read}
///     <td style="vertical-align: top">Opens file as read-only, reading from start
///
/// <tr><td style="vertical-align: top">\emph{write}
///     <td style="vertical-align: top">Opens file as write-only, writing to end
///
/// <tr><td style="vertical-align: top">\emph{read | write}
///     <td style="vertical-align: top">Opens file as read-write, reading from start
///
/// <tr><td style="vertical-align: top">\emph{write | trunc}
///     <td style="vertical-align: top">Opens file as write-only, destroying contents
///
/// <tr><td style="vertical-align: top">\emph{read | write | trunc}
///     <td style="vertical-align: top">Opens file as read-write, destroying contents
/// </table>
enum fmode_ : uint8_t
{
	fmode_read      = 0b00000001 //!< Opens file for reading
,	fmode_write     = 0b00000010 //!< Opens file for writing
,	fmode_trunc     = 0b00000100 //!< Contents of the file will be destroyed, only compatible with write enabled modes
,	fmode_exclusive = 0b00001000 //!< Generates an error if the opened file is not empty
,   fmode_binary    = 0b00010000 //!< Open in binary mode
,	fmode_wide      = 0b00100000 //!< Opens a file in wide mode
};

///
/// \brief Structure for handling streams of data
///
/// \details operations, when errored, will return a corresponding error.
///          Use file::get_error() to check if an error is present and return a corresponding string.
///          Use file::clear_error() to clear the errored state.
///          Some operations, specifically file::rename() and file::copy().
///          <br>
///          This file paradigm is to subvert time-of-check time-of-use (TOCTOU) attacks. This involves a threat actor
///          reading that our application checks if a file exists, and replacing the file with another malicious file
///          or symlink between our call to open the file.
class file
{
// Constants ===========================================================================================================
public:

	/// \name Constants
	/// @{

	/// \brief value of an invalid position
	static constexpr size_t npos = -1;

	/// @}


// Validation Functions ================================================================================================
public:

	/// \name Validation Functions
	/// @{

	///
	/// \brief Check if the provided mode bitflags are a valid combination
	/// \param mode the bitfield
	/// \returns true if the combination of flags is valid, false otherwise
	static constexpr bool is_valid(uint8_t mode) {
		const bool t = mode & fmode_trunc;
		const bool x = mode & fmode_exclusive;
		const bool w = mode & fmode_write;

		// when x is true, t must be true
		// when t is true, w must be true
		return (t && x && w)
		||     (t && w)
		||    !(t || x);
	}

	/// @}


// STD Text Streams ====================================================================================================
public:

	/// \name STD Text Streams
	/// @{

	///
	/// \returns the c stdout stream
	static file& cout();

	///
	/// \returns the c stdin stream
	static file& cin();

	///
	/// \returns the c stderr stream
	static file& cerr();

	/// @}


// Constructors & Destructor ===========================================================================================
public:

	/// \name Constructors & Destructor
	/// @{

	///
	/// \brief Default constructor
	/// \details Initializes an empty stream
	file();

	///
	/// \details Initializes a stream pointing to \emph{path} opened with \emph{mode}
	/// \param path the path of the file
	/// \param mode the mode to open with
	file(const cstring& path, uint8_t mode)
		: file() {
		open(path, mode);
	}

	///
	/// \details Initializes a stream pointing to \emph{path} opened with \emph{mode}
	/// \param path the path of the file
	/// \param mode the mode to open with
	file(const string& path, uint8_t mode)
		: file() {
		open(path, mode);
	}

	///
	/// \brief Path constructor
	/// \details Initializes a stream pointing to \emph{path} opened with \emph{mode}
	/// \param path the path of the file
	/// \param mode the mode to open with
	file(const path& path, uint8_t mode)
		: file() {
		open(path, mode);
	}

	///
	/// \brief Move constructor
	/// \param file the stream to take ownership of
	file(file&& file) noexcept;

	///
	/// \brief Copy Constructor
	/// \details Deleted, no semantics for copying a stream. Holding multiple copies of a stream will cause conflicts.
	file(const file&) = delete;

	///
	/// \brief Destructor
	/// \details Flushes and closes an open stream
	~file();

	///@}


// Assignment ==========================================================================================================
public:

	/// \name Assignment
	/// @{


	///
	/// \brief Move assignment
	/// \param file the stream to take ownership of
	file& operator=(file&& file) noexcept;

	///
	/// \brief Copy Assignment
	/// \details Deleted, no semantics for copying a stream. Holding multiple copies of a stream will cause conflicts.
	/// \returns Deleted
	file& operator=(const file&) = delete;

	/// @}


// Properties ==========================================================================================================
public:

	/// \name Properties
	/// @{

	///
	/// \returns The path the stream
	const path& get_path() const {
		return _path;
	}

	///
	/// \returns The mode of the stream
	uint8_t mode() const {
		return _mode;
	}

	///
	/// \returns \emph{true} if there is a valid, open stream.
	bool is_open() const {
		return _handle != nullptr;
	}

	/// @}


// File Access =========================================================================================================
public:

	/// \name File Access
	/// @{

	///
	/// \param path the path to the file
	/// \param mode the mode flags to open the file with
	/// \returns \emph{false} on success, \emph{true} on error
	bool open(const cstring& path, uint8_t mode);

	///
	/// \param path the path to the file
	/// \param mode the mode flags to open the file with
	/// \returns \emph{false} on success, \emph{true} on error
	bool open(const string& path, uint8_t mode);

	///
	/// \brief Open a file
	/// \param path the path to the file
	/// \param mode the mode flags to open the file with
	/// \returns \emph{false} on success, \emph{true} on error
	bool open(const path& path, uint8_t mode);

	///
	/// \brief Close a stream
	/// \returns \emph{false} on success, \emph{true} on error
	bool close();

	///
	/// \brief Commit the streams buffer to the file
	/// \returns \emph{false} on success, \emph{true} on error
	bool commit();

	/// @}


// File Operations =====================================================================================================
public:

	/// \name File Operations
	/// @{

	///
	/// \brief closes the stream and erases the file
	/// \returns \emph{false} on success, \emph{true} on error
	bool erase();

	///
	/// \param path the new path
	/// \returns \emph{false} on success, \emph{true} on error
	///
	/// \details
	/// Copies contents to the new path, and erases the old file.
	/// * Attempts to open a write-only stream at path
	/// * Attempts to reopen this file as read-only
	/// * Copies the contents of this file to the new stream
	/// * Reopen the new stream with the flags of this file and binds to it
	/// * Closes the old file
	bool rename(const cstring& path);

	///
	/// \param path the new path
	/// \returns \emph{false} on success, \emph{true} on error
	///
	/// \details
	/// Copies contents to the new path, and erases the old file.
	/// * Attempts to open a write-only stream at path
	/// * Attempts to reopen this file as read-only
	/// * Copies the contents of this file to the new stream
	/// * Reopen the new stream with the flags of this file and binds to it
	/// * Closes the old file
	bool rename(const string& path);

	///
	/// \brief Rebind the stream.
	/// \param path the new path
	/// \returns \emph{false} on success, \emph{true} on error
	///
	/// \details
	/// Copies contents to the new path, and erases the old file.
	/// * Attempts to open a write-only stream at path
	/// * Attempts to reopen this file as read-only
	/// * Copies the contents of this file to the new stream
	/// * Reopen the new stream with the flags of this file and binds to it
	/// * Closes the old file
	bool rename(const path& path);

	///
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	///
	/// \details
	/// Copies the contents to a new stream at the provided path.
	/// * Attempts to open a write-only stream at path, <br>
	/// * Attempts to reopen this file as read-only, <br>
	/// * Copies the contents of this file to the new stream, <br>
	/// * Reopen the new stream with the flags of this file.
	file copy(const cstring& path);

	///
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	///
	/// \details
	/// Copies the contents to a new stream at the provided path.
	/// * Attempts to open a write-only stream at path, <br>
	/// * Attempts to reopen this file as read-only, <br>
	/// * Copies the contents of this file to the new stream, <br>
	/// * Reopen the new stream with the flags of this file.
	file copy(const string& path);

	///
	/// \brief Copy contents to a new file.
	/// \details Copies the contents of the current stream into a new stream bound to the file at the provided path.
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	///
	/// \details
	/// Copies the contents to a new stream at the provided path.
	/// * Attempts to open a write-only stream at path, <br>
	/// * Attempts to reopen this file as read-only, <br>
	/// * Copies the contents of this file to the new stream, <br>
	/// * Reopen the new stream with the flags of this file.
	file copy(const path& path);

	/// @}


// File Positioning ====================================================================================================
public:

	/// \name File Positioning
	/// @{

	///
	/// \returns the position index in the stream
	size_t get_pos() const;

	///
	/// \param i the new index to move to
	/// \returns \emph{false} on success, \emph{true} otherwise
	bool set_pos(size_t i);

	///
	/// \brief return to the start of the stream
	/// \returns \emph{false} on success, \emph{true} otherwise
	bool rewind();

	///
	/// \returns \emph{true} if the stream has reached the end of the file, \emph{false} otherwise
	bool eof() const;

	/// @}


// Binary Read Operations ==============================================================================================
public:

	/// \name Binary Read Operations
	/// @{

	///
	/// \brief binary read
	/// \param data the buffer to write to
	/// \param size the size of each object in bytes
	/// \param n the number of objects to read
	/// \returns the number of objects successfully read
	size_t read(void* data, size_t size, size_t n);

	///
	/// \brief type read
	/// \tparam T the type to read
	/// \param data the buffer to write to
	/// \param n the number of objects to read
	/// \returns the number of objects successfully read
	template<typename T>
	size_t read(T* data, size_t n) {
		return read(static_cast<void*>(data), sizeof(T), n);
	}

	///
	/// \brief type read
	/// \tparam T the type to read
	/// \tparam n the number of objects to read
	/// \param data the buffer to write to
	/// \returns the number of objects successfully read
	template<typename T, size_t n>
	size_t read(T (&data)[n]) {
		return read(static_cast<void*>(data), sizeof(T), n);
	}

	/// @}


// Binary Write Operations =============================================================================================
public:

	/// \name Binary Write Operations
	/// @{

	///
	/// \brief put a character at the current position in the stream
	/// \param c the character to put
	/// \returns \emph{false} on success, \emph{true} otherwise
	bool putc(char c);

	///
	/// \brief put a wide character at the current position in the stream
	/// \param c the character to put
	/// \returns \emph{false} on success, \emph{true} otherwise
	bool putwc(wchar_t c);

	///
	/// \brief write a buffer to at the current position in the stream
	/// \param data the buffer to read from
	/// \param size the size of each object in bytes
	/// \param n the number of objects to write
	/// \returns the number of objects successfully written
	size_t write(const void* data, size_t size, size_t n);

	///
	/// \brief write a character buffer to at the current position in the stream
	/// \tparam n the number of characters to write
	/// \param data the buffer to read from
	/// \returns the number of objects successfully written
	template<size_t n>
	size_t write(const char (&data)[n]) {
		return write(data, sizeof(char), n - 1);
	}

	///
	/// \brief write a wide character buffer to at the current position in the stream
	/// \tparam n the number of characters to write
	/// \param data the buffer to read from
	/// \returns the number of objects successfully written
	template<size_t n>
	size_t write(const wchar_t (&data)[n]) {
		return write(data, sizeof(wchar_t), n - 1);
	}

	///
	/// \brief write a buffer to at the current position in the stream
	/// \tparam T the object type to write
	/// \param data the buffer to read from
	/// \param n the number of objects to write
	/// \returns the number of objects successfully written
	template<typename T>
	size_t write(const T* data, size_t n) {
		return write(static_cast<const void*>(data), sizeof(T), n);
	}

	///
	/// \brief write a buffer to at the current position in the stream
	/// \tparam T the object type to write
	/// \tparam n the number of objects to write
	/// \param data the buffer to read from
	/// \returns the number of objects successfully written
	template<typename T, size_t n>
	size_t write(const T (&data)[n]) {
		return write(static_cast<const void*>(data), sizeof(T), n);
	}

	/// @}


// Read Operations =====================================================================================================
public:

	/// \name Read Operations
	/// @{

	///
	/// \returns the character read at the current position in the stream
	/// \details Advances the position by one character
	char getc();

	///
	/// \returns the wide character read at the current position in the stream
	/// \details Advances the position by one wide character
	wchar_t getwc();

	///
	/// \returns A string containing the characters from the current position to the next newline character
	/// \details Advances the position to the character following the next newline character
	string getline();

	///
	/// \returns A wide string containing the characters from the current position to the next newline character
	/// \details Advances the position to the character following the next newline character
	wstring getwline();

	/// @}


// Printing Operations =================================================================================================
public:

	/// \name Print Operations
	/// @{

	///
	/// \param str the string to print
	void print(const cstring& str);

	///
	/// \brief print a string to the stream
	/// \param str the string to print
	void print(const string&  str);

	///
	/// \param str the string to print
	void println(const cstring& str);

	///
	/// \brief print a string to the stream followed by a newline character
	/// \param str the string to print
	void println(const string&  str);

	///
	/// \brief print a formatted string to the stream
	/// \tparam ArgsT the argument types
	/// \param str the format string
	/// \param args the argument values
	template<typename...ArgsT>
	void printf(const cstring& str, ArgsT&&...args) {
		const string fmt = fennec::format(str, fennec::forward<ArgsT>(args)...);
		this->print(cstring(fmt.cstr(), fmt.length()));
	}

	/// @}




// Error Handling ======================================================================================================
public:

	/// \name Error Handling
	/// @{

	///
	/// \brief Returns the current error state.
	/// \returns A string containing the current error.
	cstring get_error() const { return { _error, ::strlen(_error) }; }

	///
	/// \brief clears the errored state
	void clear_error() { _error = nullptr; }

	/// @}

private:
	FILE* _handle;
	path    _path;
	uint8_t _mode;
	char*  _error;
};

}

#endif // FENNEC_FILESYSTEM_FILE_H
