#pragma once

#include "ikvm_input.hpp"

#include <mutex>
#include <string>
#include <vector>

namespace ikvm
{

/*
 * @class Video
 * @brief Sets up the V4L2 video device and performs read operations
 */
class Video
{
  public:
    /*
     * @brief Constructs Video object
     *
     * @param[in] p     - Path to the V4L2 video device
     * @param[in] input - Reference to the Input object
     * @param[in] fr    - desired frame rate of the video
     */
    Video(const std::string& p, Input& input, int fr = 30, int sub = 0);
    ~Video();
    Video(const Video&) = default;
    Video& operator=(const Video&) = default;
    Video(Video&&) = default;
    Video& operator=(Video&&) = default;

    /*
     * @brief Gets the video frame data
     *
     * @return Pointer to the video frame data
     */
    char* getData();
    /* @brief Performs read to grab latest video frame */
    void getFrame();
    /*
     * @brief Gets whether or not the video frame needs to be resized
     *
     * @return Boolean indicating if the frame needs to be resized
     */
    bool needsResize();
    /* @brief Performs the resize and re-allocates framebuffer */
    void resize();
    /* @brief Starts streaming from the video device */
    void start();
    /* @brief Stops streaming from the video device */
    void stop();
    /* @brief Restarts streaming from the video device */
    void restart()
    {
        stop();
        start();
    }

    /*
     * @brief Gets the desired video frame rate in frames per second
     *
     * @return Value of the desired frame rate
     */
    inline int getFrameRate() const
    {
        return frameRate;
    }
    /*
     * @brief Gets the size of the video frame data
     *
     * @return Value of the size of the video frame data in bytes
     */
    inline size_t getFrameSize() const
    {
        return buffers[lastFrameIndex].payload;
    }
    /*
     * @brief Gets the height of the video frame
     *
     * @return Value of the height of video frame in pixels
     */
    inline size_t getHeight() const
    {
        return height;
    }
    /*
     * @brief Gets the pixel format  of the video frame
     *
     * @return Value of the pixel format of video frame */
    inline uint32_t getPixelformat() const
    {
        return pixelformat;
    }
    /*
     * @brief Gets the width of the video frame
     *
     * @return Value of the width of video frame in pixels
     */
    inline size_t getWidth() const
    {
        return width;
    }
    /*
     * @brief Gets the subsampling of the video frame
     *
     * @return Value of the subsampling of video frame, 1:420/0:444
     */
    inline int getSubsampling() const
    {
        return subSampling;
    }
    /*
     * @brief Sets the subsampling of the video frame
     *
     * @return Value of the subsampling of video frame, 1:420/0:444
     */
    inline void setSubsampling(int _sub)
    {
        subSampling = _sub;
    }

    /* @brief Number of bits per component of a pixel */
    static const int bitsPerSample;
    /* @brief Number of bytes of storage for a pixel */
    static const int bytesPerPixel;
    /* @brief Number of components in a pixel (i.e. 3 for RGB pixel) */
    static const int samplesPerPixel;

  private:
    /*
     * @struct Buffer
     * @brief Store the address and size of frame data from streaming
     *        operations
     */
    struct Buffer
    {
        Buffer() : data(nullptr), queued(false), payload(0), size(0) {}
        ~Buffer() = default;
        Buffer(const Buffer&) = default;
        Buffer& operator=(const Buffer&) = default;
        Buffer(Buffer&&) = default;
        Buffer& operator=(Buffer&&) = default;

        void* data;
        bool queued;
        size_t payload;
        size_t size;
    };

    /*
     * @brief Boolean to indicate whether the resize was triggered during
     *        the open operation
     */
    bool resizeAfterOpen;
    /* @brief Indicates whether or not timings query was last sucessful */
    bool timingsError;
    /* @brief File descriptor for the V4L2 video device */
    int fd;
    /* @brief Desired frame rate of video stream in frames per second */
    int frameRate;
    /* @brief Buffer index for the last video frame */
    int lastFrameIndex;
    /* @brief Height in pixels of the video frame */
    size_t height;
    /* @brief Width in pixels of the video frame */
    size_t width;
    /* @brief jpeg's subsampling, 1:420/0:444 */
    int subSampling;
    /* @brief Reference to the Input object */
    Input& input;
    /* @brief Path to the V4L2 video device */
    const std::string path;
    /* @brief Streaming buffer storage */
    std::vector<Buffer> buffers;

    /* @brief Pixel Format  */
    uint32_t pixelformat;
};

} // namespace ikvm