use crate::Globals;
use util::ResultExt;

use wayland_client::protocol::wl_surface::WlSurface;
use wayland_client::protocol::{wl_pointer::WlPointer, wl_shm::WlShm};
use wayland_client::Connection;
use wayland_cursor::{CursorImageBuffer, CursorTheme};

pub(crate) struct Cursor {
    theme: Option<CursorTheme>,
    theme_name: Option<String>,
    theme_size: u32,
    surface: WlSurface,
    size: u32,
    shm: WlShm,
    connection: Connection,
}

impl Drop for Cursor {
    fn drop(&mut self) {
        self.theme.take();
        self.surface.destroy();
    }
}

impl Cursor {
    pub fn new(connection: &Connection, globals: &Globals, size: u32) -> Self {
        Self {
            theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
            theme_name: None,
            theme_size: size,
            surface: globals.compositor.create_surface(&globals.qh, ()),
            shm: globals.shm.clone(),
            connection: connection.clone(),
            size,
        }
    }

    pub fn set_theme(&mut self, theme_name: &str) {
        if let Some(theme) = CursorTheme::load_from_name(
            &self.connection,
            self.shm.clone(),
            theme_name,
            self.theme_size,
        )
        .log_err()
        {
            self.theme = Some(theme);
            self.theme_name = Some(theme_name.to_string());
        } else if let Some(theme) =
            CursorTheme::load(&self.connection, self.shm.clone(), self.theme_size).log_err()
        {
            self.theme = Some(theme);
            self.theme_name = None;
        }
    }

    fn set_theme_size(&mut self, theme_size: u32) {
        self.theme = self
            .theme_name
            .as_ref()
            .and_then(|name| {
                CursorTheme::load_from_name(
                    &self.connection,
                    self.shm.clone(),
                    name.as_str(),
                    theme_size,
                )
                .log_err()
            })
            .or_else(|| {
                CursorTheme::load(&self.connection, self.shm.clone(), theme_size).log_err()
            });
    }

    pub fn set_size(&mut self, size: u32) {
        self.size = size;
        self.set_theme_size(size);
    }

    pub fn set_icon(
        &mut self,
        wl_pointer: &WlPointer,
        serial_id: u32,
        mut cursor_icon_name: &str,
        scale: i32,
    ) {
        self.set_theme_size(self.size * scale as u32);

        if let Some(theme) = &mut self.theme {
            let mut buffer: Option<&CursorImageBuffer>;

            if let Some(cursor) = theme.get_cursor(&cursor_icon_name) {
                buffer = Some(&cursor[0]);
            } else if let Some(cursor) = theme.get_cursor("default") {
                buffer = Some(&cursor[0]);
                cursor_icon_name = "default";
                log::warn!(
                    "Linux: Wayland: Unable to get cursor icon: {}. Using default cursor icon",
                    cursor_icon_name
                );
            } else {
                buffer = None;
                log::warn!("Linux: Wayland: Unable to get default cursor too!");
            }

            if let Some(buffer) = &mut buffer {
                let (width, height) = buffer.dimensions();
                let (hot_x, hot_y) = buffer.hotspot();

                self.surface.set_buffer_scale(scale);

                wl_pointer.set_cursor(
                    serial_id,
                    Some(&self.surface),
                    hot_x as i32 / scale,
                    hot_y as i32 / scale,
                );

                self.surface.attach(Some(&buffer), 0, 0);
                self.surface.damage(0, 0, width as i32, height as i32);
                self.surface.commit();
            }
        } else {
            log::warn!("Linux: Wayland: Unable to load cursor themes");
        }
    }
}
