#include "PenDevice.h"

#include <QSocketNotifier>

#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/input.h>

PenDevice::PenDevice(int screenWidth, int screenHeight,
                     const QString &devicePath, QObject *parent)
    : QObject(parent), m_screenW(screenWidth), m_screenH(screenHeight)
{
    m_fd = ::open(devicePath.toLocal8Bit().constData(), O_RDONLY | O_NONBLOCK);
    if (m_fd < 0) {
        qWarning("PenDevice: cannot open %s", qPrintable(devicePath));
        return;
    }

    input_absinfo ai;
    if (ioctl(m_fd, EVIOCGABS(ABS_X), &ai) == 0) { m_xMin = ai.minimum; m_xMax = ai.maximum; }
    if (ioctl(m_fd, EVIOCGABS(ABS_Y), &ai) == 0) { m_yMin = ai.minimum; m_yMax = ai.maximum; }
    if (ioctl(m_fd, EVIOCGABS(ABS_PRESSURE), &ai) == 0) { m_pMin = ai.minimum; m_pMax = ai.maximum; }

    m_notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
    connect(m_notifier, &QSocketNotifier::activated, this, &PenDevice::readEvents);
}

PenDevice::~PenDevice()
{
    if (m_fd >= 0)
        ::close(m_fd);
}

QPointF PenDevice::mapToScreen() const
{
    double nx = (m_xMax > m_xMin) ? double(m_rawX - m_xMin) / double(m_xMax - m_xMin) : 0.0;
    double ny = (m_yMax > m_yMin) ? double(m_rawY - m_yMin) / double(m_yMax - m_yMin) : 0.0;
    if (m_invertX) nx = 1.0 - nx;
    if (m_invertY) ny = 1.0 - ny;
    if (m_swapXY) { double t = nx; nx = ny; ny = t; }
    return QPointF(nx * m_screenW, ny * m_screenH);
}

void PenDevice::readEvents()
{
    struct input_event ev;
    ssize_t n;
    while ((n = ::read(m_fd, &ev, sizeof(ev))) == sizeof(ev)) {
        switch (ev.type) {
        case EV_ABS:
            if (ev.code == ABS_X)            m_rawX = ev.value;
            else if (ev.code == ABS_Y)       m_rawY = ev.value;
            else if (ev.code == ABS_PRESSURE) m_rawP = ev.value;
            else if (ev.code == ABS_TILT_X)  m_rawTiltX = ev.value;
            else if (ev.code == ABS_TILT_Y)  m_rawTiltY = ev.value;
            break;
        case EV_KEY:
            if (ev.code == BTN_TOUCH) {
                m_touching = (ev.value != 0);
                if (m_touching) emit strokeStart(mapToScreen());
                else            emit strokeEnd();
            } else if (ev.code == BTN_TOOL_RUBBER) {
                m_eraser = (ev.value != 0);
            }
            break;
        case EV_SYN:
            if (ev.code == SYN_REPORT && m_touching) {
                double pressure = (m_pMax > m_pMin)
                    ? double(m_rawP - m_pMin) / double(m_pMax - m_pMin) : 1.0;
                const double tiltX = m_rawTiltX / 9000.0; // -1 .. 1
                const double tiltY = m_rawTiltY / 9000.0;
                emit strokeMove(mapToScreen(), pressure, tiltX, tiltY, m_eraser);
            }
            break;
        default:
            break;
        }
    }
}
