#pragma once

#include <thread>
#include <mutex>
#include <queue>
#include <string>
#include <memory>
#include <vector>
#include <filesystem>

struct subprocess_s;

struct MinecraftServerOptions
{
    std::string argv0 = "";
    int view_distance = 5;
};

/// @brief Singleton wrapper around a Minecraft server subprocess instance.
/// Built to be used in a single-threaded way (to follow catch2 requirements).
class MinecraftServer
{
private:
    MinecraftServer();
    ~MinecraftServer();
    MinecraftServer(const MinecraftServer&) = delete;
    MinecraftServer(MinecraftServer&&) = delete;
    MinecraftServer& operator=(const MinecraftServer&) = delete;

public:
    static MinecraftServerOptions options;
    static MinecraftServer& GetInstance();

    void Initialize();

    /// @brief Wait for the subprocess to write a line to stdout or stderr. Thread safe
    /// @param timeout_ms If != 0 and no line received after timeout_ms, will throw an exception
    /// @return The received line content
    std::string WaitLine(const long long int timeout_ms = 0);

    /// @brief Wait for the subprocess to write a line matching regex to stdout or stderr.
    /// @param regex A regex pattern of the line to match
    /// @param timeout_ms If != 0 and no matching line received after timeout_ms, will throw an exception
    /// @return First element is the full line, other are all the captured groups from the regex
    std::vector<std::string> WaitLine(const std::string& regex, const long long int timeout_ms = 0);

    /// @brief Write a line to subprocess stdin. Not thread-safe.
    /// @param input Line to write
    void SendLine(const std::string& input);

    /// @brief Path to the folder holding the structure files
    /// @return The path in which the server loads the structures
    std::filesystem::path GetStructurePath() const;

private:
    /// @brief Terminate the subprocess
    void Kill();
    /// @brief Internal function used to retrieve stdout and stderr
    void InternalThreadRead();
    /// @brief Prepare a folder to host a server
    void InitServerFolder(const std::filesystem::path& path);
    /// @brief Set a gamerule value on the server
    void SetGamerule(const std::string& gamerule, const std::string& value);
    /// @brief Set all non default gamerules on the server
    void InitServerGamerules();

private:
    /// @brief Thread running the stdout reading loop
    std::thread read_thread;
    /// @brief Internal mutex for the std::queue lines 
    std::mutex internal_read_mutex;
    /// @brief Queue of lines generated by the subprocess
    std::queue<std::string> lines;

    /// @brief Server subprocess handle
    std::unique_ptr<subprocess_s> subprocess;

    bool running;

    std::filesystem::path server_path;
};
