/*
 * QT Client for X2GoKDrive
 * Copyright (C) 2018-2023 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>
 * Copyright (C) 2018-2023 phoca-GmbH
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include "xcbclip.h"

#include <QX11Info>
#include <QTimer>
#include <QBuffer>
#include <QDateTime>


#define SELECTION_DELAY 30000 //timeout for selection operation
#define INCR_SIZE 256*1024 //size of part for incr selection incr selection

XCBClip::XCBClip(Client* parent)
{

    this->parent=parent;

    uint32_t             mask = 0;
    xcb_generic_error_t *error = 0;

    xcb_xfixes_query_version_cookie_t xfixes_query_cookie;
    xcb_xfixes_query_version_reply_t *xfixes_query;
    uint32_t             values[2];


    con = xcb_connect (NULL, NULL);
    /* Create the window */
    xcb_screen_t   *screen = xcb_setup_roots_iterator (xcb_get_setup (con)).data;
    clipWinId = xcb_generate_id (con);
    mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    values[0] = screen->white_pixel;
    values[1] = XCB_EVENT_MASK_PROPERTY_CHANGE;

    ATOM_CLIPBOARD=atom("CLIPBOARD");

    //create window which will recieve selection events and provide remote selection to X-clients
    xcb_create_window (con,
                       XCB_COPY_FROM_PARENT,
                       clipWinId,
                       screen->root,
                       0, 0,
                       1, 1,
                       0,
                       XCB_WINDOW_CLASS_INPUT_OUTPUT,
                       screen->root_visual,
                       mask, values);

    xcb_flush(con);
    //check if we have xfixes, we need it to recieve selection owner events

    reply = xcb_get_extension_data(con, &xcb_xfixes_id);
    if (reply && reply->present)
    {
        xfixes_query_cookie = xcb_xfixes_query_version(con, XCB_XFIXES_MAJOR_VERSION, XCB_XFIXES_MINOR_VERSION);
        xfixes_query = xcb_xfixes_query_version_reply (con, xfixes_query_cookie, &error);
        if (!xfixes_query || error || xfixes_query->major_version < 2)
        {
            Client::KDRStdErr()<<"XFixes query failed";
            free(error);
        }
        else
        {
            //we'll recieve sel owner events for primary amd clipboard
            mask =  XCB_XFIXES_SELECTION_EVENT_MASK_SET_SELECTION_OWNER;
            xcb_xfixes_select_selection_input_checked(con,clipWinId, XCB_ATOM_PRIMARY, mask);
            xcb_xfixes_select_selection_input_checked(con, clipWinId, ATOM_CLIPBOARD, mask);
        }
        free(xfixes_query);
    }
    xcb_flush(con);
    QTimer::singleShot(250, this, SLOT(checkEvents()));
}

XCBClip::~XCBClip()
{
    for(uint i = delayedSelectionRequests.length()-1; i<=0;--i)
    {
        discardDelayedRequest(i);
    }
    //remove all pending INCR requests
    remove_obsolete_incr_transactions(false);
    xcb_destroy_window(con, clipWinId);
    xcb_disconnect(con);
}


xcb_atom_t XCBClip::atom(const QString& name)
{
    //get atom for the name, return 0 if not found
    xcb_intern_atom_cookie_t cookie;
    xcb_intern_atom_reply_t *reply;
    xcb_atom_t a=0;

    cookie = xcb_intern_atom(con, 0, name.length(), name.toLatin1().data());
    if ((reply = xcb_intern_atom_reply(con, cookie, NULL)))
    {
//         Client::KDRStdErr()<<"found atom for "<<name;
        a=reply->atom;
        free(reply);
    }
    return a;
}

bool XCBClip::is_image_atom(xcb_atom_t at)
{
    //check if selection data is image
    if(!at)
        return false;
    if( at == atom("image/png") ||
        at == atom("image/xpm") ||
        at == atom("image/jpg") ||
        at == atom("image/jpeg") ||
        at == atom("PIXMAP") ||
        at == atom("image/bmp"))
        return true;
    return false;
}

bool XCBClip::is_string_atom(xcb_atom_t at)
{
    //check if selection data is string/text
    if(!at)
        return false;
    if( at == atom("UTF8_STRING") ||
        at == atom("STRING") ||
        at == atom("TEXT") ||
        at == atom("text/plain;charset=utf-8") ||
        at == atom("text/plain"))
        return true;
    return false;
}

