// =====================================================================================================================
//  fennec, a free and open source game engine
//  Copyright © 2025  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_LANGPROC_IO_FILE_H
#define FENNEC_LANGPROC_IO_FILE_H

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

#include <fennec/langproc/strings/cstring.h>
#include <fennec/langproc/strings/string.h>
#include <fennec/langproc/strings/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">`read`
///     <td style="vertical-align: top">Opens file as read-only, reading from start
///
/// <tr><td style="vertical-align: top">`write`
///     <td style="vertical-align: top">Opens file as write-only, writing to end
///
/// <tr><td style="vertical-align: top">`read | write`
///     <td style="vertical-align: top">Opens file as read-write, reading from start
///
/// <tr><td style="vertical-align: top">`write | trunc`
///     <td style="vertical-align: top">Opens file as write-only, destroying contents
///
/// <tr><td style="vertical-align: top">`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().
class file
{
public:
	/// \brief value of an invalid position
	static constexpr size_t npos = -1;

	///
	/// \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);
	}

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

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

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

	///
	/// \brief default constructor, initializes an empty stream
	file();

	///
	/// \brief default destructor, cleans up an open stream
	~file();

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

	file& operator=(file&& file) noexcept;

	// don't allow copying streams
	file(const file&) = delete;


// 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 true if there is a valid, open stream.
	bool is_open() const {
		return _handle != nullptr;
	}


// File Access =========================================================================================================

	///
	/// \brief open a file
	/// \param path the path to the file
	/// \param mode the mode flags to open the file with
	/// \returns false on success, true on error
	bool open(const cstring& 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 false on success, 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 false on success, true on error
	bool open(const path& path, uint8_t mode);

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

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


// File Operations =====================================================================================================

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

	///
	/// \brief rebinds the stream, copying contents to path, and erasing the old file
	/// \param path the new path
	/// \returns false on success, true on error
	///
	/// \details 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);

	///
	/// \brief rebinds the stream, copying contents to path, and erasing the old file
	/// \param path the new path
	/// \returns false on success, true on error
	///
	/// \details 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 rebinds the stream, copying contents to path, and erasing the old file
	/// \param path the new path
	/// \returns false on success, true on error
	///
	/// \details 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);

	///
	/// \brief copies the contents of this file to path.
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	file copy(const cstring& path);

	///
	/// \brief copies the contents of this file to path.
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	file copy(const string& path);

	///
	/// \brief copies the contents of this file to path.
	/// \param path the path to copy to
	/// \returns a file at the new path with the copied contents
	file copy(const path& path);


// File Positioning ====================================================================================================

	size_t get_pos() const;
	bool   set_pos(size_t i);
	bool   rewind();
	bool   eof() const;


// Read Operations =====================================================================================================

	char getc();
	wchar_t getwc();

	size_t read(void* data, size_t size, size_t n);

	template<typename T>
	size_t read(T* data, size_t n) {
		return read(static_cast<void*>(data), sizeof(T), n);
	}

	template<typename T, size_t n>
	size_t read(T (&data)[n]) {
		return read(static_cast<void*>(data), sizeof(T), n);
	}

	string getline();
	wstring getwline();


// Write Operations ====================================================================================================

	bool putc(char c);
	bool putwc(wchar_t c);

	size_t write(const void* data, size_t size, size_t n);

	template<size_t n>
	size_t write(const char (&data)[n]) {
		return write(data, sizeof(char), n - 1);
	}

	template<size_t n>
	size_t write(const wchar_t (&data)[n]) {
		return write(data, sizeof(wchar_t), n - 1);
	}

	template<typename T>
	size_t write(const T* data, size_t n) {
		return write(static_cast<const void*>(data), sizeof(T), n);
	}

	template<typename T, size_t n>
	size_t write(const T (&data)[n]) {
		return write(static_cast<const void*>(data), sizeof(T), n);
	}


// Printing Operations =================================================================================================




// Error Handling ======================================================================================================

	const char* get_error() const { return _error; }
	void clear_error() { _error = nullptr; }

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

}

#endif // FENNEC_LANGPROC_IO_FILE_H
