// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <QTextStream>

#include "mousesimulator.h"

extern "C" {
#include <xdo.h>
#include <X11/X.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/XF86keysym.h>
}
#include <unistd.h>

namespace {
const quint16 KTime_Wait_After_Click = 50000;
const quint16 KMultiply_Time_Wait_For_Shift_Key = 12;
const quint16 KWidth_Parts = 24;
const quint16 KHeight_Parts = 11;
const quint16 KTotal_Lang_Rows = 5;
}

MouseSimulator::MouseSimulator(QObject *parent, Window x11vkbWinId, quint16 x11vkbWidth, quint16 x11vkbHeight) :
    QObject(parent),
    m_xdo(xdo_new(nullptr)),
    m_x11vkbWinId(x11vkbWinId),
    m_x11vkbWidth(x11vkbWidth),
    m_x11vkbHeight(x11vkbHeight)
{
}

/**
 * @brief MouseSimulator::~MouseSimulator
 */
MouseSimulator::~MouseSimulator()
{
    xdo_free(m_xdo);
}

void MouseSimulator::mouseLeftClickOnVkb(QPair<quint16, quint16> coordinates) const
{
    xdo_move_mouse_relative_to_window(m_xdo, m_x11vkbWinId, 0, 0);
    Display *dpy = m_xdo->xdpy;
    XEvent event;

    XQueryPointer(dpy, RootWindow(dpy,0), &event.xbutton.root,
                   &event.xbutton.window, &event.xbutton.x_root,
                   &event.xbutton.y_root, &event.xbutton.x, &event.xbutton.y,
                   &event.xbutton.state);

    XTestFakeMotionEvent (dpy, 0, event.xbutton.x + coordinates.first,
                          event.xbutton.y + coordinates.second, CurrentTime);
    XSync(dpy, 0);

    XTestFakeButtonEvent(dpy, Button1, True, CurrentTime);
    XTestFakeButtonEvent(dpy, Button1, False, CurrentTime);
    XFlush(dpy);
    usleep(KTime_Wait_After_Click);
}

void MouseSimulator::setWidthHeight(QPair<quint16, quint16> pairWidthHeight)
{
    m_x11vkbWidth  = pairWidthHeight.first;
    m_x11vkbHeight = pairWidthHeight.first;
}

void MouseSimulator::clickCtrlPlusSToSave(const Window winId) const
{
    xdo_move_mouse_relative_to_window(m_xdo, winId, 5, 5);
    Display *dpy = m_xdo->xdpy;
    XTestFakeKeyEvent(dpy, XKeysymToKeycode( dpy, XK_Control_L ), true, CurrentTime);
    XTestFakeKeyEvent(dpy, XKeysymToKeycode( dpy, XK_S ), true, CurrentTime);
    XTestFakeKeyEvent(dpy, XKeysymToKeycode( dpy, XK_S ), false, CurrentTime);
    XTestFakeKeyEvent(dpy, XKeysymToKeycode( dpy, XK_Control_L ), false, CurrentTime);
    XFlush(dpy);
}

void MouseSimulator::clickLangKey(const quint16 layoutEnglish) const
{
    if (layoutEnglish == 2) {
        mouseLeftClickOnVkb(getPositionOfNotEngLayout(4,2));
    } else {
        mouseLeftClickOnVkb(getPosition(4,2));
    }
}

void MouseSimulator::clickLangLine(const LanguageLines langLine) const
{
    quint16 width = m_x11vkbWidth/KTotal_Lang_Rows;
    quint16 height = m_x11vkbHeight/KHeight_Parts;
    switch (langLine) {
    case LanguageLines::FirstLine:
        height = height*4;
        break;
    case LanguageLines::SecondLine:
        height = height*5;
        break;
    case LanguageLines::ThirdLine:
        height = height*6;
        break;
    case LanguageLines::FourthLine:
        height = height*7;
        break;
    case LanguageLines::FifthLine:
        height = height*8;
        break;
    }
    mouseLeftClickOnVkb({width, height});
}

void MouseSimulator::clickHideKeyboard() const
{
    mouseLeftClickOnVkb(getPosition(4,6));
}

QPair<quint16, quint16> MouseSimulator::getPosition(const quint16 rowNum, const quint16 keyNum) const
{
    QPair<quint16, quint16> coordinates(0,0);

    if (0 < rowNum && rowNum <= 4) {
        coordinates.second = this->vkbPositionOfRow(rowNum);
        switch (rowNum)
        {
        case 1:
        case 3:
            if (0 < keyNum && keyNum <= 11) {
                coordinates.first = m_x11vkbWidth/KWidth_Parts*(keyNum*2);
            }
            break;
        case 2:
            if (0 < keyNum && keyNum <= 10) {
                coordinates.first = m_x11vkbWidth/KWidth_Parts*(keyNum*2+1);
            }
            break;
        case 4:
            switch (keyNum) {
            case 1:
                coordinates.first = m_x11vkbWidth/KWidth_Parts*(keyNum*2);
                break;
            case 2:
                coordinates.first = m_x11vkbWidth/KWidth_Parts*5;
                break;
            case 3:
                coordinates.first = m_x11vkbWidth/2;
                break;
            case 4:
                coordinates.first = m_x11vkbWidth/KWidth_Parts*18;
                break;
            case 5:
                coordinates.first = m_x11vkbWidth/KWidth_Parts*20;
                break;
            case 6:
                coordinates.first = m_x11vkbWidth/KWidth_Parts*(KWidth_Parts-2);
                break;
            default:
                break;
            }
            break;
        default:
            break;
        }
    }
    return coordinates;
}