QString XCBClip::atom_name(xcb_atom_t xatom)
{
    QString name;
    xcb_get_atom_name_cookie_t cookie = xcb_get_atom_name(con, xatom);
    xcb_get_atom_name_reply_t *reply=xcb_get_atom_name_reply(con, cookie, NULL);


    if(!reply)
        return name;
    if(!reply->name_len)
    {
        free(reply);
        return name;
    }
    name=QString(QByteArray(xcb_get_atom_name_name(reply), xcb_get_atom_name_name_length(reply)));
    free(reply);
    return name;
}

QStringList XCBClip::atomsInReply(xcb_get_property_reply_t *reply)
{
    QStringList atoms;
    xcb_atom_t* tg=(xcb_atom_t*) xcb_get_property_value(reply);
    for(uint i=0;i<xcb_get_property_value_length(reply)/sizeof(xcb_atom_t);++i)
    {
        atoms << atom_name(tg[i]);
    }
    return atoms;
}

xcb_atom_t XCBClip::best_atom_from_list(const QStringList& list)
{
    //here we chose the best of supported formats for selection
    xcb_atom_t a;
    if((a=target_has_atom(list, "UTF8_STRING")))
    {
        return a;
    }

    if((a=target_has_atom(list, "text/plain;charset=utf-8")))
    {
        return a;
    }

    if((a=target_has_atom(list, "STRING")))
    {
        return a;
    }
    if((a=target_has_atom(list, "TEXT")))
    {
        return a;
    }
    if((a=target_has_atom(list, "text/plain")))
    {
        return a;
    }

    //Server supports only PNG and JPEG formats

    if((a=target_has_atom(list, "image/png")))
    {
         return a;
    }
/*    if((a=target_has_atom(list, "image/xpm")))
    {
        return a;
    }
    if((a=target_has_atom(list, "PIXMAP")))
    {
        return a;
    }
    if((a=target_has_atom(list, "image/bmp")))
    {
        return a;
    }*/
    if((a=target_has_atom(list, "image/jpg")))
    {
        return a;
    }
    if((a=target_has_atom(list, "image/jpeg")))
    {
        return a;
    }
    return 0;
}

xcb_atom_t XCBClip::target_has_atom(const QStringList& atoms, const QString& name)
{
    foreach (const QString& xatom, atoms)
    {
        if(xatom==name)
        {
            return atom(name);
        }
    }
    return 0;
}

void XCBClip::discardDelayedRequest(uint index)
{
    DelayedRequest *d=delayedSelectionRequests.takeAt(index);
    xcb_send_event(con, false, d->request->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)d->event);
    xcb_flush(con);
    free(d->event);
    free((xcb_generic_event_t *)(d->request));
    delete d;
}


void XCBClip::processDelayedRequests()
{
    //process delayed requests
    for(uint i = delayedSelectionRequests.length()-1; i<=0;--i)
    {
        DelayedRequest* d=delayedSelectionRequests[i];
        SelectionType selection = selection_from_atom( d->request->selection);
        if(currentXTime() > (d->request->time + SELECTION_DELAY))
        {
//             Client::KDRStdErr()<<"timeout selection: "<<selection;
            discardDelayedRequest(i);
            continue;
        }
        if(!inputSelection[selection].owner)
        {
//             Client::KDRStdErr()<<"we are not owner of requested selection: "<<selection;
            //we are not anymore owners of this selection
            discardDelayedRequest(i);
            continue;
        }
        if(inputSelection[selection].timestamp > d->request->time )
        {
//             Client::KDRStdErr()<<"selection request for "<<selection<<" is too old";
            //requested selection is older than the current one
            discardDelayedRequest(i);
            continue;
        }
        if(!check_req_sanity(d->request))
        {
//             Client::KDRStdErr()<<"can't convert selection "<<selection<<" to requested myme type "<<d->request->property;
            //our selection don't support requested mime type
            discardDelayedRequest(i);
            continue;
        }
        if(inputSelection[selection].state != InputSelection::COMPLETED)
        {
            //we don't have the data yet
            continue;
        }
        d->event->property=send_data(d->request);
        discardDelayedRequest(i);
    }
}


