/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "events.h"
#include "device.h"

#include <QSize>

namespace KWin
{
namespace LibInput
{

Event *Event::create(libinput_event *event)
{
    if (!event) {
        return nullptr;
    }
    const auto t = libinput_event_get_type(event);
    // TODO: add touch events
    // TODO: add device notify events
    switch (t) {
    case LIBINPUT_EVENT_KEYBOARD_KEY:
        return new KeyEvent(event);
    case LIBINPUT_EVENT_POINTER_AXIS:
    case LIBINPUT_EVENT_POINTER_BUTTON:
    case LIBINPUT_EVENT_POINTER_MOTION:
    case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
        return new PointerEvent(event, t);
    case LIBINPUT_EVENT_TOUCH_DOWN:
    case LIBINPUT_EVENT_TOUCH_UP:
    case LIBINPUT_EVENT_TOUCH_MOTION:
    case LIBINPUT_EVENT_TOUCH_CANCEL:
    case LIBINPUT_EVENT_TOUCH_FRAME:
        return new TouchEvent(event, t);
    case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN:
    case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE:
    case LIBINPUT_EVENT_GESTURE_SWIPE_END:
        return new SwipeGestureEvent(event, t);
    case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN:
    case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE:
    case LIBINPUT_EVENT_GESTURE_PINCH_END:
        return new PinchGestureEvent(event, t);
    case LIBINPUT_EVENT_RAW_TAP:
        return new TapGestureEvent(event, t);
    case LIBINPUT_EVENT_GESTURE_HOLD_BEGIN:
    case LIBINPUT_EVENT_GESTURE_HOLD_END:
        return new HoldGestureEvent(event, t);
    case LIBINPUT_EVENT_TABLET_TOOL_AXIS:
    case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY:
    case LIBINPUT_EVENT_TABLET_TOOL_TIP:
        return new TabletToolEvent(event, t);
    case LIBINPUT_EVENT_TABLET_TOOL_BUTTON:
        return new TabletToolButtonEvent(event, t);
    case LIBINPUT_EVENT_TABLET_PAD_RING:
        return new TabletPadRingEvent(event, t);
    case LIBINPUT_EVENT_TABLET_PAD_STRIP:
        return new TabletPadStripEvent(event, t);
    case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
        return new TabletPadButtonEvent(event, t);
    case LIBINPUT_EVENT_SWITCH_TOGGLE:
        return new SwitchEvent(event, t);
    default:
        return new Event(event, t);
    }
}

Event::Event(libinput_event *event, libinput_event_type type)
    : m_event(event)
    , m_type(type)
    , m_device(nullptr)
{
}

Event::~Event()
{
    libinput_event_destroy(m_event);
}

Device *Event::device() const
{
    if (!m_device) {
        m_device = Device::getDevice(libinput_event_get_device(m_event));
    }
    return m_device;
}

libinput_device *Event::nativeDevice() const
{
    if (m_device) {
        return m_device->device();
    }
    return libinput_event_get_device(m_event);
}

KeyEvent::KeyEvent(libinput_event *event)
    : Event(event, LIBINPUT_EVENT_KEYBOARD_KEY)
    , m_keyboardEvent(libinput_event_get_keyboard_event(event))
{
}

KeyEvent::~KeyEvent() = default;

uint32_t KeyEvent::key() const
{
    return libinput_event_keyboard_get_key(m_keyboardEvent);
}

InputRedirection::KeyboardKeyState KeyEvent::state() const
{
    switch (libinput_event_keyboard_get_key_state(m_keyboardEvent)) {
    case LIBINPUT_KEY_STATE_PRESSED:
        return InputRedirection::KeyboardKeyPressed;
    case LIBINPUT_KEY_STATE_RELEASED:
        return InputRedirection::KeyboardKeyReleased;
    default:
        Q_UNREACHABLE();
    }
}

uint32_t KeyEvent::time() const
{
    return libinput_event_keyboard_get_time(m_keyboardEvent);
}

PointerEvent::PointerEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_pointerEvent(libinput_event_get_pointer_event(event))
{
}

PointerEvent::~PointerEvent() = default;

QPointF PointerEvent::absolutePos() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
    return QPointF(libinput_event_pointer_get_absolute_x(m_pointerEvent),
                   libinput_event_pointer_get_absolute_y(m_pointerEvent));
}

QPointF PointerEvent::absolutePos(const QSize &size) const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE);
    return QPointF(libinput_event_pointer_get_absolute_x_transformed(m_pointerEvent, size.width()),
                   libinput_event_pointer_get_absolute_y_transformed(m_pointerEvent, size.height()));
}

