/*
 * Copyright © Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2 or 3,
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// TODO: Report errors to Mir's logging facilities. We should do this following Mir's logging refactor.
// TODO: Implement continue_after_config and pause_for_config

// Some notes about the implementation here:
// 1. CXX-Rust doesn't support passing Option<T> to C++ functions, so I use booleans
//    instead.
// 2. All enums are changed to i32 for ABI stability

mod device;
mod event_processing;
mod ffi;
mod libinput_interface;
mod platform;

use crate::device::{LibinputDevice, LibinputDeviceMetadata, LibinputDeviceObserver};
use crate::platform::PlatformRs;

#[cxx::bridge(namespace = "mir::input::evdev_rs")]
mod ffi_bridge {
    #[repr(u32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum DeviceCapability {
        unknown = 0,
        pointer = 2,
        keyboard = 4,
        touchpad = 8,
        touchscreen = 16,
        gamepad = 32,
        joystick = 64,
        switch_ = 128,
        multitouch = 256,
        alpha_numeric = 512,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirKeyboardAction {
        mir_keyboard_action_up,
        mir_keyboard_action_down,
        mir_keyboard_action_repeat,
        mir_keyboard_action_modifiers,
        mir_keyboard_actions,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
    pub enum MirTouchAction {
        mir_touch_action_up = 0,
        mir_touch_action_down = 1,
        mir_touch_action_change = 2,
        #[default]
        mir_touch_actions,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
    pub enum MirTouchTooltype {
        #[default]
        mir_touch_tooltype_unknown = 0,
        mir_touch_tooltype_finger = 1,
        mir_touch_tooltype_stylus = 2,
        mir_touch_tooltypes,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirPointerAcceleration {
        mir_pointer_acceleration_none = 1,
        mir_pointer_acceleration_adaptive = 2,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirPointerAction {
        mir_pointer_action_button_up,
        mir_pointer_action_button_down,
        mir_pointer_action_enter,
        mir_pointer_action_leave,
        mir_pointer_action_motion,
        mir_pointer_actions,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirPointerAxisSource {
        mir_pointer_axis_source_none,
        mir_pointer_axis_source_wheel,
        mir_pointer_axis_source_finger,
        mir_pointer_axis_source_continuous,
        mir_pointer_axis_source_wheel_tilt,
    }

    #[repr(u32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirPointerButton {
        mir_pointer_button_primary = 1,
        mir_pointer_button_secondary = 2,
        mir_pointer_button_tertiary = 4,
        mir_pointer_button_back = 8,
        mir_pointer_button_forward = 16,
        mir_pointer_button_side = 32,
        mir_pointer_button_extra = 64,
        mir_pointer_button_task = 128,
    }

    #[repr(i32)]
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub enum MirPointerHandedness {
        mir_pointer_handedness_right = 0,
        mir_pointer_handedness_left = 1,
    }

    #[derive(Copy, Clone)]
    pub struct SetPointerSettingsData {
        pub handedness: i32,
        pub cursor_acceleration_bias: f64,
        pub acceleration: i32,
        pub horizontal_scroll_scale: f64,
        pub vertical_scroll_scale: f64,
    }

    #[derive(Copy, Clone, Default)]
    pub struct PointerSettings {
        pub is_set: bool,
        pub handedness: i32,
        pub cursor_acceleration_bias: f64,
        pub acceleration: i32,
        pub horizontal_scroll_scale: f64,
        pub vertical_scroll_scale: f64,
        pub has_error: bool,
    }

    // PointerEventData is declared as an extern C++ type mapped to the Rust struct above

    // KeyEventData is declared as an extern C++ type mapped to the Rust struct above

    // TouchContactData and TouchEventData defined as shared structs
    #[derive(Copy, Clone)]
    pub struct TouchContactData {
        pub touch_id: i32,
        pub action: i32,
        pub tooltype: i32,
        pub position_x: f32,
        pub position_y: f32,
        pub pressure: f32,
        pub touch_major: f32,
        pub touch_minor: f32,
        pub orientation: f32,
    }

    pub struct TouchEventData {
        pub has_time: bool,
        pub time_microseconds: u64,
        pub contacts: Vec<TouchContactData>,
    }

    extern "Rust" {
        type PlatformRs;
        type LibinputDeviceObserver;
        type LibinputDevice;
        type LibinputDeviceMetadata;

        fn start(self: &mut PlatformRs) -> bool;
        fn assign_seat(self: &mut PlatformRs);
        fn continue_after_config(self: &PlatformRs);
        fn pause_for_config(self: &PlatformRs);
        fn stop(self: &mut PlatformRs);
        unsafe fn libinput_fd(self: &mut PlatformRs) -> i32;
        pub fn process(self: &mut PlatformRs);
        fn create_device_observer(self: &PlatformRs) -> Box<LibinputDeviceObserver>;
        fn create_input_device(self: &mut PlatformRs, device_id: i32) -> Box<LibinputDevice>;

        fn activated(self: &mut LibinputDeviceObserver, fd: i32);
        fn suspended(self: &mut LibinputDeviceObserver);
        fn removed(self: &mut LibinputDeviceObserver);

        /// # Safety
        ///
        /// This is unsafe because it is receiving raw pointers from C++ as parameters.
        unsafe fn start(
            self: &mut LibinputDevice,
            input_sink: *mut InputSink,
            event_builder: *mut EventBuilder,
        );
        fn stop(self: &mut LibinputDevice);
        fn get_device_info(self: &LibinputDevice) -> Box<LibinputDeviceMetadata>;

        fn valid(self: &LibinputDeviceMetadata) -> bool;
        fn name(self: &LibinputDeviceMetadata) -> &str;
        fn unique_id(self: &LibinputDeviceMetadata) -> &str;
        fn capabilities(self: &LibinputDeviceMetadata) -> u32;
        fn get_pointer_settings(self: &LibinputDevice) -> Box<PointerSettings>;
        fn set_pointer_settings(self: &LibinputDevice, settings: &SetPointerSettingsData);

        fn evdev_rs_create(
            bridge: SharedPtr<PlatformBridge>,
            device_registry: SharedPtr<InputDeviceRegistry>,
            reporter: UniquePtr<InputReport>,
        ) -> Box<PlatformRs>;
    }

    unsafe extern "C++" {
        include!("platform_bridge.h");
        include!("input_report.h");
        include!("mir/input/input_device_registry.h");
        include!("mir/input/device_capability.h");
        include!("mir/input/input_sink.h");
        include!("mir/input/event_builder.h");
        include!("mir/events/event.h");
        include!("mir/events/scroll_axis.h");
        include!("mir_toolkit/events/enums.h");

        pub type PlatformBridge;
        pub type InputReport;
        pub type DeviceWrapper;
        pub type EventBuilderWrapper;
        pub type RectangleWrapper;
        // Map C++ KeyEventData to the Rust struct
        type KeyEventData = crate::ffi::KeyEventData;
        // Map C++ PointerEventData to the Rust struct
        type PointerEventData = crate::ffi::PointerEventData;
        // TouchContactData and TouchEventData are defined above in the bridge

        #[namespace = "mir::input"]
        pub type Device;

        #[namespace = "mir::input"]
        pub type InputDevice;

        #[namespace = "mir::input"]
        pub type InputDeviceRegistry;

        #[namespace = "mir::input"]
        pub type InputSink;

        #[namespace = "mir::input"]
        pub type EventBuilder;

        #[namespace = ""]
        pub type MirEvent;

        pub fn acquire_device(
            self: &PlatformBridge,
            major: i32,
            minor: i32,
        ) -> UniquePtr<DeviceWrapper>;
        pub fn bounding_rectangle(
            self: &PlatformBridge,
            sink: &InputSink,
        ) -> UniquePtr<RectangleWrapper>;
        pub fn create_input_device(self: &PlatformBridge, device_id: i32)
            -> SharedPtr<InputDevice>;
        pub fn raw_fd(self: &DeviceWrapper) -> i32;

        // # Safety
        //
        // This is unsafe because it receives a raw C++ pointer as an argument.
        pub unsafe fn create_event_builder_wrapper(
            self: &PlatformBridge,
            event_builder: *mut EventBuilder,
        ) -> UniquePtr<EventBuilderWrapper>;

        pub fn pointer_event(
            self: &EventBuilderWrapper,
            data: &PointerEventData,
        ) -> SharedPtr<MirEvent>;

        pub fn key_event(self: &EventBuilderWrapper, data: &KeyEventData) -> SharedPtr<MirEvent>;
        pub fn touch_event(
            self: &EventBuilderWrapper,
            data: &TouchEventData,
        ) -> SharedPtr<MirEvent>;

        #[namespace = "mir::input"]
        pub fn add_device(
            self: Pin<&mut InputDeviceRegistry>,
            device: &SharedPtr<InputDevice>,
        ) -> WeakPtr<Device>;

        #[namespace = "mir::input"]
        pub fn remove_device(self: Pin<&mut InputDeviceRegistry>, device: &SharedPtr<InputDevice>);

        #[namespace = "mir::input"]
        pub fn handle_input(self: Pin<&mut InputSink>, event: &SharedPtr<MirEvent>);

        pub fn x(self: &RectangleWrapper) -> i32;
        pub fn y(self: &RectangleWrapper) -> i32;
        pub fn width(self: &RectangleWrapper) -> i32;
        pub fn height(self: &RectangleWrapper) -> i32;

        pub fn received_event_from_kernel(
            self: &InputReport,
            when_microseconds: u64,
            type_: i32,
            code: i32,
            value: i32,
        );
    }
}

// Re-export the bridge module as ffi_bridge for easier access
pub use ffi_bridge::*;

/// Creates a platform on the Rust side of things.
///
/// The `bridge` and `device_registry` parameters are provided by the C++ side of things.
/// The C++ platform simply forwards all calls to the Rust implementation.
pub fn evdev_rs_create(
    bridge: cxx::SharedPtr<PlatformBridge>,
    device_registry: cxx::SharedPtr<InputDeviceRegistry>,
    report: cxx::UniquePtr<InputReport>,
) -> Box<PlatformRs> {
    return Box::new(PlatformRs::new(bridge, device_registry, report));
}

// # Safety
//
// The other side of the classes below are known by us to be thread-safe, so we can
// safely send it to another thread. However, Rust has no way of knowing that, so
// we have to assert it ourselves.
unsafe impl Send for PlatformBridge {}
unsafe impl Sync for PlatformBridge {}
unsafe impl Send for InputDeviceRegistry {}
unsafe impl Sync for InputDeviceRegistry {}
unsafe impl Send for InputDevice {}
unsafe impl Sync for InputDevice {}
unsafe impl Send for InputSink {}
unsafe impl Sync for InputSink {}
unsafe impl Send for EventBuilder {}
unsafe impl Sync for EventBuilder {}