void XCBClip::updateCurrentTime(xcb_timestamp_t t)
{
    //updating the current X time. It's not very precicely, but enough for us
    if(t > lastXTime)
    {
        //update current time
        lastXTime=t;
        timeDifference=QDateTime::currentMSecsSinceEpoch() - t;
//         Client::KDRStdErr()<<"X time dif:"<<QDateTime::currentMSecsSinceEpoch() - t<<"x11 time"<<t<<"calculated: "<<currentXTime();
    }
}

xcb_timestamp_t XCBClip::currentXTime()
{
    //get current X time
    return QDateTime::currentMSecsSinceEpoch() - timeDifference;

}


void XCBClip::checkEvents()
{
    //check delayed events
    processDelayedRequests();
    //delete obsolete INCR transactions if we have something
    remove_obsolete_incr_transactions();
    xcb_generic_event_t *e=xcb_poll_for_event(con);
    if(!e)
    {
        //don't have events let*s check again in 100msec
        QTimer::singleShot(100, this, SLOT(checkEvents()));
        return;
    }

    uint response_type=e->response_type & ~0x80;

    //we notified that selection is changed in primary or clipboard
    if (response_type == reply->first_event + XCB_XFIXES_SELECTION_NOTIFY)
    {
        xcb_xfixes_selection_notify_event_t *notify_event=(xcb_xfixes_selection_notify_event_t *)e;
        updateCurrentTime(notify_event->timestamp);


//         Client::KDRStdErr()<<"SEL OWNER notify, selection:"<<notify_event->selection<< " window "<< notify_event->window<< "owner"<< notify_event->owner;
        if(notify_event->owner == clipWinId)
        {
//             Client::KDRStdErr()<<"not processing, we are the owner of this notify";
        }
        else
        {
            switch(parent->clipboardMode())
            {
                case CLIP_SERVER:
                case CLIP_NONE:
                    Client::KDRStdErr()<<"Client selection is disabled";
                    return;
                default:
                    break;
            }
            //cancel all previous incr reading
            incrementalSize=incrementalSizeRead=0;
            incrAtom=0;
            if(notify_event->selection==XCB_ATOM_PRIMARY)
            {
                inputSelection[PRIMARY].owner=false;
            }
            else
            {
                inputSelection[CLIPBOARD].owner=false;
            }
            //get supported mime types
            request_selection_data( notify_event->selection, atom( "TARGETS"), atom( "TARGETS"), 0);
        }
    }
    else
    {
        //we notified that property is changed
        if (response_type == XCB_PROPERTY_NOTIFY)
        {
            process_property_notify(e);
        }
        //we got reply to our selection request (mime types or data)
        else if (response_type == XCB_SELECTION_NOTIFY)
        {
            process_selection_notify(e);
        }
        else if (response_type == XCB_SELECTION_REQUEST)
        {
            if(!process_selection_request(e))
            {
                //we delayed processing of this request till data received from server
                //we will free the event when we have the data
                QTimer::singleShot(10, this, SLOT(checkEvents()));
                return;
            }
        }
        else
        {
//             Client::KDRStdErr()<<"not processing this event "<<response_type;
        }
    }

    free(e);

    //return to the main eventsloop before processing new events, selection has less priority than other events
    QTimer::singleShot(10, this, SLOT(checkEvents()));
}




void XCBClip::process_selection_notify(xcb_generic_event_t *e)
{
    xcb_selection_notify_event_t *sel_event;

//     Client::KDRStdErr()<<"selection notify";
    sel_event=(xcb_selection_notify_event_t *)e;
    updateCurrentTime(sel_event->time);



    //processing the event which is reply for convert selection call


    if (sel_event->requestor != clipWinId)
    {
//         Client::KDRStdErr()<<("not our window");
        return;
    }
    else
    {
//         Client::KDRStdErr()<<"selection notify sel , target , property "<< sel_event->selection<< sel_event->target<< sel_event->property;
        if(sel_event->property==XCB_NONE)
        {
//             Client::KDRStdErr()<<( "NO SELECTION");
        }
        else
        {

            currentSelection=sel_event->selection;
            //read property
            read_selection_property(currentSelection, sel_event->property);
        }
    }
}

void XCBClip::destroy_incr_transaction(int index)
{
    //destroy incr transaction with index
    IncrTransaction* tr=incrTransactions.takeAt(index);
    const quint32 mask[] = { XCB_EVENT_MASK_NO_EVENT };
    //don't resive property notify events for this window anymore
    xcb_change_window_attributes(con, tr->requestor,
                                 XCB_CW_EVENT_MASK, mask);
    xcb_flush(con);

    delete tr;
}


