/*
 * Copyright (C) 2009 Chase Douglas
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.
 */

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/uinput.h>
#include <poll.h>
#include <sys/uio.h>
#include <unistd.h>

#include <QHostAddress>
#include <QSocketNotifier>

#include "Connection.h"
#include "LinuxInputDevice.h"
#include "rinput.h"

LinuxInputDevice::LinuxInputDevice(Connection *_connection) :
    InputDevice::InputDevice(_connection),
    uinput(-1),
    readNotifier(NULL) {
    QByteArray name("Remote Input Device (");
    name.append(connection->peerAddress().toString().toAscii().data());
    name.append(')');

    memset(&uiudev, 0, sizeof(uiudev));
    strncpy(uiudev.name, name.data(), UINPUT_MAX_NAME_SIZE);
    uiudev.id.version = 1;
    uiudev.id.bustype = BUS_VIRTUAL;
}

bool LinuxInputDevice::create() {
    static const char *uinputFiles[] = {
        "/dev/uinput",
        "/dev/input/uinput"
    };

    bool success = FALSE;
    for (unsigned int i = 0; i < sizeof(uinputFiles) / sizeof(char *); i++) {
        if ((uinput = open(uinputFiles[i], O_RDWR | O_NDELAY)) >= 0) {
            success = TRUE;
            break;
        }
    }

    if (!success) {
        qCritical("Error: Failed to open uinput device file");
        rinput_message_t errorMessage = { RINPUT_ERROR, { RINPUT_INPUT_DEVICE_ALLOC_FAILED } };
        hton_rinput(&errorMessage);
        connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
        return FALSE;
    }

    readNotifier = new QSocketNotifier(uinput, QSocketNotifier::Read, this);
    connect(readNotifier, SIGNAL(activated(int)), SLOT(uinputReadyRead()));

    return TRUE;
}

