/*
 * 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 "displayarea.h"
#include "extwin.h"
#include <QPainter>
#include <QPaintEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QScreen>
#include <QGuiApplication>

#include <QKeyEvent>
#include <QTimer>

#include "client.h"

#ifdef Q_OS_LINUX
#include <QX11Info>
#include <xcb/xproto.h>
#endif


#ifdef Q_OS_WIN
static DisplayArea* display=0;


LRESULT CALLBACK LowLevelKeyboardProc(
    _In_ int    nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    if(!display->hasFocus())
    {
//         Client::KDRStdErr()<<"Not in focus, not processing events";
        return CallNextHookEx(0, nCode, wParam,lParam);
    }
    if(nCode<0)
    {
//         Client::KDRStdErr()<<"Not processing this event";
        return CallNextHookEx(0, nCode, wParam,lParam);
    }

    uint32_t etype=KEYPRESS;
    KBDLLHOOKSTRUCT* str= (KBDLLHOOKSTRUCT*) lParam;

    if((str->flags&LLKHF_ALTDOWN) && (str->vkCode == VK_TAB || str->vkCode == VK_F4))
    {
        uint32_t key= DisplayArea::win2pc105(str->scanCode);
        uint32_t mods=0;
        mods|=Mod1Mask;
        switch(wParam)
        {
            case WM_KEYDOWN:
            case WM_SYSKEYDOWN: etype=KEYPRESS;
//             Client::KDRStdErr()<<"KEYPRESS: System alt+TAB"<<KDR_ENDL;
            break;

            case WM_KEYUP:
            case WM_SYSKEYUP: etype=KEYRELEASE;
//             Client::KDRStdErr()<<"KEYRELEASE: System alt+TAB"<<KDR_ENDL;
            break;
        }

        char evmsg[EVLENGTH]={};
        memcpy(evmsg,(char*)&etype,4);
        memcpy(evmsg+4,(char*)&mods,4);
        memcpy(evmsg+8,(char*)&key,4);
        display->client->sendEvent(evmsg);
        //send key release immediately after key press to avoid key sticking
        //server with version > 6 will do it automatically
        if(etype==KEYPRESS && display->client->getPublicServerVersion()<7)
        {
            etype=KEYRELEASE;
            memcpy(evmsg,(char*)&etype,4);
            memcpy(evmsg+4,(char*)&mods,4);
            memcpy(evmsg+8,(char*)&key,4);
            display->client->sendEvent(evmsg);
        }
        return -1;
    }
    return CallNextHookEx(0, nCode, wParam, lParam);
}
#endif

DisplayArea::DisplayArea(Client* client, QWidget* parentWidget):QFrame(parentWidget)
{
    this->client=client;
    this->parentWidget=parentWidget;
    setMouseTracking(true);
    setFocusPolicy(Qt::StrongFocus);
    if(!client->isRootless())
        grabKeyboard();

}

void DisplayArea::grabKeyboard()
{
#ifdef Q_OS_LINUX
    Client::KDRStdErr()<<"Grab X11 Keyboard"<<KDR_ENDL;
    xcb_grab_key(QX11Info::connection(), 0, winId(), XCB_MOD_MASK_ANY, XCB_GRAB_ANY, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
#endif

#ifdef Q_OS_WIN
    display=this;
    Client::KDRStdErr()<<"Setting Windows Keyboard hook"<<KDR_ENDL;
    if(!SetWindowsHookExA( WH_KEYBOARD_LL,LowLevelKeyboardProc,0, 0  ))
    {
        Client::KDRStdErr()<<"Set kbd hook Failed "<<GetLastError()<<KDR_ENDL;
    }
#endif
}

DisplayArea::~DisplayArea()
{
}

void DisplayArea::paintEvent(QPaintEvent* ev)
{
    QPainter painter;
//     bool disp=false;
    painter.begin(this);
    if(client->needUpdate())
    {
        Frame* currentFrame=client->getCurrentFrame();
        QPixmap pix;
        if(!currentFrame->crc)
        {
            //                 Client::KDRStdErr()<<"Draw DISPLAY PIX "<<KDR_ENDL;
            pix=currentFrame->regions[0]->pix;
//             disp=true;
        }
        else
        {
            //                 Client::KDRStdErr()<<"Draw PIX from cache"<<KDR_ENDL;
            pix=client->getPixmapFromCache(currentFrame->crc);
            if(pix.isNull())
            {
                KDRStdErr()<<"Replacing with display image"<<KDR_ENDL;
                QPixmap disp;
                disp.convertFromImage(*client->getDisplayImage());
                pix=disp.copy(currentFrame->x, currentFrame->y, currentFrame->width, currentFrame->height);
            }
        }
        if(currentFrame->x==-1 || currentFrame->y==-1)
        {
            currentFrame->x=currentFrame->y=0;
        }
        if(!client->isRootless())
         {
//             if(!disp)
            {
                painter.drawPixmap(currentFrame->x,currentFrame->y,pix);

            }
//             else
//             {
//                 painter.drawPixmap(currentFrame->x,currentFrame->y,pix,currentFrame->x,currentFrame->y,currentFrame->width,currentFrame->height);
//                 dispImagePainter.drawPixmap(currentFrame->x,currentFrame->y,pix,currentFrame->x,currentFrame->y,currentFrame->width,currentFrame->height);
//             }
        }
        else
        {
//             if(!disp)
            {
                ExtWin* parWin=(ExtWin*)parentWidget;
                QPoint kdrPos=parWin->virtualToKdrivePosition(parWin->geometry().topLeft());
                painter.drawPixmap(currentFrame->x-kdrPos.x(),currentFrame->y-kdrPos.y(),pix);
//                 Client::KDRStdErr()<<"DRAW pix on: "<<currentFrame->x-kdrPos.x()<<":"<<currentFrame->y-kdrPos.y()<<KDR_ENDL;
            }
//             else
//             {
//                 painter.drawPixmap(currentFrame->x-((ExtWin*)parentWidget)->adjustGeometry().x(),currentFrame->y-((ExtWin*)parentWidget)->adjustGeometry().y(),pix,
//                                    currentFrame->x,currentFrame->y,ev->rect().width(),ev->rect().height());
//                 dispImagePainter.drawPixmap(currentFrame->x-((ExtWin*)parentWidget)->adjustGeometry().x(),currentFrame->y-((ExtWin*)parentWidget)->adjustGeometry().y(),pix,
//                                    currentFrame->x,currentFrame->y,ev->rect().width(),ev->rect().height());
//             }
        }
    }
    else
    {
        if(!client->isRootless())
        {
//             Client::KDRStdErr()<<"WM update: "<<Client::QRectToStr(ev->rect())<<KDR_ENDL;
            painter.drawImage(ev->rect(),*(client->getDisplayImage()),ev->rect());
        }
        else
        {
//             if(((ExtWin*)parentWidget)->getWindowType()==WINDOW_TYPE_NORMAL)
//                 Client::KDRStdErr()<<"WM update: "<<Client::QRectToStr(ev->rect())<<KDR_ENDL;
            ExtWin* parWin=(ExtWin*)parentWidget;
            QPoint kdrPos=parWin->virtualToKdrivePosition(parWin->geometry().topLeft());

            painter.drawImage(ev->rect().x(),ev->rect().y(),
                               *(client->getDisplayImage()), ev->rect().x()+kdrPos.x(),
                              ev->rect().y()+kdrPos.y(), ev->rect().width(), ev->rect().height());
        }
    }
    painter.end();
}

uint32_t DisplayArea::X11MouseButtonsState(Qt::MouseButtons qtMouseButtons)
{
    uint32_t x11Buttons=0;
    if(qtMouseButtons&Qt::LeftButton)
    {
        x11Buttons|=Button1Mask;
    }
    if(qtMouseButtons&Qt::RightButton)
    {
        x11Buttons|=Button3Mask;
    }
    if(qtMouseButtons&Qt::MiddleButton)
    {
        x11Buttons|=Button2Mask;
    }
    return x11Buttons;
}

uint32_t DisplayArea::X11MouseButton(Qt::MouseButton qtMouseButton)
{
    switch(qtMouseButton)
    {
        case Qt::LeftButton: return Button1;
        case Qt::RightButton: return Button3;
        case Qt::MidButton: return Button2;
        default: return 0;
    }
}



void DisplayArea::mouseMoveEvent(QMouseEvent* event)
{

    if(!client->isDisplayPointer(event))
        return;
    char evmsg[EVLENGTH]={};
    uint32_t etype, x, y;
    etype=MOUSEMOTION;

//     Client::KDRStdErr()<<"kbd mods"<<event->modifiers();

    if(!client->isRootless())
    {
        x=event->x();
        y=event->y();
    }
    else
    {
        ExtWin* w=(ExtWin*) parentWidget;
        QPoint parTopLeft=w->virtualToKdrivePosition(w->geometry().topLeft());
        x=event->x()+parTopLeft.x();
        y=event->y()+parTopLeft.y();
    }

    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&x,4);
    memcpy(evmsg+8,(char*)&y,4);

    client->sendEvent(evmsg);

//     Client::KDRStdErr()<<"mouse move"<<event->x()<<event->y();

}

void DisplayArea::mousePressEvent(QMouseEvent* event)
{
//     Client::KDRStdErr()<<"kbd mods"<<event->modifiers();
    char evmsg[EVLENGTH]={};
    uint32_t etype, state, button;
    etype=MOUSEPRESS;
    state=X11MouseButtonsState(event->buttons());
    button=X11MouseButton(event->button());

    state|=qtModifiers2pc105(event->modifiers());
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&button,4);

    client->sendEvent(evmsg);

//     Client::KDRStdErr()<<"mouse press"<<event->button()<<event->buttons()<<button<<state;
}

void DisplayArea::mouseReleaseEvent(QMouseEvent* event)
{
//     Client::KDRStdErr()<<"kbd mods"<<event->modifiers();
    char evmsg[EVLENGTH]={};
    uint32_t etype, state, button;
    etype=MOUSERELEASE;
    state=X11MouseButtonsState(event->buttons());
    button=X11MouseButton(event->button());

    state|=qtModifiers2pc105(event->modifiers());
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&button,4);

    client->sendEvent(evmsg);

//     Client::KDRStdErr()<<"mouse release"<<event->button()<<event->buttons()<<button<<state;
}

void DisplayArea::wheelEvent(QWheelEvent* event)
{
//     Client::KDRStdErr()<<"mouse wheel event"<<event->buttons()<<event->angleDelta();
    char evmsg[EVLENGTH]={};
    uint32_t etype, state, button;

    if(event->angleDelta().y() > 0)
    {
       button=Button4;
    }
    else

    {
        button=Button5;
    }

    etype=MOUSEPRESS;
    state=0;
    state|=qtModifiers2pc105(event->modifiers());
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&button,4);

//     Client::KDRStdErr()<<etype<<state<<button;
    client->sendEvent(evmsg);

    etype=MOUSERELEASE;
    state=X11MouseButtonsState(event->buttons());
    if(event->angleDelta().y() > 0)
    {
        button=Button4;
        state|=Button4Mask;
    }
    else
    {
        button=Button5;
        state|=Button5Mask;
    }

    state|=qtModifiers2pc105(event->modifiers());
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&button,4);

//     Client::KDRStdErr()<<etype<<state<<button;
    client->sendEvent(evmsg);
}

/*
void DisplayArea::debugLinuxMods(uint32_t nativeModifiers)
{
    if(nativeModifiers&ShiftMask)
        Client::KDRStdErr()<<"shift";
    if(nativeModifiers&ControlMask)
        Client::KDRStdErr()<<"ctrl";
    if(nativeModifiers&LockMask)
        Client::KDRStdErr()<<"lock";
    if(nativeModifiers&Mod1Mask)
        Client::KDRStdErr()<<"mod1";
    if(nativeModifiers&Mod2Mask)
        Client::KDRStdErr()<<"mod2";
    if(nativeModifiers&Mod3Mask)
        Client::KDRStdErr()<<"mod3";
    if(nativeModifiers&Mod4Mask)
        Client::KDRStdErr()<<"mod4";
    if(nativeModifiers&Mod5Mask)
        Client::KDRStdErr()<<"mod5";
    if(nativeModifiers&WINCAPS)
        Client::KDRStdErr()<<"win caps";
    if(nativeModifiers&WINNUM)
        Client::KDRStdErr()<<"win num";
}
*/

