#pragma once

#include "ikvm_args.hpp"
#include "ikvm_input.hpp"
#include "ikvm_video.hpp"

#include <rfb/rfb.h>

#include <vector>

namespace ikvm
{
/*
 * @class Server
 * @brief Manages the RFB server connection and updates
 */
class Server
{
  public:
    /*
     * @struct ClientData
     * @brief Store necessary data for each connected RFB client
     */
    struct ClientData
    {
        /*
         * @brief Constructs ClientData object
         *
         * @param[in] s - Number of frames to skip when client connects
         * @param[in] i - Pointer to Input object
         */
        ClientData(int s, Input* i) : skipFrame(s), input(i), last_crc{-1}
        {
            needUpdate = false;
        }
        ~ClientData() = default;
        ClientData(const ClientData&) = default;
        ClientData& operator=(const ClientData&) = default;
        ClientData(ClientData&&) = default;
        ClientData& operator=(ClientData&&) = default;

        int skipFrame;
        Input* input;
        bool needUpdate;
        int64_t last_crc;
    };

    /*
     * @brief Constructs Server object
     *
     * @param[in] args - Reference to Args object
     * @param[in] i    - Reference to Input object
     * @param[in] v    - Reference to Video object
     */
    Server(const Args& args, Input& i, Video& v);
    ~Server();
    Server(const Server&) = default;
    Server& operator=(const Server&) = default;
    Server(Server&&) = default;
    Server& operator=(Server&&) = default;

    /* @brief Resizes the RFB framebuffer */
    void resize();
    /* @brief Executes any pending RFB updates and client input */
    void run();
    /* @brief Sends pending video frame to clients */
    void sendFrame();

    /*
     * @brief Indicates whether or not video data is desired
     *
     * @return Boolean to indicate whether any clients need a video frame
     */
    inline bool wantsFrame() const
    {
        return server->clientHead;
    }
    /*
     * @brief Get the Video object
     *
     * @return Reference to the Video object
     */
    inline const Video& getVideo() const
    {
        return video;
    }

  private:
    /*
     * @brief Handler for a client frame update message
     *
     * @param[in] cl - Handle to the client object
     * @param[in] furMsg - Pointer of the FUR message
     */
    static void clientFramebufferUpdateRequest(
        rfbClientPtr cl, rfbFramebufferUpdateRequestMsg* furMsg);
    /*
     * @brief Handler for a client disconnecting
     *
     * @param[in] cl - Handle to the client object
     */
    static void clientGone(rfbClientPtr cl);
    /*
     * @brief Handler for client connecting
     *
     * @param[in] cl - Handle to the client object
     */
    static enum rfbNewClientAction newClient(rfbClientPtr cl);

    /* @brief Performs the resize operation on the framebuffer */
    void doResize();

    /* @brief Boolean to indicate if a resize operation is on-going */
    bool pendingResize;
    /* @brief Number of frames handled since a client connected */
    int frameCounter;
    /* @brief Number of connected clients */
    unsigned int numClients;
    /* @brief Microseconds to process RFB events every frame */
    long int processTime;
    /* @brief Handle to the RFB server object */
    rfbScreenInfoPtr server;
    /* @brief Reference to the Input object */
    Input& input;
    /* @brief Reference to the Video object */
    Video& video;
    /* @brief Default framebuffer storage */
    std::vector<char> framebuffer;
    /* @brief Identical frames detection */
    bool calcFrameCRC;
    /* @brief Cursor bitmap width */
    static constexpr int cursorWidth = 20;
    /* @brief Cursor bitmap height */
    static constexpr int cursorHeight = 20;
    /* @brief Cursor bitmap */
    static constexpr char cursor[] =
        "                    "
        " x                  "
        " xx                 "
        " xxx                "
        " xxxx               "
        " xxxxx              "
        " xxxxxx             "
        " xxxxxxx            "
        " xxxxxxxx           "
        " xxxxxxxxx          "
        " xxxxxxxxxx         "
        " xxxxxxxxxxx        "
        " xxxxxxx            "
        " xxxxxxx            "
        " xxx  xxx           "
        " xx   xxx           "
        " x     xxx          "
        "       xxx          "
        "        x           "
        "                    ";
    /* @brief Cursor bitmap mask */
    static constexpr char cursorMask[] =
        " o                  "
        "oxo                 "
        "oxxo                "
        "oxxxo               "
        "oxxxxo              "
        "oxxxxxo             "
        "oxxxxxxo            "
        "oxxxxxxxo           "
        "oxxxxxxxxo          "
        "oxxxxxxxxxo         "
        "oxxxxxxxxxxo        "
        "oxxxxxxxxxxxo       "
        "oxxxxxxxoooo        "
        "oxxxxxxxo           "
        "oxxxooxxxo          "
        "oxxo oxxxo          "
        "oxo   oxxxo         "
        " o    oxxxo         "
        "       oxo          "
        "        o           ";
};

} // namespace ikvm