#pragma once

#include <xcb/xcb.h>

#include <vector>
#include <atomic>
#include <string>

class XwlSource;

class XwlProperty
{
public:
    XwlProperty(xcb_atom_t selection,
                xcb_atom_t target,
                xcb_timestamp_t timestamp,
                xcb_connection_t *xcbConn,
                XwlSource *source);

    virtual bool handlePropertyNotify(xcb_property_notify_event_t *event) = 0;

    void timeout();

    xcb_timestamp_t timestamp() const
    {
        return m_timestamp;
    }
    bool isFinished() const
    {
        return m_isFinished;
    }

    virtual bool isWirteFinished() = 0;

    xcb_atom_t targetProperty() const
    {
        return m_targetProperty;
    }
    bool incr() const
    {
        return m_incr;
    }
    void resetTimeout()
    {
        m_timeout = false;
    }

protected:
    virtual void endProperty() = 0;
    virtual void refreshProperty() = 0;

    xcb_atom_t atom() const
    {
        return m_atom;
    }
    void setIncr(bool set)
    {
        m_incr = set;
    }

protected:
    xcb_connection_t *m_xcbConn = nullptr;
    XwlSource *m_source = nullptr;
    xcb_atom_t m_targetProperty = XCB_ATOM_NONE;
    xcb_atom_t m_targetsAtom = XCB_ATOM_NONE;
    xcb_atom_t m_wlSelectionAtom = XCB_ATOM_NONE;
    xcb_atom_t m_incrAtom = XCB_ATOM_NONE;
    std::atomic<bool> m_isFinished{false};

private:
    xcb_atom_t m_atom = XCB_ATOM_NONE;
    xcb_timestamp_t m_timestamp = XCB_CURRENT_TIME;

    bool m_incr = false;
    bool m_timeout = false;
};

class XwlSendProperty : public XwlProperty
{
public:
    XwlSendProperty(xcb_atom_t selection,
                    xcb_selection_request_event_t *request,
                    xcb_connection_t *xcbConn,
                    XwlSource *source);
    virtual ~XwlSendProperty();

    void loadData(std::vector<char> &data);
    void loadData(std::string path);

    bool handlePropertyNotify(xcb_property_notify_event_t *event) override;

    void endProperty() override;
    void handlePropertyDelete();

    bool isWirteFinished() override
    {
        return true;
    }

private:
    void refreshProperty() override;
    void startIncr();
    int flushSourceData();

private:
    xcb_selection_request_event_t *m_request = nullptr;

    std::vector<std::pair<std::vector<char>, int>> m_chunks;

    bool m_propertyIsSet = false;
    bool m_flushPropertyOnDelete = false;
};

class DataReceiver
{
public:
    DataReceiver(int index);
    virtual ~DataReceiver();

    void readFromProperty(xcb_get_property_reply_t *reply);

    void setData(const char *value, int length);
    std::vector<char> data() const;
    std::string getPath() const;

    void partRead(int length);

    void wirteData();

    bool isWirteFinished()
    {
        return m_isWirteFinish;
    }

    void stop()
    {
        m_isWirteStop = true;
    }

private:
    xcb_get_property_reply_t *m_propertyReply = nullptr;
    std::vector<char> m_data;
    FILE      *m_file{nullptr};
    int       m_length{0};
    std::string       m_savePath;
    std::atomic<bool> m_isWirteFinish{false};
    std::atomic<bool> m_isWirteStop{false};
};

class XwlReadProperty : public XwlProperty
{
public:
    XwlReadProperty(xcb_atom_t selection,
                    xcb_atom_t target,
                    xcb_timestamp_t timestamp,
                    xcb_window_t parentWindow,
                    xcb_connection_t *xcbConn,
                    XwlSource *source,
                    int index,
                    std::string mimetype);
    virtual ~XwlReadProperty();

    bool handleSelectionNotify(xcb_selection_notify_event_t *event);
    bool handlePropertyNotify(xcb_property_notify_event_t *event) override;

    std::vector<char> data() const;

    bool isWirteFinished() override
    {
        if (m_receiver == NULL) {
            return true;
        } else {
            return m_receiver->isWirteFinished();
        }
    }

    std::string getPath();
    std::string getMimeType();

    bool isConvertFlag()
    {
        return m_covertFlag;
    }

    void setConcertFlag(bool flag)
    {
        m_covertFlag = flag;
    }

protected:
    void endProperty() override;

private:
    void refreshProperty() override;
    void getIncrChunk();
    void startReadProperty();

private:
    xcb_window_t m_window;
    DataReceiver *m_receiver = nullptr;
    int         m_index;
    std::string  m_mimetype;
    bool         m_covertFlag = false;
    pthread_t        m_xwldispatch;
    int          m_threadResult{-1};
};