void DisplayArea::keyPressEvent(QKeyEvent* event)
{

//     Client::KDRStdErr()<<"Press key:"<<event->key()<<
//     "nat scan:"<<event->nativeScanCode()<<"nat vkey:"<<event->nativeVirtualKey()<<"text:"<<event->text();
//     debugLinuxMods(event->nativeModifiers());
//     Client::KDRStdErr()<<KDR_ENDL;

    char evmsg[EVLENGTH]={};
    uint32_t state, key, etype;
#ifdef Q_OS_LINUX
    key=event->nativeScanCode();
    state=event->nativeModifiers();
#else
    key=win2pc105(event->nativeScanCode());
    state=qtwinModifiers2pc105(event->nativeModifiers(), event->modifiers());
#endif
    etype=KEYPRESS;
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&key,4);
    client->sendEvent(evmsg);
//send key release immediately after key press to avoid key sticking
//server with version > 6 will do it automatically
    if(client->getPublicServerVersion()<7)
    {
        etype=KEYRELEASE;
        memcpy(evmsg,(char*)&etype,4);
        memcpy(evmsg+4,(char*)&state,4);
        memcpy(evmsg+8,(char*)&key,4);
        client->sendEvent(evmsg);
    }
}

void DisplayArea::keyReleaseEvent(QKeyEvent* event)
{
    char evmsg[EVLENGTH]={};
    uint32_t state, key, etype;
    #ifdef Q_OS_LINUX
    key=event->nativeScanCode();
    state=event->nativeModifiers();
    #else
    key=win2pc105(event->nativeScanCode());
    state=qtwinModifiers2pc105(event->nativeModifiers(), event->modifiers());
    #endif

//only send additional release event if modifier is set
#ifndef Q_OS_WIN
    if(!state)
        return;
#endif
//     Client::KDRStdErr()<<"RELEASE key:"<<event->key()<<
//     "nat scan:"<<event->nativeScanCode()<<"nat vkey:"<<event->nativeVirtualKey()<<"text:"<<event->text();
//     debugLinuxMods(event->nativeModifiers());
//     Client::KDRStdErr()<<KDR_ENDL;

    etype=KEYRELEASE;
    memcpy(evmsg,(char*)&etype,4);
    memcpy(evmsg+4,(char*)&state,4);
    memcpy(evmsg+8,(char*)&key,4);

    client->sendEvent(evmsg);
}

