#include "xwlproperty.h"
#include "xwlutils.h"
#include "xwlsource.h"
#include "../clipboarddataprocess.h"
extern "C"
{
#include "log.h"
}

#include <xcb/xfixes.h>

using namespace std;

// in Bytes: equals 64KB
static  uint32_t s_incrChunkSize = 63 * 1024;
const int g_wirteBuffer =  4096;

XwlProperty::XwlProperty(xcb_atom_t selection, xcb_atom_t target, xcb_timestamp_t timestamp, xcb_connection_t *xcbConn, XwlSource *source)
    : m_atom(selection)
    , m_targetProperty(target)
    , m_timestamp(timestamp)
    , m_xcbConn(xcbConn)
    , m_source(source)
{
    m_targetsAtom = XwlUtils::getAtom("TARGETS", xcbConn);
    m_wlSelectionAtom = XwlUtils::getAtom("WL_SELECTION", xcbConn);
    m_incrAtom = XwlUtils::getAtom("INCR", xcbConn);
}

void XwlProperty::timeout()
{
    if (m_timeout) {
        endProperty();
    }
    m_timeout = true;
}

XwlSendProperty::XwlSendProperty(xcb_atom_t selection, xcb_selection_request_event_t *request, xcb_connection_t *xcbConn, XwlSource *source)
    : XwlProperty(selection, request->target, 0, xcbConn, source)
    , m_request(request)
{

}

XwlSendProperty::~XwlSendProperty()
{
    delete m_request;
    m_request = nullptr;
}

void XwlSendProperty::loadData(std::vector<char> &data)
{
    if (data.empty()) {
        endProperty();
        return;
    }

    int loadedLen = 0;
    while (loadedLen < data.size()) {
        if (m_chunks.size() == 0 || m_chunks.back().second == s_incrChunkSize) {
            // append new chunk
            auto next = pair<std::vector<char>, int>();
            next.first.resize(s_incrChunkSize);
            next.second = 0;
            m_chunks.push_back(next);
        }

        const auto oldLen = m_chunks.back().second;
        const auto avail = s_incrChunkSize - m_chunks.back().second;

        int curAvail = data.size() - loadedLen;
        ssize_t readLen = -1;
        if (curAvail != 0) {
            readLen = curAvail > avail ? avail : curAvail;
        }
        if (readLen == -1) {
            log_error("Error reading in data.\n");
            // TODO: cleanup X side?
            endProperty();
            return;
        }
        std::copy(data.begin() + loadedLen, data.begin() + loadedLen + readLen, m_chunks.back().first.begin());
        loadedLen += readLen;
        m_chunks.back().second = oldLen + readLen;

        if (loadedLen == data.size() || readLen == 0) {
            // at the fd end - complete transfer now
            m_chunks.back().first.resize(m_chunks.back().second);

            if (incr()) {
                // incremental transfer is to be completed now
                m_flushPropertyOnDelete = true;
                if (!m_propertyIsSet) {
                    // flush if target's property is not set at the moment
                    flushSourceData();
                }
            } else {
                // non incremental transfer is to be completed now,
                // data can be transferred to X client via a single property set
                flushSourceData();
                m_source->sendSelectionNotify(m_request, true);
                endProperty();
            }
        } else if (m_chunks.back().second == s_incrChunkSize) {
            // first chunk full, but not yet at fd end -> go incremental
            if (incr()) {
                m_flushPropertyOnDelete = true;
                if (!m_propertyIsSet) {
                    // flush if target's property is not set at the moment
                    flushSourceData();
                }
            } else {
                // starting incremental transfer
                startIncr();
            }
        }
        refreshProperty();
    }
}

void XwlSendProperty::loadData(string path)
{
    //todo
    vector<char> vData = ClipboardDataProcess::getData(path.c_str());
    loadData(vData);
}