void XCBClip::remove_obsolete_incr_transactions( bool checkTs)
{
    //remove_obsolete_incr_transactions
    //if checkTS true, check timestamp and destroy only if ts exceed delay
    for (int i= incrTransactions.size()-1;i>=0;--i)
    {
        if( (!checkTs) || (  incrTransactions[i]->timestamp+SELECTION_DELAY  <  QDateTime::currentMSecsSinceEpoch()))
        {
            Client::KDRStdErr()<<"timeout INCR selection for "<<incrTransactions[i]->requestor<<" "<<incrTransactions[i]->timestamp<<" "<<QDateTime::currentMSecsSinceEpoch();
            destroy_incr_transaction(i);
        }
    }
}


void XCBClip::process_incr_transaction_property(xcb_property_notify_event_t * pn)
{
    //process incr transactions
    for (int i=0;i < incrTransactions.size();++i)
    {
        IncrTransaction* tr=incrTransactions[i];
        if((tr->requestor == pn->window) && (tr->property == pn->atom ) && ( pn->state == XCB_PROPERTY_DELETE) )
        {
            //requestor ready for the new portion of data
            uint left=tr->data.size()-tr->sentBytes;
            if(!left)
            {
//                 Client::KDRStdErr()<<"all INCR data sent to "<<tr->requestor;
                //all data sent, sending NULL data and destroying transaction
                xcb_change_property(con, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property,
                                    tr->target, 8, 0, NULL);
                xcb_flush(con);
                destroy_incr_transaction(i);
                return;
            }
            uint sendingBytes=(INCR_SIZE< left)?INCR_SIZE:left;

//             Client::KDRStdErr()<<"sending incr bytes "<<sendingBytes ;

            xcb_change_property(con, XCB_PROP_MODE_REPLACE, tr->requestor, tr->property,
                                tr->target, 8, sendingBytes, tr->data.constData() + tr->sentBytes);
            xcb_flush(con);
            tr->sentBytes+=sendingBytes;
            tr->timestamp=QDateTime::currentMSecsSinceEpoch();
            return;
        }
    }
    //notify event doesn't belong to any of started incr transactions or it's notification for new property
    return;
}

void XCBClip::process_property_notify(xcb_generic_event_t *e)
{
    xcb_property_notify_event_t *pn;

//     Client::KDRStdErr()<<("property notify");

    pn = (xcb_property_notify_event_t *)e;
    updateCurrentTime(pn->time);

    if (pn->window != clipWinId)
    {
        //this property doesn't belong to our window;
        //let's check if it's not the property corresponding to one of incr transactions
        process_incr_transaction_property(pn);
        return;
    }
//     Client::KDRStdErr()<<"property, state "<< pn->atom<< pn->state;
    if(pn->state==XCB_PROPERTY_NEW_VALUE)
    {
        if(incrAtom==pn->atom && incrementalSize)
        {
            //we recieveing the selection data incrementally, let's read a next chunk
//             Client::KDRStdErr()<<"reading incr property "<< pn->atom;
            read_selection_property(currentSelection, pn->atom);
        }
    }
}