QSizeF PointerEvent::delta() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION);
    return QSizeF(libinput_event_pointer_get_dx(m_pointerEvent), libinput_event_pointer_get_dy(m_pointerEvent));
}

QSizeF PointerEvent::deltaUnaccelerated() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_MOTION);
    return QSizeF(libinput_event_pointer_get_dx_unaccelerated(m_pointerEvent), libinput_event_pointer_get_dy_unaccelerated(m_pointerEvent));
}

uint32_t PointerEvent::time() const
{
    return libinput_event_pointer_get_time(m_pointerEvent);
}

quint64 PointerEvent::timeMicroseconds() const
{
    return libinput_event_pointer_get_time_usec(m_pointerEvent);
}

uint32_t PointerEvent::button() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON);
    return libinput_event_pointer_get_button(m_pointerEvent);
}

InputRedirection::PointerButtonState PointerEvent::buttonState() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_BUTTON);
    switch (libinput_event_pointer_get_button_state(m_pointerEvent)) {
    case LIBINPUT_BUTTON_STATE_PRESSED:
        return InputRedirection::PointerButtonPressed;
    case LIBINPUT_BUTTON_STATE_RELEASED:
        return InputRedirection::PointerButtonReleased;
    default:
        Q_UNREACHABLE();
    }
}

QVector<InputRedirection::PointerAxis> PointerEvent::axis() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS);
    QVector<InputRedirection::PointerAxis> a;
    if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL)) {
        a << InputRedirection::PointerAxisHorizontal;
    }
    if (libinput_event_pointer_has_axis(m_pointerEvent, LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL)) {
        a << InputRedirection::PointerAxisVertical;
    }
    return a;
}

qreal PointerEvent::axisValue(InputRedirection::PointerAxis axis) const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS);
    const libinput_pointer_axis a = axis == InputRedirection::PointerAxisHorizontal
                                          ? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL
                                          : LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
    return libinput_event_pointer_get_axis_value(m_pointerEvent, a) * device()->scrollFactor();
}

qint32 PointerEvent::discreteAxisValue(InputRedirection::PointerAxis axis) const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS);
    const libinput_pointer_axis a = (axis == InputRedirection::PointerAxisHorizontal)
        ? LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL
        : LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL;
    return libinput_event_pointer_get_axis_value_discrete(m_pointerEvent, a) * device()->scrollFactor();
}

InputRedirection::PointerAxisSource PointerEvent::axisSource() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_POINTER_AXIS);
    switch (libinput_event_pointer_get_axis_source(m_pointerEvent)) {
    case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
        return InputRedirection::PointerAxisSourceWheel;
    case LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
        return InputRedirection::PointerAxisSourceFinger;
    case LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
        return InputRedirection::PointerAxisSourceContinuous;
    case LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
        return InputRedirection::PointerAxisSourceWheelTilt;
    default:
        return InputRedirection::PointerAxisSourceUnknown;
    }
}

TouchEvent::TouchEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_touchEvent(libinput_event_get_touch_event(event))
{
}

TouchEvent::~TouchEvent() = default;

quint32 TouchEvent::time() const
{
    return libinput_event_touch_get_time(m_touchEvent);
}

QPointF TouchEvent::absolutePos() const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION);
    return QPointF(libinput_event_touch_get_x(m_touchEvent),
                   libinput_event_touch_get_y(m_touchEvent));
}