uint32_t DisplayArea::qtModifiers2pc105(Qt::KeyboardModifiers qtModifiers)
{
    uint32_t mods=0;
    if(qtModifiers&Qt::ShiftModifier)
    {
        mods|=ShiftMask;
    }
    if(qtModifiers&Qt::MetaModifier)
    {
        mods|=Mod4Mask;
    }
    if(qtModifiers&Qt::GroupSwitchModifier)
    {
        mods|=Mod5Mask;
    }

    if(qtModifiers&Qt::ControlModifier && qtModifiers&Qt::AltModifier)
    {
        mods|=Mod5Mask;
    }
    else
    {
        if(qtModifiers&Qt::ControlModifier)
        {
            mods|=ControlMask;
        }
        if(qtModifiers&Qt::AltModifier)
        {
            mods|=Mod1Mask;
        }
    }

    return mods;
}


#ifdef Q_OS_WIN
uint32_t DisplayArea::qtwinModifiers2pc105(uint32_t nativeModifiers, Qt::KeyboardModifiers qtModifiers )
{
    uint32_t mods=0;
    if(nativeModifiers&WINCAPS)
    {
        mods|=LockMask;
    }
    if(nativeModifiers&WINNUM)
    {
        mods|=Mod2Mask;
    }
    return mods|qtModifiers2pc105(qtModifiers);
}

