//! Utilities for compositor side cursor theming support
//!
//! This protocol allows clients to request compositor to draw a cursor for them, thus resulting
//! in more consistent look and feel of the cursor across the applications.
//!
//! ## Initialization
//!
//! ```
//! extern crate smithay;
//! extern crate wayland_server;
//!
//! use smithay::wayland::cursor_shape::CursorShapeManagerState;
//!
//! # use smithay::backend::input::{KeyState, TabletToolDescriptor};
//! # use smithay::input::{
//! #   pointer::{PointerTarget, AxisFrame, MotionEvent, ButtonEvent, RelativeMotionEvent,
//! #             GestureSwipeBeginEvent, GestureSwipeUpdateEvent, GestureSwipeEndEvent,
//! #             GesturePinchBeginEvent, GesturePinchUpdateEvent, GesturePinchEndEvent,
//! #             GestureHoldBeginEvent, GestureHoldEndEvent},
//! #   keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
//! #   touch::{DownEvent, UpEvent, MotionEvent as TouchMotionEvent, ShapeEvent, OrientationEvent, TouchTarget, FrameMarker},
//! #   tablet::{Tablet, TabletSeatHandler, tool::{self, TabletToolTarget}},
//! #   Seat, SeatHandler, SeatState,
//! # };
//! # use smithay::utils::{IsAlive, Serial};
//! # use smithay::wayland::seat::WaylandFocus;
//! # use wayland_server::protocol::wl_surface;
//!
//! # #[derive(Debug, Clone, PartialEq)]
//! # struct Target;
//! # impl IsAlive for Target {
//! #   fn alive(&self) -> bool { true }
//! # }
//! # impl WaylandFocus for Target {
//! #   fn wl_surface(&self) -> Option<std::borrow::Cow<'_, wl_surface::WlSurface>> {
//! #       None
//! #   }
//! # }
//!
//! # impl PointerTarget<State> for Target {
//! #   fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {}
//! #   fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {}
//! #   fn relative_motion(&self, seat: &Seat<State>, data: &mut State, event: &RelativeMotionEvent) {}
//! #   fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {}
//! #   fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {}
//! #   fn frame(&self, seat: &Seat<State>, data: &mut State) {}
//! #   fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {}
//! #   fn gesture_swipe_begin(&self, seat: &Seat<State>, data: &mut State, event: &GestureSwipeBeginEvent) {}
//! #   fn gesture_swipe_update(&self, seat: &Seat<State>, data: &mut State, event: &GestureSwipeUpdateEvent) {}
//! #   fn gesture_swipe_end(&self, seat: &Seat<State>, data: &mut State, event: &GestureSwipeEndEvent) {}
//! #   fn gesture_pinch_begin(&self, seat: &Seat<State>, data: &mut State, event: &GesturePinchBeginEvent) {}
//! #   fn gesture_pinch_update(&self, seat: &Seat<State>, data: &mut State, event: &GesturePinchUpdateEvent) {}
//! #   fn gesture_pinch_end(&self, seat: &Seat<State>, data: &mut State, event: &GesturePinchEndEvent) {}
//! #   fn gesture_hold_begin(&self, seat: &Seat<State>, data: &mut State, event: &GestureHoldBeginEvent) {}
//! #   fn gesture_hold_end(&self, seat: &Seat<State>, data: &mut State, event: &GestureHoldEndEvent) {}
//! # }
//! # impl KeyboardTarget<State> for Target {
//! #   fn enter(&self, seat: &Seat<State>, data: &mut State, keys: Vec<KeysymHandle<'_>>, serial: Serial) {}
//! #   fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial) {}
//! #   fn key(
//! #       &self,
//! #       seat: &Seat<State>,
//! #       data: &mut State,
//! #       key: KeysymHandle<'_>,
//! #       state: KeyState,
//! #       serial: Serial,
//! #       time: u32,
//! #   ) {}
//! #   fn modifiers(&self, seat: &Seat<State>, data: &mut State, modifiers: ModifiersState, serial: Serial) {}
//! # }
//! # impl TouchTarget<State> for Target {
//! #   fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {}
//! #   fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {}
//! #   fn motion(&self, seat: &Seat<State>, data: &mut State, event: &TouchMotionEvent) {}
//! #   fn frame(&self, seat: &Seat<State>, data: &mut State, marker: FrameMarker) {}
//! #   fn cancel(&self, seat: &Seat<State>, data: &mut State, marker: FrameMarker) {}
//! #   fn shape(&self, seat: &Seat<State>, data: &mut State, event: &ShapeEvent) {}
//! #   fn orientation(&self, seat: &Seat<State>, data: &mut State, event: &OrientationEvent) {}
//! #   fn last_frame(&self, seat: &Seat<State>, data: &mut State) -> Option<FrameMarker> { unimplemented!() }
//! # }
//! # impl TabletToolTarget<State> for Target {
//! #   fn proximity_in(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, tablet: &Tablet, serial: Serial) {}
//! #   fn proximity_out(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor) {}
//! #   fn down(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, event: &tool::DownEvent) {}
//! #   fn up(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, event: &tool::UpEvent) {}
//! #   fn motion(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, event: &tool::MotionEvent) {}
//! #   fn axis(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, frame: tool::AxisFrame) {}
//! #   fn button(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, event: &tool::ButtonEvent) {}
//! #   fn frame(&self, seat: &Seat<State>, data: &mut State, tool_descriptor: &TabletToolDescriptor, time: u32) {}
//! # }
//! # struct State {
//! #     seat_state: SeatState<Self>,
//! # };
//! # let mut display = wayland_server::Display::<State>::new().unwrap();
//! # impl SeatHandler for State {
//! #     type KeyboardFocus = Target;
//! #     type PointerFocus = Target;
//! #     type TouchFocus = Target;
//! #
//! #     fn seat_state(&mut self) -> &mut SeatState<Self> {
//! #         &mut self.seat_state
//! #     }
//! # }
//! # impl TabletSeatHandler for State {
//! #     type ToolFocus = Target;
//! # }
//!
//! let state = CursorShapeManagerState::new::<State>(&display.handle());
//!
//! smithay::delegate_dispatch2!(State);
//! ```

