/* -*- mode: c++ -*-
 * Copyright 2025 Dracal Technologies Inc. All rights reserved.
 */

#pragma once

#include <dracal/common/platform.hpp>

#if defined(DRACAL_HAS_FILESYSTEM)
    #include <filesystem>
namespace fs = std::filesystem;
#elif defined(DRACAL_HAS_EXPERIMENTAL_FILESYSTEM)
    #include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
    // cppcheck-suppress preprocessorErrorDirective
    #error "No filesystem support available"
#endif

#include <cstdint>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>

namespace dracal::common {

namespace fs = std::filesystem;

/**
 * Create an empty file if missing.
 * @param[in] filename Path to file as string
 * @param[in] overwrite If true, truncate the file if it already exists
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool touch(const std::string &filename, const bool overwrite = false, const bool quiet = false) noexcept;

/**
 * Create an empty file if missing.
 * @param[in] path Path to file
 * @param[in] overwrite If true, truncate the file if it already exists
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool touch(const fs::path &path, const bool overwrite = false, const bool quiet = false) noexcept;

/**
 * Open a file stream, creating the file if it does not exist.
 * @param[out] file Output file stream to open
 * @param[in] filename Path to file as string
 * @param[in] overwrite If true, truncate the file if it already exists
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 * @note The caller is responsible for closing the file stream.
 * @see close(std::ofstream &, const std::string &, bool)
 */
bool touch(std::ofstream &file, const std::string &filename, const bool overwrite = false,
           const bool quiet = false) noexcept;

/**
 * Open a file stream, creating the file if it does not exist.
 * @param[out] file Output file stream to open
 * @param[in] path Path to file
 * @param[in] overwrite If true, truncate the file if it already exists
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 * @note The caller is responsible for closing the file stream.
 * @see close(std::ofstream &, const std::string &, bool)
 */
bool touch(std::ofstream &file, const fs::path &path, const bool overwrite = false, const bool quiet = false) noexcept;

/**
 * Open a file stream.
 * @param[out] file Input file stream to open
 * @param[in] filename Path to file as strin
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 * @note The caller is responsible for closing the file stream.
 * @see close(std::ofstream &, const std::string &, bool)
 */
bool open(std::ifstream &file, size_t &size, const std::string &filename, const bool quiet = false) noexcept;

/**
 * Open a file stream.
 * @param[out] file Input file stream to open
 * @param[in] path Path to file
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 * @note The caller is responsible for closing the file stream.
 * @see close(std::ofstream &, const std::string &, bool)
 */
bool open(std::ifstream &file, size_t &size, const fs::path &path, const bool quiet = false) noexcept;

/**
 * Append lines to an open file stream.
 * @param[out] file Open output file stream
 * @param[in] lines Vector of strings to append
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool write_append(std::ofstream &file, const std::vector<std::string> &lines, const bool quiet = false) noexcept;

/**
 * Read a plain-old-data value from an open binary file stream.
 * @param[in,out] file Open input file stream to read from
 * @param[in,out] read_size Accumulator incremented by the number of bytes successfully read
 * @param[out] data Destination value to fill
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
template <typename buffer>
bool read_buffer(std::ifstream &file, size_t &read_size, buffer &data, const bool quiet = false) noexcept;

/**
 * Read a string of fixed length from an open binary file stream.
 * @param[in,out] file Open input file stream to read from
 * @param[in,out] read_size Accumulator incremented by the number of bytes successfully read
 * @param[out] str Destination string; must be pre-sized to the number of bytes to read
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
template <> bool read_buffer(std::ifstream &file, size_t &read_size, std::string &str, const bool quiet) noexcept;

/**
 * Read a vector of plain-old-data values from an open binary file stream.
 * @param[in,out] file Open input file stream to read from
 * @param[in,out] read_size Accumulator incremented by the number of bytes successfully read
 * @param[out] vec Destination vector; must be pre-sized to the number of elements to read
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
template <typename T>
bool read_buffer(std::ifstream &file, size_t &read_size, std::vector<T> &vec, const bool quiet = false) noexcept;

/**
 * Read multiple plain-old-data values from an open binary file stream in a single call.
 * @param[in,out] file Open input file stream to read from
 * @param[in,out] read_size Accumulator incremented by the number of bytes successfully read
 * @param[in] quiet If true, suppress error messages
 * @param[out] args Destination values to fill, in order
 * @return true on success, false on failure
 */
template <typename... buffers>
bool read_buffers(std::ifstream &file, size_t &read_size, const bool quiet, buffers &...args) noexcept {
    static_assert((std::is_trivially_copyable_v<buffers> && ...), "all buffer types must be plain old data");
    std::vector<char> raw(static_cast<std::size_t>((sizeof(buffers) + ...)));
    if (!read_buffer(file, read_size, raw, quiet)) {
        return false;
    }
    std::size_t offset = 0;
    ((std::memcpy(&args, raw.data() + offset, sizeof(buffers)), offset += sizeof(buffers)), ...);
    return true;
}

/**
 * Close an open file stream.
 * @param[out] file Open output file stream to close
 * @param[in] filename Path to file as string (used for error messages)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool close(std::ofstream &file, const std::string &filename, const bool quiet = false) noexcept;

/**
 * Close an open file stream.
 * @param[out] file Open output file stream to close
 * @param[in] filename Path to file (used for error messages)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool close(std::ofstream &file, const fs::path &filename, const bool quiet = false) noexcept;

/**
 * Close an open file stream.
 * @param[out] file Open output file stream to close
 * @param[in] filename Path to file as string (used for error messages)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool close(std::ifstream &file, const std::string &filename, const bool quiet = false) noexcept;

/**
 * Close an open file stream.
 * @param[out] file Open output file stream to close
 * @param[in] filename Path to file (used for error messages)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool close(std::ifstream &file, const fs::path &filename, const bool quiet = false) noexcept;

/**
 * Read entire file contents into a string
 * @param[in] filename Path to file as string
 * @param[out] text Output string to store file contents
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool read_file(const std::string &filename, std::string &text, const bool quiet = false) noexcept;

/**
 * Read entire file contents into a string
 * @param[in] file Path to file
 * @param[out] text Output string to store file contents
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool read_file(const fs::path &file, std::string &text, const bool quiet = false) noexcept;

/**
 * Write string to file
 * @param[in] filename Path to file as string
 * @param[in] text String to write
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool write_file(const std::string &filename, const std::string &text, const bool quiet = false) noexcept;

/**
 * Write vector of strings to file
 * @param[in] filename Path to file as string
 * @param[in] lines Vector of strings to write
 * @param[in] add_newlines If true, add newlines between lines (but not after the last line)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool write_file(const std::string &filename, const std::vector<std::string> &lines, const bool add_newlines = false,
                const bool quiet = false) noexcept;

/**
 * Write string to file
 * @param[in] file Path to file
 * @param[in] text String to write
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool write_file(const fs::path &file, const std::string &text, const bool quiet = false) noexcept;

/**
 * Write vector of strings to file
 * @param[in] file Path to file
 * @param[in] lines Vector of strings to write
 * @param[in] add_newlines If true, add newlines between lines (but not after the last line)
 * @param[in] quiet If true, suppress error messages
 * @return true on success, false on failure
 */
bool write_file(const fs::path &file, const std::vector<std::string> &lines, const bool add_newlines = false,
                const bool quiet = false) noexcept;

/**
 * Write data to a printer device using POSIX low-level I/O.
 * This function is only available on POSIX systems (Linux, macOS, etc.)
 *
 * @param[in] device_path Path to the printer device (e.g., "/dev/usb/lp0")
 * @param[in] lines Vector of strings to write to the printer. Each line must end with '\n'
 * @param[in] quiet If true, suppresses warning messages
 * @return true on success, false on failure
 */
bool write_printer(const std::string &device_path, const std::vector<std::string> &lines,
                   const bool quiet = false) noexcept;

} // namespace dracal::common