void XCBClip::read_selection_property(xcb_atom_t selection, xcb_atom_t property)
{
    QString stype, sprop;
    xcb_atom_t data_atom;
    unsigned int bytes_left, bytes_read=0;
    xcb_get_property_cookie_t cookie;
    xcb_get_property_reply_t *reply;
    OutputChunk* chunk;


    //request property which represents value of selection (data or mime types)
    //get max 100K of data, we don't need to send more than that over network for perfomance reasons
    cookie= xcb_get_property(con,false, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, 0, parent->max_chunk());
    reply=xcb_get_property_reply(con, cookie, NULL);
    if(!reply)
    {
        Client::KDRStdErr()<< "NULL reply";
    }
    else
    {
        if(reply->type==XCB_NONE)
        {
//             Client::KDRStdErr()<< "NONE reply";
        }
        else
        {
            //here we have type of data
            stype=atom_name(reply->type);
            sprop=atom_name(property);

//             Client::KDRStdErr()<< "Property, type, format, length"<< sprop<< stype<< reply->format<< reply->length;
            //need to read property incrementally
            if(reply->type == atom("INCR"))
            {
                unsigned int sz=*((unsigned int*) xcb_get_property_value(reply));
//                 Client::KDRStdErr()<< "have incr property size: "<< sz;
                incrAtom=property;
                incrementalSize=sz;
                incrementalSizeRead=0;

                //deleteing property should tell the selection owner that we are ready for incremental reading of data
                xcb_delete_property(con, clipWinId, property);
                xcb_flush(con);
                free(reply);
                return;
            }
            //we have supported mime types in reply
            if(reply->type == atom( "ATOM"))
            {
                if(reply->format!=32)
                {
                    Client::KDRStdErr()<<( "wrong format for TARGETS");
                }
                else
                {
                    QStringList atoms=atomsInReply(reply);
//                     Client::KDRStdErr() << "target supports mime types:"<<atoms;
                    data_atom=0;
                    //get the best of supported mime types and request the selection in this format
                    data_atom=best_atom_from_list(atoms);
                    if(parent->serverSupportsExtSelection())
                    {
                        //servere support extended selection, we'll send the selection data first when it's requested by the server
                        //now we'll just notify the server that there is the new selection and send supported mime types
                        SelectionType sel=selection_from_atom(selection);
                        best_atom[sel]=data_atom;
                        SelectionMime mime=UTF_STRING;

                        if( ! is_string_atom(best_atom[sel]) )
                        {
                            mime = PIXMAP;
                        }

//                         Client::KDRStdErr()<<"MIME"<<mime;
                        parent->send_selnotify_to_server(sel,mime);
                    }
                    else
                    {

                        xcb_delete_property( con, clipWinId, property);
                        xcb_flush(con);

                        //request the data from selection
                        if(data_atom)
                            request_selection_data( selection, data_atom, data_atom, 0);
                        else
                        {
//                             Client::KDRStdErr()<<( "there are no supported mime types in the target");
                        }
                    }
                }
            }
            else
            {
                //here we have selection as string or image
                if(is_image_atom( reply->type) || is_string_atom( reply->type))
                {
                    //read property data in loop in the chunks with size (100KB)
                    do
                    {
                        bytes_left=reply->bytes_after;
                        //now we can access property data

                        /*FILE* cp=fopen("/tmp/clip", "a");
                         *                        fwrite(xcb_get_property_value(reply),1, xcb_get_property_value_length(reply),cp);
                         *                        fclose(cp);*/



                        SelectionType sel= selection_from_atom(selection);

                        SelectionMime mime= UTF_STRING;
                        if(is_image_atom(reply->type))
                            mime=PIXMAP;

                        chunk=new OutputChunk(sel, mime);


                        if(xcb_get_property_value_length(reply))
                        {
                            chunk->data.setRawData((const char*) xcb_get_property_value(reply),xcb_get_property_value_length(reply));
                        }

                        if(is_string_atom(property))
                        {
                            chunk->mimeData=UTF_STRING;
                        }
                        else
                            chunk->mimeData=PIXMAP;

                        chunk->selection=selection_from_atom(selection);


                        if(incrementalSize && (incrAtom==property))
                        {
                            //we are doing incremental reading
                            if(incrementalSizeRead == 0)
                            {
                                //it's the first chunk
                                chunk->firstChunk=true;
                            }
                            incrementalSizeRead+=xcb_get_property_value_length(reply);
                            if(!bytes_left && ! bytes_read && !xcb_get_property_value_length(reply))
                            {
                                //we got the property with 0 size it means that we recieved all data of incr property
//                                 Client::KDRStdErr()<<"INCR Property done, read  " << incrementalSizeRead;
                                incrAtom=0;
                                incrementalSize=0;
                                //it's the last chunk
                                chunk->lastChunk=true;
                            }
                            chunk->totalSize=incrementalSize;
                        }
                        else
                        {
                            //we are doing simple read
                            if(bytes_read==0)
                            {
                                //it's the first chunk
                                chunk->firstChunk=true;
                            }
                            if(bytes_left==0)
                            {
                                //the last chunk
                                chunk->lastChunk=true;
                            }
                            chunk->totalSize=xcb_get_property_value_length(reply)+bytes_left;
                        }

                        bytes_read+=xcb_get_property_value_length(reply);
//                         Client::KDRStdErr()<<"read chunk of selection - size, total read, left, first:, last:"<< xcb_get_property_value_length(reply)<< bytes_read<< bytes_left<< chunk->firstChunk<< chunk->lastChunk;

                        //attach chunk to the end of output chunk queue
                        parent-> addToSelectionOutput(chunk);

                        //send this chunk
                        parent->sendOutputSelChunk();

                        if(bytes_left)
                        {
                            free(reply);
                            cookie= xcb_get_property(con, 0, clipWinId, property, XCB_GET_PROPERTY_TYPE_ANY, bytes_read/4,parent->max_chunk());
                            reply=xcb_get_property_reply(con, cookie, NULL);
                            if(!reply)
                            {
                                //something is wrong
                                Client::KDRStdErr()<<("NULL reply");
                                break;
                            }
                        }
                        //read in loop till no data left
                    }while(bytes_left);
                }
                else
                {
                    stype=atom_name(reply->type);
                    Client::KDRStdErr()<<"Not supported mime type: "<<stype << reply->type;
                }
            }
            if(reply)
                free(reply);
            //if reading incr property this will say sel owner that we are ready for the next chunk of data
            xcb_delete_property(con, clipWinId, property);
            xcb_flush(con);
        }
    }
}