void XwlSendProperty::startIncr()
{
    uint32_t mask[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
    xcb_change_window_attributes(m_xcbConn,
                                 m_request->requestor,
                                 XCB_CW_EVENT_MASK, mask);

    // spec says to make the available space larger
    const uint32_t chunkSpace = 1024 + s_incrChunkSize;
    xcb_change_property(m_xcbConn,
                        XCB_PROP_MODE_REPLACE,
                        m_request->requestor,
                        m_request->property,
                        m_incrAtom,
                        32, 1, &chunkSpace);
    xcb_flush(m_xcbConn);

    setIncr(true);
    // first data will be flushed after the property has been deleted
    // again by the requestor
    m_flushPropertyOnDelete = true;
    m_propertyIsSet = true;
    m_source->sendSelectionNotify(m_request, true);
}

int XwlSendProperty::flushSourceData()
{
    if (m_chunks.empty()) {
        return 0;
    }

    xcb_change_property(m_xcbConn,
                        XCB_PROP_MODE_REPLACE,
                        m_request->requestor,
                        m_request->property,
                        m_request->target,
                        8,
                        m_chunks[0].first.size(),
                        m_chunks[0].first.data());
    xcb_flush(m_xcbConn);

    m_propertyIsSet = true;
    refreshProperty();

    int size = m_chunks[0].first.size();
    m_chunks.erase(m_chunks.begin());
    return size;
}

void XwlSendProperty::endProperty()
{
    m_isFinished = true;
}

bool XwlSendProperty::handlePropertyNotify(xcb_property_notify_event_t *event)
{
    if (event->window == m_request->requestor) {
        if (event->state == XCB_PROPERTY_DELETE && event->atom == m_request->property) {
            handlePropertyDelete();
        }
        return true;
    }
    return false;
}
void XwlSendProperty::handlePropertyDelete()
{
    if (!incr()) {
        // non-incremental transfer: nothing to do
        return;
    }
    m_propertyIsSet = false;

    if (m_flushPropertyOnDelete) {
        if (m_chunks.empty()) {
            uint32_t mask[] = {0};
            xcb_change_window_attributes(m_xcbConn,
                                         m_request->requestor,
                                         XCB_CW_EVENT_MASK, mask);

            xcb_change_property(m_xcbConn,
                                XCB_PROP_MODE_REPLACE,
                                m_request->requestor,
                                m_request->property,
                                m_request->target,
                                8, 0, nullptr);
            xcb_flush(m_xcbConn);
            m_flushPropertyOnDelete = false;
            endProperty();
        } else if (!m_chunks.empty()) {
            flushSourceData();
        }
    }
}

void XwlSendProperty::refreshProperty()
{
    m_source->refreshSendProperty();
}

DataReceiver::DataReceiver(int index)
{
    m_savePath = ClipboardDataProcess::getClipboardDir(cachedataDir) + "/" + std::to_string(ClipboardDataProcess::getCurrentTime()) + "_" + std::to_string(index) + ".bin";
}

DataReceiver::~DataReceiver()
{
    if (m_propertyReply) {
        free(m_propertyReply);
        m_propertyReply = nullptr;
    }
}

void DataReceiver::readFromProperty(xcb_get_property_reply_t *reply)
{
    m_propertyReply = reply;

    setData(static_cast<char *>(xcb_get_property_value(reply)),
            xcb_get_property_value_length(reply));
}

void DataReceiver::setData(const char *value, int length)
{
    // simply set data without copy
    int curSize = m_data.size();
    m_data.resize(curSize + length);
    m_length += length;
    std::copy(value, value + length, m_data.begin() + curSize);
}

vector<char> DataReceiver::data() const
{
    return m_data;
}

string DataReceiver::getPath() const
{
    return m_savePath;
}

void DataReceiver::wirteData()
{
    m_file = ClipboardDataProcess::create_file_header(m_savePath.c_str(), 1);
    if (m_file) {
        int vNum = m_length / g_wirteBuffer;
        for (int i = 0; i  < vNum; i++) {
            // 创建一个新的vector来存储拷贝的数据
            std::vector<char> copied_data(m_data.begin() + i * g_wirteBuffer, m_data.begin() + (i + 1) *g_wirteBuffer);
            ClipboardDataProcess::write_file_data(m_file, (char *)&copied_data[0], g_wirteBuffer);
            if (m_isWirteStop)
                break;
        }
        if (!m_isWirteStop) {
            int vRemainder = m_length % g_wirteBuffer;
            // 创建一个新的vector来存储拷贝的数据
            std::vector<char> Remainder_data(m_data.begin() + vNum * g_wirteBuffer, m_data.begin() + vNum * g_wirteBuffer + vRemainder);
            ClipboardDataProcess::write_file_data(m_file, (char *)&Remainder_data[0], vRemainder);
            ClipboardDataProcess::update_file_size(m_file, m_length);
        }
        ClipboardDataProcess::close_file(m_file);
        m_file = nullptr;
    }
    m_isWirteFinish = true;
}

XwlReadProperty::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, string mimetype)
    : XwlProperty(selection, target, timestamp, xcbConn, source), m_mimetype(mimetype), m_index(index)
{
    m_window = xcb_generate_id(xcbConn);
    const uint32_t values[] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE};
    xcb_create_window(xcbConn,
                      XCB_COPY_FROM_PARENT,
                      m_window,
                      parentWindow,
                      0, 0,
                      10, 10,
                      0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT,
                      XCB_COPY_FROM_PARENT,
                      XCB_CW_EVENT_MASK,
                      values);
    // convert selection
    xcb_convert_selection(xcbConn,
                          m_window,
                          selection,
                          target,
                          m_wlSelectionAtom,
                          timestamp);

    xcb_flush(xcbConn);
}