QPair<quint16, quint16> MouseSimulator::getPositionOfNotEngLayout(const quint16 rowNum, const quint16 keyNum) const
{
    QPair<quint16, quint16> coordinates(0, vkbPositionOfRow(rowNum));
    if (rowNum == 4 && keyNum == 2) {
        coordinates.first = m_x11vkbWidth/KWidth_Parts*4;
    }
    return coordinates;
}

void MouseSimulator::clickLetter(const QChar &letter) const
{
    switch (letter.toLower().unicode())
    {
    case 'a':
        mouseLeftClickOnVkb(this->getPosition(2,1));
        break;
    case 'b':
        mouseLeftClickOnVkb(this->getPosition(3,6));
        break;
    case 'c':
        mouseLeftClickOnVkb(this->getPosition(3,4));
        break;
    case 'd':
        mouseLeftClickOnVkb(this->getPosition(2,3));
        break;
    case 'e':
        mouseLeftClickOnVkb(this->getPosition(1,3));
        break;
    case 'f':
        mouseLeftClickOnVkb(this->getPosition(2,4));
        break;
    case 'g':
        mouseLeftClickOnVkb(this->getPosition(2,5));
        break;
    case 'h':
        mouseLeftClickOnVkb(this->getPosition(2,6));
        break;
    case 'i':
        mouseLeftClickOnVkb(this->getPosition(1,8));
        break;
    case 'j':
        mouseLeftClickOnVkb(this->getPosition(2,7));
        break;
    case 'k':
        mouseLeftClickOnVkb(this->getPosition(2,8));
        break;
    case 'l':
        mouseLeftClickOnVkb(this->getPosition(2,9));
        break;
    case 'm':
        mouseLeftClickOnVkb(this->getPosition(3,8));
        break;
    case 'n':
        mouseLeftClickOnVkb(this->getPosition(3,7));
        break;
    case 'o':
        mouseLeftClickOnVkb(this->getPosition(1,9));
        break;
    case 'p':
        mouseLeftClickOnVkb(this->getPosition(1,10));
        break;
    case 'q':
        mouseLeftClickOnVkb(this->getPosition(1,1));
        break;
    case 'r':
        mouseLeftClickOnVkb(this->getPosition(1,4));
        break;
    case 's':
        mouseLeftClickOnVkb(this->getPosition(2,2));
        break;
    case 't':
        mouseLeftClickOnVkb(this->getPosition(1,5));
        break;
    case 'u':
        mouseLeftClickOnVkb(this->getPosition(1,7));
        break;
    case 'v':
        mouseLeftClickOnVkb(this->getPosition(3,5));
        break;
    case 'w':
        mouseLeftClickOnVkb(this->getPosition(1,2));
        break;
    case 'x':
        mouseLeftClickOnVkb(this->getPosition(3,3));
        break;
    case 'y':
        mouseLeftClickOnVkb(this->getPosition(1,6));
        break;
    case 'z':
        mouseLeftClickOnVkb(this->getPosition(3,2));
        break;
    default:
        break;
    }

}

void MouseSimulator::clickEnglishLetter(QChar &letter) const
{
    this->clickExtraKeyIfNeeded(letter);

    if (letter.isLetter()) {
        this->clickLetter(letter);
    } else if (letter.isNumber()) {
        quint16 number = static_cast<quint16>(letter.digitValue());
        mouseLeftClickOnVkb(this->getPosition(1, number == 0 ? 10 : static_cast<quint16>(number) ));
    } else {
        switch (letter.unicode())
        {
        case ' ':
            mouseLeftClickOnVkb(this->getPosition(4,3));
            break;
        case ',':
            mouseLeftClickOnVkb(this->getPosition(3,9));
            break;
        case '.':
            mouseLeftClickOnVkb(this->getPosition(3,10));
            break;
        case '\'':
            mouseLeftClickOnVkb(this->getPosition(4,4));
            break;
        case '\b':
            mouseLeftClickOnVkb(this->getPosition(1,11));
            break;
        case QChar::CarriageReturn :
            mouseLeftClickOnVkb(this->getPosition(2,10));
            break;
        default:
            break;
        }
    }

    this->clickExtraKeyIfNeeded(letter);
}

void MouseSimulator::clickExtraKeyIfNeeded(QChar &letter) const
{
    bool clickIsNeeded = false;
    if (letter.isUpper()) {
        mouseLeftClickOnVkb(this->getPosition(3,1));
        clickIsNeeded = true;
    } else if (letter.isNumber()) {
        mouseLeftClickOnVkb(this->getPosition(4,1));
        clickIsNeeded = true;
    }

    if (clickIsNeeded) {
        usleep(KTime_Wait_After_Click*KMultiply_Time_Wait_For_Shift_Key);
    }
}

quint16 MouseSimulator::vkbPositionOfRow(const quint16 row) const
{
    quint16 heightSize = m_x11vkbHeight/KHeight_Parts;
    switch (row)
    {
    case 1 :
        heightSize = heightSize*3;
        break;
    case 2 :
        heightSize = heightSize*5;
        break;
    case 3 :
        heightSize = heightSize*7;
        break;
    case 4 :
        heightSize = heightSize*9;
        break;
    default:
        heightSize = 0;
        break;
    }
    return heightSize;
}