SelectionType XCBClip::selection_from_atom(xcb_atom_t selection)
{
    if(selection == XCB_ATOM_PRIMARY)
        return PRIMARY;
    return CLIPBOARD;

}

xcb_atom_t XCBClip::atom_from_selection(SelectionType selection)
{
    if(selection == PRIMARY)
        return XCB_ATOM_PRIMARY;
    return ATOM_CLIPBOARD;

}

void XCBClip::requestSelectionData(SelectionType selection)
{
    //Client requesting data for selection using this function
    request_selection_data(atom_from_selection(selection), best_atom[selection], best_atom[selection], 0);
}


void  XCBClip::request_selection_data( xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t t)
{
    //execute convert selection for primary or clipboard to get mimetypes or data (depends on target atom)
    if(!t)
        t=XCB_CURRENT_TIME;
    if(property)
    {
        xcb_delete_property(con,clipWinId,property);
    }
    xcb_convert_selection(con, clipWinId,selection, target, property, t);
    xcb_flush(con);
}


void XCBClip::setInputSelectionData(SelectionType selection, SelectionMime mime, bool firstChunk, bool lastChunk, uint32_t compressed, uint size, char* data, bool notify)
{
    //if notify is true, we don't have actual data, just notification
    //copy data to selection buffer
//     Client::KDRStdErr()<<"Get chunk of input selection: selection, myme, firstChunk, lastChunk, compressed, size:"<<selection<<mime<<firstChunk<<lastChunk<<compressed<<size<<notify;


    if(firstChunk)
    {
        inputSelection[selection].selData.clear();
        inputSelection[selection].selMime=mime;
        total_compressed=0;
    }

    if(!compressed)
        inputSelection[selection].selData.append(data,size);
    else
    {
        total_compressed+=compressed;
        inputSelection[selection].selData.append(Client::zuncompress(data, compressed, size));
//         Client::KDRStdErr()<<"uncompress from "<<compressed<<" to "<<size;
    }

    if(lastChunk )
    {
        if(notify)
        {
            inputSelection[selection].state=InputSelection::NOTIFIED;
//             Client::KDRStdErr()<<"Got selection notify from server";
            own_selection(selection);
        }
        else
        {
            //if state is requested, means we already own a selection
            if(inputSelection[selection].state!=InputSelection::REQUESTED)
            {
                own_selection(selection);
            }
            inputSelection[selection].state=InputSelection::COMPLETED;
//             Client::KDRStdErr()<<"Got selection data for "<<selection<<"total size: "<<inputSelection[selection].selData.size()<<"compressed size"<<total_compressed;
        }

    }
}

void XCBClip::own_selection(SelectionType selection)
{
    switch(parent->clipboardMode())
    {
        case CLIP_CLIENT:
        case CLIP_NONE:
            Client::KDRStdErr()<<"Server selection is disabled";
            return;
        default:
            break;
    }
    xcb_atom_t sel=atom_from_selection(selection);
    xcb_set_selection_owner(con, clipWinId, sel, XCB_CURRENT_TIME);
    xcb_flush(con);
    inputSelection[selection].owner=true;
    inputSelection[selection].timestamp=currentXTime();
}

