/**
 * @file File.h
 * @author apio (cloudapio.eu)
 * @brief A C++-friendly API for file access.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#pragma once
#include <fcntl.h>
#include <luna/Buffer.h>
#include <luna/Result.h>
#include <luna/SharedPtr.h>
#include <luna/StringView.h>
#include <os/Path.h>
#include <stdio.h>
#include <sys/types.h>

namespace os
{
    /**
     * @brief An object-oriented file handle, which is closed when all references to it go out of scope.
     */
    class File : public Shareable
    {
      public:
        /**
         * @brief The supported file opening modes.
         */
        enum OpenMode
        {
            ReadOnly = O_RDONLY,            // Open the file read-only.
            WriteOnly = O_WRONLY | O_TRUNC, // Open the file write-only, truncating it.
            ReadWrite = O_RDWR | O_TRUNC,   // Open the file read-write, truncating it.
            Append = O_WRONLY | O_APPEND,   // Open the file write-only, and append to it.
            ReadAppend = O_RDWR | O_APPEND, // Open the file read-write, and append to it.
        };

        /**
         * @brief Create a new File object from a file path, opening the file.
         *
         * @param path The path to open.
         * @param flags The opening mode to pass to open(2).
         * @return Result<SharedPtr<File>> A new File object, or ENOENT if the file did not exist.
         */
        static Result<SharedPtr<File>> open(const Path& path, OpenMode flags);

        /**
         * @brief Create a new File object from a file path, opening the file or creating it if it does not exist.
         *
         * @param path The path to open.
         * @param flags The opening mode to pass to open(2).
         * @param mode If a new file is created, the file permissions to apply to it. (default: u=rw,g=r,o=r)
         * @return Result<SharedPtr<File>> A new File object.
         */
        static Result<SharedPtr<File>> open_or_create(const Path& path, OpenMode flags, mode_t mode = 0644);

        /**
         * @brief Create a new File object from a file path, creating the file but erroring out if the file already
         * exists.
         *
         * @param path The path to open.
         * @param flags The opening mode to pass to open(2).
         * @param mode The file permissions to apply to the created file. (default: u=rw,g=r,o=r)
         * @return Result<SharedPtr<File>> A new File object, or EEXIST if the file already existed.
         */
        static Result<SharedPtr<File>> create(const Path& path, OpenMode flags, mode_t mode = 0644);

        /**
         * @brief If path is "-", return standard input (as is common for many CLI apps). Otherwise, open path for
         * reading.
         *
         * @param path The path to open.
         * @return Result<SharedPtr<File>> A new File object if path was not "-", or the File
         * object for standard input if path was "-".
         */
        static Result<SharedPtr<File>> open_input_file(StringView path);

        /**
         * @brief Returns the File corresponding to standard input.
         *
         * @return SharedPtr<File> The File object for standard input.
         */
        static SharedPtr<File> standard_input();

        /**
         * @brief Returns the File corresponding to standard output.
         *
         * @return SharedPtr<File> The File object for standard output.
         */
        static SharedPtr<File> standard_output();

        /**
         * @brief Returns the File corresponding to standard error.
         *
         * @return SharedPtr<File> The File object for standard error.
         */
        static SharedPtr<File> standard_error();

        /**
         * @brief Make this File object automatically close on the next call to execve(2).
         */
        void set_close_on_exec();

        /**
         * @brief Write a string to this File.
         *
         * @param str The string to write (can be not null-terminated).
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> write(StringView str);

        /**
         * @brief Write a memory buffer to this File.
         *
         * @param buf The buffer to write.
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> write(const Buffer& buf);

        /**
         * @brief Read a line from this File.
         *
         * @return Result<String> The line as an owned String object, or an empty string if there is no more
         * data to read.
         */
        Result<String> read_line();

        /**
         * @brief Read the entire File's contents as a string.
         *
         * @return Result<String> The file's contents as an owned String object.
         */
        Result<String> read_all_as_string();

        /**
         * @brief Read data from this File.
         *
         * @param buf The output buffer to store the data in. Will be resized to fit the data.
         * @param size The maximum number of bytes to read.
         * @return Result<void> Whether the operation succeeded.
         */
        Result<void> read(Buffer& buf, usize size);

        /**
         * @brief Read an object from this File.
         *
         * @tparam T The type of the object to read.
         * @param object A reference to the object in question.
         * @return Result<void> Whether the operation succeeded.
         */
        template <typename T> Result<void> read_typed(T& object)
        {
            TRY(raw_read((u8*)&object, sizeof(T)));
            return {};
        }

        /**
         * @brief Read the entire File's contents.
         *
         * @return Result<Buffer> The file's contents as an owned Buffer object.
         */
        Result<Buffer> read_all();

        /**
         * @brief Read a single character from the File.
         *
         * @return Result<int> A character value above 0 (to be cast to an unsigned char), or EOF (typically -1)
         * if there is no more data to read.
         */
        Result<int> getchar();

        /**
         * @brief Returns the file descriptor associated with this File.
         *
         * @return int The file descriptor.
         */
        int fd() const
        {
            return fileno(m_file);
        }

        /**
         * @brief Flushes the stream's buffers.
         */
        void flush();

        /**
         * @brief Move this File's file pointer back to the beginning.
         */
        void rewind();

        /**
         * @brief Buffering modes for a File.
         */
        enum BufferingMode
        {
            NotBuffered = _IONBF,
            LineBuffered = _IOLBF,
            FullyBuffered = _IOFBF,
        };

        /**
         * @brief Change the buffering mode of this File.
         *
         * @param mode The buffering mode.
         */
        void set_buffer(BufferingMode mode);

        File(Badge<File>);
        ~File();

      private:
        static Result<SharedPtr<File>> construct(const Path& path, int flags, mode_t mode);
        static void initialize_standard_streams();

        Result<usize> raw_read(u8* buf, usize length);
        Result<usize> raw_write(const u8* buf, usize length);

        FILE* m_file { nullptr };
    };

    /**
     * @brief Print a formatted string to standard output.
     *
     * @param fmt The format string (in the same format as printf(3)).
     * @param ... The format arguments.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> print(StringView fmt, ...);

    /**
     * @brief Print a newline-terminated formatted string to standard output.
     *
     * @param fmt The format string (in the same format as printf(3)).
     * @param ... The format arguments.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> println(StringView fmt, ...);

    /**
     * @brief Print a formatted string to standard error.
     *
     * @param fmt The format string (in the same format as printf(3)).
     * @param ... The format arguments.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> eprint(StringView fmt, ...);

    /**
     * @brief Print a newline-terminated formatted string to standard error.
     *
     * @param fmt The format string (in the same format as printf(3)).
     * @param ... The format arguments.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> eprintln(StringView fmt, ...);
}