#include "xwlselection.h"
#include "xwlsource.h"
#include "xwlutils.h"
extern "C"
{
#include "log.h"
}

#include <xcb/xcb_event.h>
#include <xcb/xfixes.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

using namespace std;

XwlSelection::XwlSelection(xcb_atom_t atom, xcb_window_t parent, const xcb_query_extension_reply_t *queryExtension, xcb_connection_t *xcbConn)
    : m_parentWindow(parent)
    , m_atom(atom)
    , m_queryExtension(queryExtension)
    , m_xcbConn(xcbConn)
{
    m_window = xcb_generate_id(xcbConn);
    m_requestorWindow = m_window;
    m_targetsAtom = XwlUtils::getAtom("TARGETS", xcbConn);
    m_wlSelectionAtom = XwlUtils::getAtom("WL_SELECTION", xcbConn);
    m_timestampAtom = XwlUtils::getAtom("TIMESTAMP", xcbConn);
    m_deleteAtom = XwlUtils::getAtom("DELETE", xcbConn);
    xcb_flush(xcbConn);
}

XwlSelection::~XwlSelection()
{
    deleteX11Source();
}

void XwlSelection::sendSelectionNotify(xcb_selection_request_event_t *event, bool success)
{
    // Every X11 event is 32 bytes (see man xcb_send_event), so XCB will copy
    // 32 unconditionally. Use a union to ensure we don't disclose stack memory.
    union {
        xcb_selection_notify_event_t notify;
        char buffer[32];
    } u;
    memset(&u, 0, sizeof(u));
    static_assert(sizeof(u.notify) < 32, "wouldn't need the union otherwise");
    u.notify.response_type = XCB_SELECTION_NOTIFY;
    u.notify.sequence = 0;
    u.notify.time = event->time;
    u.notify.requestor = event->requestor;
    u.notify.selection = event->selection;
    u.notify.target = event->target;
    u.notify.property = success ? event->property : xcb_atom_t(XCB_ATOM_NONE);

    xcb_send_event(m_xcbConn, 0, event->requestor, XCB_EVENT_MASK_NO_EVENT, (const char *)&u);
    xcb_flush(m_xcbConn);
}

bool XwlSelection::createX11Source(xcb_xfixes_selection_notify_event_t *event)
{
    if (event && (event->owner == XCB_WINDOW_NONE || event->owner == m_window)) {
        if (m_x11Source && event->owner == XCB_WINDOW_NONE) {
            m_x11Source->processOwnSelection();
        }
        return false;
    }
    deleteX11Source();
    m_x11Source = new X11Source(this, event);
    return true;
}

void XwlSelection::deleteX11Source()
{
    if (m_x11Source) {
        delete m_x11Source;
        m_x11Source = nullptr;
    }
}

void XwlSelection::clearEndPropertys()
{
    if (m_x11Source) {
        m_x11Source->clearEndPropertys();
    }
}

void XwlSelection::timeout()
{
    if (m_x11Source) {
        m_x11Source->timeout();
    }
}

void XwlSelection::ownSelection(bool own)
{
    log_info("Whether to set owner: %d\n", own);
    if (own) {
        xcb_set_selection_owner(m_xcbConn,
                                m_window,
                                m_atom,
                                XCB_TIME_CURRENT_TIME);
    } else {
        m_disownPending = true;
        xcb_set_selection_owner(m_xcbConn,
                                XCB_WINDOW_NONE,
                                m_atom,
                                m_timestamp);
    }
    xcb_flush(m_xcbConn);
}

bool XwlSelection::handleXfixesNotify(xcb_xfixes_selection_notify_event_t *event)
{
    if (event->selection != m_atom) {
        return false;
    }
    if (event->window != m_window) {
        return false;
    }
    if (m_disownPending) {
        // notify of our own disown - ignore it
        m_disownPending = false;
        return true;
    }
    if (event->owner == m_window) {
        // When we claim a selection we must use XCB_TIME_CURRENT,
        // grab the actual timestamp here to answer TIMESTAMP requests
        // correctly
        m_timestamp = event->timestamp;
        return true;
    }

    // Being here means some other X window has claimed the selection.
    doHandleXfixesNotify(event);
    return true;
}

bool XwlSelection::handleSelectionRequest(xcb_selection_request_event_t *event)
{
    if (event->selection != m_atom) {
        return false;
    }
    if (m_window != event->owner || !m_x11Source) {
        if (event->time < m_timestamp) {
            // cancel earlier attempts at receiving a selection
            // TODO: is this for sure without problems?
            sendSelectionNotify(event, false);
            return true;
        }
        return false;
    }
    return m_x11Source->handleSelectionRequest(event);
}

bool XwlSelection::handleSelectionNotify(xcb_selection_notify_event_t *event)
{
    if (m_x11Source && m_x11Source->handleSelectionNotify(event)) {
        m_x11Source->startReadPropertys();
        // ownSelection(true);
        return true;
    }
    if (m_x11Source) {
        return m_x11Source->handleReadSelectionNotify(event);
    }
    return false;
}

bool XwlSelection::handlePropertyNotify(xcb_property_notify_event_t *event)
{
    if (m_x11Source) {
        if (m_x11Source->handleReadPropertyNotify(event)) {
            return true;
        } else {
            return m_x11Source->handleSendPropertyNotify(event);
        }
    }
    return false;
}

bool XwlSelection::filterEvent(xcb_generic_event_t *event)
{
    switch (event->response_type & XCB_EVENT_RESPONSE_TYPE_MASK) {
    case XCB_SELECTION_NOTIFY:
        return handleSelectionNotify(reinterpret_cast<xcb_selection_notify_event_t *>(event));
    case XCB_PROPERTY_NOTIFY:
        return handlePropertyNotify(reinterpret_cast<xcb_property_notify_event_t *>(event));
    case XCB_SELECTION_REQUEST:
        return handleSelectionRequest(reinterpret_cast<xcb_selection_request_event_t *>(event));
    case XCB_CLIENT_MESSAGE:
        return handleClientMessage(reinterpret_cast<xcb_client_message_event_t *>(event));
    default:
        if (m_queryExtension && event->response_type == m_queryExtension->first_event + XCB_XFIXES_SELECTION_NOTIFY) {
            return handleXfixesNotify(reinterpret_cast<xcb_xfixes_selection_notify_event_t *>(event));
        }
        return false;
    }
}