bool XCBClip::process_selection_request(xcb_generic_event_t *e)
{
    //processing selection request.
    //return true if the processing is finishing after return
    //false if data is not ready and we are delaying processing of this request
    //in this case calling function SHOULD NOT destroy the request neither event should not be destroyed
    //we'll free this objects after processing of the request when the data is available

    xcb_selection_request_event_t *req=(xcb_selection_request_event_t*)e;

    updateCurrentTime(req->time);

    xcb_selection_notify_event_t* event= (xcb_selection_notify_event_t*)calloc(32, 1);
    event->response_type = XCB_SELECTION_NOTIFY;
    event->requestor = req->requestor;
    event->selection = req->selection;
    event->target    = req->target;
    event->property  = XCB_NONE;
    event->time      = req->time;

    xcb_atom_t property=req->property;
    xcb_atom_t target=req->target;

    if(property == XCB_NONE)
        property=target;

    SelectionType sel=selection_from_atom(req->selection);

//     Client::KDRStdErr()<<"selection request for"<<atom_name(req->selection)<<atom_name(req->target)<<atom_name(req->property)<< "from "<<req->requestor<<"we are "<<clipWinId;
    if(!inputSelection[sel].owner)
    {
        //we don't own this selection
//         Client::KDRStdErr()<<"not our selection";
        xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
        xcb_flush(con);
        free(event);
        return true;
    }

    if(inputSelection[sel].timestamp > req->time)
    {
        //selection changed after request
//         Client::KDRStdErr()<<"requested selection doesn't exist anymore";
        xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
        xcb_flush(con);
        free(event);
        return true;
    }


    if(req->target==atom("TIMESTAMP"))
    {
        event->property=property;
//         Client::KDRStdErr()<<"requested TIMESTAMP";
        xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor,
                            property, XCB_ATOM_INTEGER, 32, 1, &inputSelection[sel].timestamp);

    }
    else if(req->target==atom("TARGETS"))
    {
        event->property=property;
//         Client::KDRStdErr()<<"requested TARGETS";
        send_mime_types(req);
    }
    else
    {
        if(check_req_sanity(req))
        {
            //if data is ready, send it to requestor
            if(inputSelection[sel].state == InputSelection::COMPLETED)
                event->property=send_data(req);
            else
            {
                //if data is not ready, request it from server and delay the processing of request
                delay_selection_request(req, event);
                return false;
            }
        }
    }
    xcb_send_event(con, false, req->requestor, XCB_EVENT_MASK_NO_EVENT, (char*)event);
    xcb_flush(con);
    free(event);
    return true;
}

void XCBClip::delay_selection_request( xcb_selection_request_event_t *request, xcb_selection_notify_event_t* event)
{
    SelectionType sel=selection_from_atom(request->selection);
    DelayedRequest* dr=new DelayedRequest;
    dr->event=event;
    dr->request=request;
    //add new request to the queue
    delayedSelectionRequests<<dr;
    if(inputSelection[sel].state==InputSelection::NOTIFIED)
    {
        //if we didn't request the data yet, let's do it now
        parent->requestSelectionFromServer(sel);
        inputSelection[sel].state=InputSelection::REQUESTED;
    }
}

QString XCBClip::mime_to_QT_img(const QString& mimeType)
{
    //convert mimeType to internal QT image format:
    //https://doc.qt.io/qt-5/qimage.html#reading-and-writing-image-files
    QString f=mimeType;
    if(mimeType=="PIXMAP")
        return "XPM";
    f=f.replace("image/","").toUpper();
    return f;
}


xcb_atom_t XCBClip::set_data_property(xcb_selection_request_event_t* req, QByteArray* data)
{
    //set data to window property

    //change when implemented
    bool support_incr=true;
    //this types of application not supporting incr selection
    if(atom_name(req->property)=="_XT_SELECTION_0" || atom_name(req->property)=="_QT_SELECTION")
    {
//         Client::KDRStdErr()<<atom_name(req->property)<<"doesn't support INCR";
        support_incr=false;
    }

    //check if we are sending incr

    if(!support_incr)
    {
        if( data->size() < (int)xcb_get_maximum_request_length(con) * 4 - 24)
        {
            //requester doesn't support INCR, sending data in one chunk
            xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target,
                                8, data->size(), (const void *)data->constData());

            xcb_flush(con);
            return req->property;
        }
        Client::KDRStdErr()<<"data is too big";
        return XCB_NONE;
    }

    if(data->size() < INCR_SIZE)
    {
        //if size is < 256K send in one property
        xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, req->target,
                            8, data->size(), (const void *)data->constData());
        xcb_flush(con);
        return req->property;
    }

    //sending INCR atom to let requester know that we are starting data incrementally
    uint bytes=data->size();
    Client::KDRStdErr()<<"starting INCR send of size "<<data->size()<<" for win ID "<<req->requestor;
    xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property,
                        atom("INCR"), 32, 1, (const void *)&bytes);
    startIncrTransaction(req->requestor, req->property, req->target, *data );
    xcb_flush(con);

    return req->property;
}