use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_device_v1::Request as ShapeRequest;
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_device_v1::Shape;
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_device_v1::WpCursorShapeDeviceV1 as CursorShapeDevice;
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_manager_v1::Request as ManagerRequest;
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_manager_v1::WpCursorShapeManagerV1 as CursorShapeManager;
use wayland_protocols::wp::tablet::zv2::server::zwp_tablet_tool_v2::ZwpTabletToolV2;
use wayland_server::GlobalDispatch;
use wayland_server::Resource;
use wayland_server::WEnum;
use wayland_server::Weak;
use wayland_server::{Dispatch, DisplayHandle, backend::GlobalId};

use crate::input::SeatHandler;
use crate::input::WeakSeat;
use crate::input::pointer::{CursorIcon, CursorImageStatus};
use crate::input::tablet::TabletSeatHandler;
use crate::utils::Serial;
use crate::wayland::seat::{WaylandFocus, pointer::allow_setting_cursor};
use crate::wayland::tablet_manager::TabletToolUserData;
use crate::wayland::tablet_manager::tablet_tool;
use crate::wayland::{Dispatch2, GlobalData, GlobalDispatch2};

use super::seat::PointerUserData;

/// State of the cursor shape manager.
#[derive(Debug)]
pub struct CursorShapeManagerState {
    global: GlobalId,
}

impl CursorShapeManagerState {
    /// Register new [CursorShapeManager] global.
    pub fn new<D>(display: &DisplayHandle) -> Self
    where
        D: GlobalDispatch<CursorShapeManager, GlobalData>,
        D: Dispatch<CursorShapeManager, GlobalData>,
        D: SeatHandler,
        D: 'static,
    {
        let global = display.create_global::<D, CursorShapeManager, _>(2, GlobalData);
        Self { global }
    }

    /// [CursorShapeManager] GlobalId getter.
    pub fn global(&self) -> GlobalId {
        self.global.clone()
    }
}

impl<D> GlobalDispatch2<CursorShapeManager, D> for GlobalData
where
    D: Dispatch<CursorShapeManager, GlobalData>,
    D: SeatHandler,
    D: 'static,
{
    fn bind(
        &self,
        _state: &mut D,
        _handle: &DisplayHandle,
        _client: &wayland_server::Client,
        resource: wayland_server::New<CursorShapeManager>,
        data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        data_init.init(resource, GlobalData);
    }
}

impl<D> Dispatch2<CursorShapeManager, D> for GlobalData
where
    D: Dispatch<CursorShapeDevice, CursorShapeDeviceUserData<D>>,
    D: SeatHandler,
    D: 'static,
{
    fn request(
        &self,
        state: &mut D,
        _client: &wayland_server::Client,
        _resource: &CursorShapeManager,
        request: <CursorShapeManager as wayland_server::Resource>::Request,
        _dhandle: &DisplayHandle,
        data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        match request {
            ManagerRequest::GetPointer {
                cursor_shape_device,
                pointer,
            } => {
                let pointer_data = pointer.data::<PointerUserData<D>>();
                let handle = match pointer_data.and_then(|data| data.handle.as_ref()) {
                    Some(handle) => handle,
                    None => return,
                };

                let Some(seat) = state
                    .seat_state()
                    .seats
                    .iter()
                    .find(|seat| seat.get_pointer().map(|h| &h == handle).unwrap_or(false))
                    .cloned()
                else {
                    return;
                };

                data_init.init(
                    cursor_shape_device,
                    CursorShapeDeviceUserData(CursorShapeDeviceUserDataInner::Pointer {
                        seat: seat.downgrade(),
                    }),
                );
            }
            ManagerRequest::GetTabletToolV2 {
                cursor_shape_device,
                tablet_tool,
            } => {
                data_init.init(
                    cursor_shape_device,
                    CursorShapeDeviceUserData(CursorShapeDeviceUserDataInner::Tablet(
                        tablet_tool.downgrade(),
                    )),
                );
            }
            ManagerRequest::Destroy => {}
            _ => unreachable!(),
        }
    }
}