XwlReadProperty::~XwlReadProperty()
{
    xcb_destroy_window(m_xcbConn, m_window);
    xcb_flush(m_xcbConn);
    if (m_receiver) {
        if (m_threadResult == 0) {
            //while (!m_receiver->isWirteFinished());
            m_receiver->stop();
            pthread_join(m_xwldispatch, NULL);
        }
        delete m_receiver;
        m_receiver = nullptr;
    }
}

vector<char> XwlReadProperty::data() const
{
    if (m_receiver && m_isFinished) {
        return m_receiver->data();
    }
    return vector<char>();
}

string XwlReadProperty::getPath()
{
    if (m_receiver)
        return  m_receiver->getPath();
    return string();
}

string XwlReadProperty::getMimeType()
{
    return m_mimetype;
}

bool XwlReadProperty::handlePropertyNotify(xcb_property_notify_event_t *event)
{
    if (event->window == m_window) {
        if (event->state == XCB_PROPERTY_NEW_VALUE && event->atom == m_wlSelectionAtom) {
            getIncrChunk();
        }
        return true;
    }
    return false;
}

bool XwlReadProperty::handleSelectionNotify(xcb_selection_notify_event_t *event)
{
    if (event->requestor != m_window) {
        return false;
    }
    if (event->selection != atom()) {
        return false;
    }
    if (event->property == XCB_ATOM_NONE) {
        log_error("Incoming X selection conversion failed.\n");
        return true;
    }
    if (event->target == m_targetsAtom) {
        log_error("Received targets too late.\n");
        return true;
    }
    if (m_receiver) {
        // second selection notify element - misbehaving source
        return true;
    }

    m_receiver = new DataReceiver(m_index);

    startReadProperty();
    return true;
}
void *xwl_wirteData(void *arg)
{
    DataReceiver *t_data = (DataReceiver *)arg;
    t_data->wirteData();
    return NULL;
}

void XwlReadProperty::endProperty()
{
    if (m_receiver && data().empty())
        m_threadResult = pthread_create(&m_xwldispatch, NULL, xwl_wirteData, m_receiver);
    m_isFinished = true;
}

void XwlReadProperty::refreshProperty()
{
    m_source->refreshReadProperty();
}

void XwlReadProperty::startReadProperty()
{
    auto cookie = xcb_get_property(m_xcbConn,
                                   1,
                                   m_window,
                                   m_wlSelectionAtom,
                                   XCB_GET_PROPERTY_TYPE_ANY,
                                   0,
                                   0x1fffffff);

    auto *reply = xcb_get_property_reply(m_xcbConn, cookie, nullptr);
    if (reply == nullptr) {
        log_error("Can't get selection property.\n");
        endProperty();
        return;
    }

    if (reply->type == m_incrAtom) {
        refreshProperty();
        setIncr(true);
        free(reply);
    } else {
        setIncr(false);
        // reply's ownership is transferred
        m_receiver->readFromProperty(reply);
        endProperty();
    }
}

void XwlReadProperty::getIncrChunk()
{
    if (!incr()) {
        // source tries to sent incrementally, but did not announce it before
        return;
    }
    if (!m_receiver) {
        // receive mechanism has not yet been setup
        return;
    }

    auto cookie = xcb_get_property(m_xcbConn,
                                   0,
                                   m_window,
                                   m_wlSelectionAtom,
                                   XCB_GET_PROPERTY_TYPE_ANY,
                                   0,
                                   0x1fffffff);

    auto *reply = xcb_get_property_reply(m_xcbConn, cookie, nullptr);
    if (!reply) {
        log_error("Can't get selection property.\n");
        endProperty();
        return;
    }

    if (xcb_get_property_value_length(reply) > 0) {
        // reply's ownership is transferred
        m_receiver->readFromProperty(reply);
        if (!incr()) {
            endProperty();
        } else {
            refreshProperty();
        }
    } else {
        // Transfer complete
        free(reply);
        endProperty();
    }
}