//creating INCR transaction
void XCBClip::startIncrTransaction(xcb_window_t requestor, xcb_atom_t property, xcb_atom_t target, QByteArray data)
{
    IncrTransaction* tr=new IncrTransaction;
    tr->requestor=requestor;
    tr->property=property;
    tr->target=target;
    tr->data=data;
    tr->sentBytes=0;
    tr->timestamp=QDateTime::currentMSecsSinceEpoch();
    incrTransactions<<tr;
    Client::KDRStdErr()<<"INCR start "<<tr->timestamp;
    const quint32 mask[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
    //we'll recive property change events for requestor window from now
    xcb_change_window_attributes(con, requestor,
                                 XCB_CW_EVENT_MASK, mask);
}


//check if the requested mime can be delivered
bool XCBClip::check_req_sanity(xcb_selection_request_event_t* req)
{
    SelectionType sel=selection_from_atom(req->selection);
    if(inputSelection[sel].selMime==UTF_STRING)
    {
        //if it's one of supported text formats send without convertion
        if(is_string_atom(req->target))
        {
            //             Client::KDRStdErr()<<"sending UTF text";
            return true;
        }
        else
        {
            Client::KDRStdErr()<<"unsupported property requested: "<<atom_name(req->target);
            return false;
        }
    }
    else
    {
        if(!is_image_atom(req->target))
        {
            Client::KDRStdErr()<<"unsupported property requested: "<<atom_name(req->target);
            return false;
        }
        //         Client::KDRStdErr()<<"sending "<<atom_name(req->target);
        return true;
    }
}


xcb_atom_t XCBClip::send_data(xcb_selection_request_event_t* req)
{
    //send data
    SelectionType sel=selection_from_atom(req->selection);
    if(inputSelection[sel].selMime==UTF_STRING)
    {
        //if it's one of supported text formats send without convertion
        return set_data_property(req, &inputSelection[sel].selData);
    }
    else
    {
//         Client::KDRStdErr()<<"sending "<<atom_name(req->target);
        //convert to desireable format
        QImage img=QImage::fromData(inputSelection[sel].selData);
        QByteArray ba;
        QBuffer buffer(&ba);
        buffer.open(QIODevice::WriteOnly);
        img.save(&buffer, mime_to_QT_img(atom_name(req->target)).toLatin1());
//         Client::KDRStdErr()<<"converted to"<<mime_to_QT_img(atom_name(req->target))<<ba.size();
        return set_data_property(req, &ba);
    }
}

void XCBClip::send_mime_types(xcb_selection_request_event_t* req)
{
    //send supported targets
    SelectionType sel=selection_from_atom(req->selection);
    QVector<xcb_atom_t> targets;
    xcb_atom_t a;

    if((a=atom("TARGETS")))
        targets.append(a);
    if((a=atom("TIMESTAMP")))
        targets.append(a);

    if(inputSelection[sel].selMime==PIXMAP)
    {
        if((a=atom("image/png")))
            targets.append(a);
        if((a=atom("image/jpg")))
            targets.append(a);
        if((a=atom("image/jpeg")))
            targets.append(a);
        if((a=atom("image/bmp")))
            targets.append(a);
        if((a=atom("image/xpm")))
            targets.append(a);
    }
    else
    {
        if((a=atom("UTF8_STRING")))
            targets.append(a);
        if((a=atom("text/plain;charset=utf-8")))
            targets.append(a);
        if((a=atom("STRING")))
            targets.append(a);
        if((a=atom("TEXT")))
            targets.append(a);
        if((a=atom("text/plain")))
            targets.append(a);
    }

    xcb_change_property(con, XCB_PROP_MODE_REPLACE, req->requestor, req->property, XCB_ATOM_ATOM,
                        32, targets.size(), (const void *)targets.constData());
    xcb_flush(con);

}