uint32_t DisplayArea::win2pc105(uint32_t nativeCode)
{
    switch(nativeCode)
    {
        case 347: return 133; //WINL
        case 312: return 108; //ALTR
        case 348: return 134; //WINR
        case 349: return 135; //OPT
        case 285: return 105; //CTRR

        case 338: return 118; //INS
        case 327: return 110; //HOME
        case 329: return 112; //PGUP
        case 339: return 119; //DEL
        case 335: return 115; //END
        case 337: return 117; //PGDN

        //ARROWS
        case 328: return 111; //UP
        case 336: return 116; //DOWN
        case 331: return 113; //LEFT
        case 333: return 114; //RIGHT

        //NUMPAD
        case 325: return 77; //NLOCK
        case 309: return 106; // /
        case 55: return 63; // *
        case 74: return 82; // -

        case 71: return 79; // 7
        case 72: return 80; // 8
        case 73: return 81; // 9

        case 75: return 83; // 4
        case 76: return 84; // 5
        case 77: return 85; // 6
        case 78: return 86; // +

        case 79: return 87; // 1
        case 80: return 88; // 2
        case 81: return 89; // 3

        case 82: return 90; // 0
        case 83: return 91; // ,
        case 284: return 104; // ENTER*/

        default: return nativeCode+8;
    }
}
#endif //Q_OS_WIN
/*        case 1:  return 0; //ESC
 *        case 59: return 0; //F1
 *        case 60: return 0; //F2
 *        case 61: return 0; //F3
 *        case 62: return 0; //F4
 *        case 63: return 0; //F5
 *        case 64: return 0; //F6
 *        case 65: return 0; //F7
 *        case 66: return 0; //F8
 *        case 67: return 0; //F9
 *        case 68: return 0; //F10
 *        case 87: return 0; //F11
 *        case 88: return 0; //F12
 *
 *        case 1: return 0; //print
 *        case 70: return 0; //scroll
 *        case 1: return 0; //Break
 *
 *        case 41: return 0; //^
 *        case 2: return 0; //1
 *        case 3: return 0; //2
 *        case 4: return 0; //3
 *        case 5: return 0; //4
 *        case 6: return 0; //5
 *        case 7: return 0; //6
 *        case 8: return 0; //7
 *        case 9: return 0; //8
 *        case 10: return 0; //9
 *        case 11: return 0; //0
 *        case 12: return 0; //ß
 *        case 13: return 0; //´
 *        case 14: return 0; //BSP
 *
 *        case 15: return 0; //TAB
 *        case 16: return 0; //q
 *        case 17: return 0; //w
 *        case 18: return 0; //e
 *        case 19: return 0; //r
 *        case 20: return 0; //t
 *        case 21: return 0; //z
 *        case 22: return 0; //u
 *        case 23: return 0; //i
 *        case 24: return 0; //o
 *        case 25: return 0; //p
 *        case 26: return 0; //ü
 *        case 27: return 0; //+
 *        case 28: return 0; //ENTER
 *
 *        case 58: return 0; //CAPS
 *        case 30: return 0; //a
 *        case 31: return 0; //s
 *        case 32: return 0; //d
 *        case 33: return 0; //f
 *        case 34: return 0; //g
 *        case 35: return 0; //h
 *        case 36: return 0; //j
 *        case 37: return 0; //k
 *        case 38: return 0; //l
 *        case 39: return 0; //ö
 *        case 40: return 0; //ä
 *        case 43: return 0; //#
 *
 *        case 42: return 0; //SHIFTL
 *        case 86: return 0; //<
 *        case 44: return 0; //y
 *        case 45: return 0; //x
 *        case 46: return 0; //c
 *        case 47: return 0; //v
 *        case 48: return 0; //b
 *        case 49: return 0; //n
 *        case 50: return 0; //m
 *        case 51: return 0; //,
 *        case 52: return 0; //.
 *        case 53: return 0; //-
 *        case 54: return 0; //SHIFTR
 *
 *        case 29: return 0; //CTRL
 *        case 347: return 0; //WINL
 *        case 56: return 0; //ALTL
 *        case 57: return 0; //SPS
 *        case 312: return 0; //ALTR
 *        case 348: return 0; //WINR
 *        case 349: return 0; //OPT
 *        case 285: return 0; //CTRR
 *
 *        case 338: return 0; //INS
 *        case 327: return 0; //HOME
 *        case 329: return 0; //PGUP
 *        case 339: return 0; //DEL
 *        case 335: return 0; //END
 *        case 337: return 0; //PGDN
 *
 * //ARROWS
 *        case 328: return 0; //UP
 *        case 336: return 0; //DOWN
 *        case 331: return 0; //LEFT
 *        case 333: return 0; //RIGHT
 *
 * //NUMPAD
 *        case 325: return 0; //NLOCK
 *        case 309: return 0; // /
 *        case 55: return 0; // *
 *        case 74: return 0; // -
 *
 *        case 71: return 0; // 7
 *        case 72: return 0; // 8
 *        case 73: return 0; // 9
 *
 *        case 75: return 0; // 4
 *        case 76: return 0; // 5
 *        case 77: return 0; // 6
 *        case 78: return 0; // +
 *
 *        case 79: return 0; // 1
 *        case 80: return 0; // 2
 *        case 81: return 0; // 3
 *
 *        case 82: return 0; // 0
 *        case 83: return 0; // ,
 *        case 284: return 0; // ENTER*/