#[doc(hidden)]
#[derive(Debug, Clone)]
pub struct CursorShapeDeviceUserData<D: SeatHandler>(CursorShapeDeviceUserDataInner<D>);

#[derive(Debug, Clone)]
pub(crate) enum CursorShapeDeviceUserDataInner<D: SeatHandler> {
    /// The device was created for the pointer.
    Pointer { seat: WeakSeat<D> },
    /// The device was created for the tablet tool.
    Tablet(Weak<ZwpTabletToolV2>),
}

impl<D> Dispatch2<CursorShapeDevice, D> for CursorShapeDeviceUserData<D>
where
    D: SeatHandler + TabletSeatHandler,
    <D as SeatHandler>::PointerFocus: WaylandFocus,
    <D as TabletSeatHandler>::ToolFocus: WaylandFocus,
    D: 'static,
{
    fn request(
        &self,
        state: &mut D,
        _client: &wayland_server::Client,
        resource: &CursorShapeDevice,
        request: <CursorShapeDevice as wayland_server::Resource>::Request,
        _dhandle: &DisplayHandle,
        _data_init: &mut wayland_server::DataInit<'_, D>,
    ) {
        match request {
            ShapeRequest::SetShape {
                serial,
                shape: WEnum::Value(shape),
            } => {
                match &self.0 {
                    CursorShapeDeviceUserDataInner::Pointer { seat } => {
                        let Some(seat) = seat.upgrade() else {
                            return;
                        };

                        let Some(handle) = seat.get_pointer() else {
                            // When the pointer capability is removed from the wl_seat,
                            // the wp_cursor_shape_device_v1 object becomes inert.
                            return;
                        };

                        if !allow_setting_cursor(&handle, Serial(serial), &resource.id()) {
                            return;
                        }

                        let cursor_icon = shape_to_cursor_icon(shape);
                        state.cursor_image(&seat, CursorImageStatus::Named(cursor_icon));
                    }
                    CursorShapeDeviceUserDataInner::Tablet(tablet) => {
                        let Ok(tablet) = tablet.upgrade() else {
                            // When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1 object becomes inert.
                            return;
                        };
                        let tablet_data = match tablet.data::<TabletToolUserData<D>>() {
                            Some(data) => data,
                            None => return,
                        };

                        let Some(handle) = tablet_data.handle.upgrade() else {
                            return;
                        };

                        if !tablet_tool::allow_setting_cursor(&handle, Serial(serial), &resource.id()) {
                            return;
                        }

                        let cursor_icon = shape_to_cursor_icon(shape);
                        state
                            .tablet_tool_image(&handle.arc.descriptor, CursorImageStatus::Named(cursor_icon));
                    }
                }
            }
            ShapeRequest::SetShape { .. } => {
                // Ignore unknown shapes.
            }
            ShapeRequest::Destroy => {}
            _ => unreachable!(),
        }
    }
}

fn shape_to_cursor_icon(shape: Shape) -> CursorIcon {
    match shape {
        Shape::Default => CursorIcon::Default,
        Shape::ContextMenu => CursorIcon::ContextMenu,
        Shape::Help => CursorIcon::Help,
        Shape::Pointer => CursorIcon::Pointer,
        Shape::Progress => CursorIcon::Progress,
        Shape::Wait => CursorIcon::Wait,
        Shape::Cell => CursorIcon::Cell,
        Shape::Crosshair => CursorIcon::Crosshair,
        Shape::Text => CursorIcon::Text,
        Shape::VerticalText => CursorIcon::VerticalText,
        Shape::Alias => CursorIcon::Alias,
        Shape::Copy => CursorIcon::Copy,
        Shape::Move => CursorIcon::Move,
        Shape::NoDrop => CursorIcon::NoDrop,
        Shape::NotAllowed => CursorIcon::NotAllowed,
        Shape::Grab => CursorIcon::Grab,
        Shape::Grabbing => CursorIcon::Grabbing,
        Shape::EResize => CursorIcon::EResize,
        Shape::NResize => CursorIcon::NResize,
        Shape::NeResize => CursorIcon::NeResize,
        Shape::NwResize => CursorIcon::NwResize,
        Shape::SResize => CursorIcon::SResize,
        Shape::SeResize => CursorIcon::SeResize,
        Shape::SwResize => CursorIcon::SwResize,
        Shape::WResize => CursorIcon::WResize,
        Shape::EwResize => CursorIcon::EwResize,
        Shape::NsResize => CursorIcon::NsResize,
        Shape::NeswResize => CursorIcon::NeswResize,
        Shape::NwseResize => CursorIcon::NwseResize,
        Shape::ColResize => CursorIcon::ColResize,
        Shape::RowResize => CursorIcon::RowResize,
        Shape::AllScroll => CursorIcon::AllScroll,
        Shape::ZoomIn => CursorIcon::ZoomIn,
        Shape::ZoomOut => CursorIcon::ZoomOut,
        Shape::DndAsk => CursorIcon::DndAsk,
        Shape::AllResize => CursorIcon::AllResize,
        _ => CursorIcon::Default,
    }
}