void LinuxInputDevice::handleMessage(rinput_message_t &message) {
    rinput_message_t errorMessage = { RINPUT_ERROR };

    switch (message.type) {
        case RINPUT_SET_CAPABILITY: {
            int request;
            errorMessage.data.error.type = RINPUT_SET_CAPABILITY_FAILED;
            errorMessage.data.error.code1 = message.data.capability.type;
            errorMessage.data.error.code2 = message.data.capability.code;

            /*
             * If a future kernel redefines event codes or types, rinputd will translate here.
             */

            if (message.data.capability.type == RINPUT_EV_UTF8) {
                break;
            }

            switch (message.data.capability.type) {
                case RINPUT_EV_KEY:
                    request = UI_SET_KEYBIT;
                    break;
                case RINPUT_EV_REL:
                    request = UI_SET_RELBIT;
                    break;
                case RINPUT_EV_ABS:
                    request = UI_SET_ABSBIT;
                    break;
                case RINPUT_EV_MSC:
                    request = UI_SET_MSCBIT;
                    break;
                case RINPUT_EV_SW:
                    request = UI_SET_SWBIT;
                    break;
                case RINPUT_EV_LED:
                    request = UI_SET_LEDBIT;
                    break;
                case RINPUT_EV_SND:
                    request = UI_SET_SNDBIT;
                    break;

                default:
                    qDebug("Warning: Remote requested unsupported input capability");
                    errorMessage.data.error.type = RINPUT_CAPABILITY_TYPE_INVALID;
                    hton_rinput(&errorMessage);
                    connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                    return;
            }

            if (capabilities.size() <= message.data.capability.type || !capabilities.testBit(message.data.capability.type)) {
                if (ioctl(uinput, UI_SET_EVBIT, message.data.capability.type) < 0) {
                    qDebug("Error: Failed to set uinput bit: %s", strerror(errno));
                    errorMessage.data.error.type = RINPUT_SET_CAPABILITY_FAILED;
                    hton_rinput(&errorMessage);
                    connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                    break;
                }
                if (capabilities.size() <= message.data.capability.type) {
                    capabilities.resize(message.data.capability.type + 1);
                }
                capabilities.setBit(message.data.capability.type);
            }

            if (ioctl(uinput, request, message.data.capability.code) < 0) {
                qWarning("Warning: Failed to set uinput bit: %s", strerror(errno));
                errorMessage.data.error.type = RINPUT_SET_CAPABILITY_FAILED;
                hton_rinput(&errorMessage);
                connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
            }

            break;
        }

        case RINPUT_SET_ABS_PARAM:
            if (message.data.abs_param.axis > RINPUT_ABS_MAX) {
                qWarning("Error: Remote requested unsupported absolute axis");
                errorMessage.data.error.type = RINPUT_ABS_AXIS_INVALID;
                errorMessage.data.error.code1 = message.data.abs_param.axis;
                hton_rinput(&errorMessage);
                connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                break;
            }

            switch (message.data.abs_param.type) {
                case RINPUT_ABS_PARAM_MAX:
                    uiudev.absmax[message.data.abs_param.axis] = message.data.abs_param.value;
                    break;
                case RINPUT_ABS_PARAM_MIN:
                    uiudev.absmin[message.data.abs_param.axis] = message.data.abs_param.value;
                    break;
                case RINPUT_ABS_PARAM_FUZZ:
                    uiudev.absfuzz[message.data.abs_param.axis] = message.data.abs_param.value;
                    break;
                case RINPUT_ABS_PARAM_FLAT:
                    uiudev.absflat[message.data.abs_param.axis] = message.data.abs_param.value;
                    break;

                default:
                    qWarning("Error: Remote requested unsupported absolute parameter type");
                    errorMessage.data.error.type = RINPUT_ABS_PARAM_TYPE_INVALID;
                    errorMessage.data.error.code1 = message.data.abs_param.type;
                    hton_rinput(&errorMessage);
                    connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                    break;
            }
            break;

        case RINPUT_CREATE:
            if (created) {
                qWarning("Warning: Remote trying to recreate device");
                break;
            }

            if (write(uinput, &uiudev, sizeof(uiudev)) < (int)sizeof(uiudev)) {
                qCritical("Error: Failed to write full uinput specifications to device");
                message.data.error.type = RINPUT_INPUT_DEVICE_CREATE_FAILED;
                hton_rinput(&errorMessage);
                connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                connection->disconnect();
                break;
            }

            if (ioctl(uinput, UI_DEV_CREATE) < 0) {
                qCritical("Error: Failed to create uinput device: %s", strerror(errno));
                message.data.error.type = RINPUT_INPUT_DEVICE_CREATE_FAILED;
                hton_rinput(&errorMessage);
                connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
                connection->disconnect();
                break;
            }

            // Echo back create message to indicate success
            errorMessage.type = RINPUT_CREATE;
            hton_rinput(&errorMessage);
            connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
            created = true;
            break;

        case RINPUT_DESTROY:
            if (!created) {
                qDebug("Warning: Remote trying to destroy device before creation");
                break;
            }
            if (ioctl(uinput, UI_DEV_DESTROY) < 0) {
                qWarning("Warning: Failed to destroy uinput device");
            }
            delete readNotifier;
            close(uinput);
            uinput = -1;
            created = false;
            connection->disconnect();
            break;

        case RINPUT_EVENT: {
            struct input_event event;

            /*
             * If a future kernel redefines event codes or types, rinputd will translate here.
             */

            if (message.data.event.type == RINPUT_EV_UTF8) {
                if (message.data.utf8.value) {
                    handleUTF8Event(message.data.utf8);
                }
                break;
            }

            event.type = message.data.event.type;
            event.code = message.data.event.code;
            event.value = message.data.event.value;

            if (event.type == RINPUT_EV_KEY && event.value == 256) {
                event.value = 1;
                handleEvent(event);

                static const struct input_event synEvent = { { 0, 0}, EV_SYN };
                handleEvent(synEvent);

                event.type = message.data.event.type;
                event.code = message.data.event.code;
                event.value = 0;
                handleEvent(event);
            }
            else {    
                handleEvent(event);
            }
            break;
        }

        default:
            qWarning("Error: Remote message type invalid");
            message.data.error.type = RINPUT_MESSAGE_TYPE_INVALID;
            errorMessage.data.error.code1 = message.type;
            hton_rinput(&errorMessage);
            connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
            break;
    }
}

void LinuxInputDevice::handleEvent(const struct input_event &event) {
    int ret = write(uinput, &event, sizeof(struct input_event));
    if (ret < (int)sizeof(struct input_event)) {
        if (ret < 0) {
            qWarning("Error: Failed to write input events");
        }
        else {
            qWarning("Warning: Not all input events handled");
        }
        rinput_message_t errorMessage = { RINPUT_ERROR, { RINPUT_INPUT_EVENTS_FAILED } };
        errorMessage.data.error.code1 = event.type;
        errorMessage.data.error.code2 = event.code;
        hton_rinput(&errorMessage);
        connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
    }
}