QPointF TouchEvent::absolutePos(const QSize &size) const
{
    Q_ASSERT(type() == LIBINPUT_EVENT_TOUCH_DOWN || type() == LIBINPUT_EVENT_TOUCH_MOTION);
    return QPointF(libinput_event_touch_get_x_transformed(m_touchEvent, size.width()),
                   libinput_event_touch_get_y_transformed(m_touchEvent, size.height()));
}

qint32 TouchEvent::id() const
{
    Q_ASSERT(type() != LIBINPUT_EVENT_TOUCH_FRAME);

    return libinput_event_touch_get_seat_slot(m_touchEvent);
}

GestureEvent::GestureEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
{
    if (type != LIBINPUT_EVENT_RAW_TAP) {
        m_gestureEvent = libinput_event_get_gesture_event(event);
    }
}

GestureEvent::~GestureEvent() = default;

quint32 GestureEvent::time() const
{
    return libinput_event_gesture_get_time(m_gestureEvent);
}

int GestureEvent::fingerCount() const
{
    return libinput_event_gesture_get_finger_count(m_gestureEvent);
}

QSizeF GestureEvent::delta() const
{
    return QSizeF(libinput_event_gesture_get_dx(m_gestureEvent),
                  libinput_event_gesture_get_dy(m_gestureEvent));
}

bool GestureEvent::isCancelled() const
{
    return libinput_event_gesture_get_cancelled(m_gestureEvent) != 0;
}

PinchGestureEvent::PinchGestureEvent(libinput_event *event, libinput_event_type type)
    : GestureEvent(event, type)
{
}

PinchGestureEvent::~PinchGestureEvent() = default;

qreal PinchGestureEvent::scale() const
{
    return libinput_event_gesture_get_scale(m_gestureEvent);
}

qreal PinchGestureEvent::angleDelta() const
{
    return libinput_event_gesture_get_angle_delta(m_gestureEvent);
}

SwipeGestureEvent::SwipeGestureEvent(libinput_event *event, libinput_event_type type)
    : GestureEvent(event, type)
{
}

SwipeGestureEvent::~SwipeGestureEvent() = default;

TapGestureEvent::TapGestureEvent(libinput_event *event, libinput_event_type type)
    : GestureEvent(event, type)
{
}

TapGestureEvent::~TapGestureEvent() = default;

HoldGestureEvent::HoldGestureEvent(libinput_event *event, libinput_event_type type)
    : GestureEvent(event, type)
{
}

HoldGestureEvent::~HoldGestureEvent() = default;

SwitchEvent::SwitchEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_switchEvent(libinput_event_get_switch_event(event))
{
}

SwitchEvent::~SwitchEvent() = default;

SwitchEvent::State SwitchEvent::state() const
{
    switch (libinput_event_switch_get_switch_state(m_switchEvent))
    {
    case LIBINPUT_SWITCH_STATE_OFF:
        return State::Off;
    case LIBINPUT_SWITCH_STATE_ON:
        return State::On;
    default:
        Q_UNREACHABLE();
    }
    return State::Off;
}

quint32 SwitchEvent::time() const
{
    return libinput_event_switch_get_time(m_switchEvent);
}

quint64 SwitchEvent::timeMicroseconds() const
{
    return libinput_event_switch_get_time_usec(m_switchEvent);
}

TabletToolEvent::TabletToolEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_tabletToolEvent(libinput_event_get_tablet_tool_event(event))
{
}

TabletToolButtonEvent::TabletToolButtonEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_tabletToolEvent(libinput_event_get_tablet_tool_event(event))
{
}

TabletPadButtonEvent::TabletPadButtonEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}

TabletPadStripEvent::TabletPadStripEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}

TabletPadRingEvent::TabletPadRingEvent(libinput_event *event, libinput_event_type type)
    : Event(event, type)
    , m_tabletPadEvent(libinput_event_get_tablet_pad_event(event))
{
}
}
}