// Translate from UTF8 to Unicode, then simulate:
// 1. CTRL+SHIFT+U
// 2. Enter hexadecimal Unicode representation
// 3. Space
void LinuxInputDevice::handleUTF8Event(rinput_utf8_t &utf8) {
    // Get length of UTF8 character from first character format
    int length = 1;
    if (utf8.character[0] >= 0xC2 && utf8.character[0] <= 0xDF) {
        length = 2;
    }
    else if (utf8.character[0] >= 0xE0 && utf8.character[0] <= 0xEF) {
        length = 3;
    }
    else if (utf8.character[0] >= 0xF0 && utf8.character[0] <= 0xF4) {
        length = 4;
    }

    // Translate from UTF8 to Unicode
    QString string(QString::fromUtf8((const char *)utf8.character, length));
    if (string.length() != 1) {
        rinput_message_t errorMessage = { RINPUT_ERROR, { RINPUT_INPUT_EVENTS_FAILED } };
        errorMessage.data.error.code1 = RINPUT_EV_UTF8;
        hton_rinput(&errorMessage);
        connection->write(QByteArray((const char *)&errorMessage, sizeof(errorMessage)));
        return;
    }

    // Simulate unicode character entry sequence
    struct input_event event;
    event.type = EV_KEY;

    event.code = KEY_LEFTCTRL;
    event.value = 1;
    handleEvent(event);

    event.code = KEY_LEFTSHIFT;
    event.value = 1;
    handleEvent(event);

    event.code = KEY_U;
    event.value = 1;
    handleEvent(event);
    event.value = 0;
    handleEvent(event);

    event.code = KEY_LEFTSHIFT;
    event.value = 0;
    handleEvent(event);

    event.code = KEY_LEFTCTRL;
    event.value = 0;
    handleEvent(event);

    // Translate from Unicode to hex digit keys
    for (const ushort *unicode = string.utf16(); *unicode != 0; unicode++) {
        for (int shift = 12; shift >= 0; shift -= 4) {
            switch ((*unicode >> shift) & 0xF) {
                case 0:
                    event.code = KEY_0;
                    break;

                case 1:
                    event.code = KEY_1;
                    break;

                case 2:
                    event.code = KEY_2;
                    break;

                case 3:
                    event.code = KEY_3;
                    break;

                case 4:
                    event.code = KEY_4;
                    break;

                case 5:
                    event.code = KEY_5;
                    break;

                case 6:
                    event.code = KEY_6;
                    break;

                case 7:
                    event.code = KEY_7;
                    break;

                case 8:
                    event.code = KEY_8;
                    break;

                case 9:
                    event.code = KEY_9;
                    break;

                case 10:
                    event.code = KEY_A;
                    break;

                case 11:
                    event.code = KEY_B;
                    break;

                case 12:
                    event.code = KEY_C;
                    break;

                case 13:
                    event.code = KEY_D;
                    break;

                case 14:
                    event.code = KEY_E;
                    break;

                default:
                    event.code = KEY_F;
                    break;
            }

            event.value = 1;
            handleEvent(event);
            event.value = 0;
            handleEvent(event);
        }
    }

    // Enter space to finish entry
    event.code = KEY_SPACE;
    event.value = 1;
    handleEvent(event);
    event.value = 0;
    handleEvent(event);
}

void LinuxInputDevice::uinputReadyRead() {
    struct pollfd pollfd = { uinput, POLLIN };

    do {
        rinput_message_t message = { RINPUT_EVENT };
        struct input_event event;

        int ret = read(uinput, &event, sizeof(event));
        if (ret < (signed)sizeof(event)) {
            qCritical("Error: Could not read input event from uinput: %s", strerror(errno));
            return;
        }

        qDebug("Read input event from uinput: type: %hu, code: %hu, value: %d", event.type, event.code, event.value);

        message.data.event.type = event.type;
        message.data.event.code = event.code;
        message.data.event.value = event.value;

        hton_rinput(&message);
        connection->write(QByteArray((const char *)&message, sizeof(message)));
    } while (poll(&pollfd, 1, 0) == 1);
}

LinuxInputDevice::~LinuxInputDevice() {
    if (created) {
        ioctl(uinput, UI_DEV_DESTROY);
    }
    if (uinput >= 0) {
        close(uinput);
    }
}
