use {
    crate::{
        cursor::Cursor,
        cursor_user::CursorUser,
        fixed::Fixed,
        ifs::wl_surface::WlSurface,
        leaks::Tracker,
        rect::Rect,
        renderer::Renderer,
        scale::Scale,
        tree::{Node, NodeLocation, NodeVisitorBase, OutputNode},
    },
    std::{cell::Cell, rc::Rc},
};

pub struct CursorSurface {
    user: Rc<CursorUser>,
    surface: Rc<WlSurface>,
    hotspot: Cell<(i32, i32)>,
    extents: Cell<Rect>,
    pub tracker: Tracker<Self>,
}

impl CursorSurface {
    pub fn new(user: &Rc<CursorUser>, surface: &Rc<WlSurface>) -> Self {
        Self {
            user: user.clone(),
            surface: surface.clone(),
            hotspot: Cell::new((0, 0)),
            extents: Cell::new(Default::default()),
            tracker: Default::default(),
        }
    }

    fn update_extents(&self) {
        let (hot_x, hot_y) = self.hotspot.get();
        self.extents
            .set(self.surface.extents.get().move_(-hot_x, -hot_y));
    }

    pub fn handle_surface_destroy(&self) {
        self.user.set(None);
    }

    pub fn handle_buffer_change(&self) {
        self.update_extents();
    }

    pub fn set_hotspot(&self, x: i32, y: i32) {
        self.hotspot.set((x, y));
        self.update_extents();
    }

    pub fn dec_hotspot(&self, hotspot_dx: i32, hotspot_dy: i32) {
        let (hot_x, hot_y) = self.hotspot.get();
        self.hotspot.set((hot_x - hotspot_dx, hot_y - hotspot_dy));
        self.update_extents();
    }

    pub fn update_hardware_cursor(&self) {
        self.user.update_hardware_cursor();
    }

    pub fn needs_damage_tracking(&self) -> bool {
        self.user.software_cursor()
    }

    pub fn surface_position(&self) -> (i32, i32) {
        let (x, y) = self.user.position();
        let (dx, dy) = self.hotspot.get();
        (x.to_int() - dx, y.to_int() - dy)
    }
}

impl Cursor for CursorSurface {
    fn render(&self, renderer: &mut Renderer, x: Fixed, y: Fixed) {
        let x_int = x.round_down();
        let y_int = y.round_down();
        let extents = self.extents.get().move_(x_int, y_int);
        if extents.intersects(&renderer.logical_extents()) {
            let (hot_x, hot_y) = self.hotspot.get();
            let scale = renderer.scale();
            if scale != 1 {
                let scale = scale.to_f64();
                let (hot_x, hot_y) = (Fixed::from_int(hot_x), Fixed::from_int(hot_y));
                let x = ((x - hot_x).to_f64() * scale).round() as _;
                let y = ((y - hot_y).to_f64() * scale).round() as _;
                renderer.render_surface_scaled(&self.surface, x, y, None, None, false);
            } else {
                renderer.render_surface(&self.surface, x_int - hot_x, y_int - hot_y, None);
            }
        }
    }

    fn render_hardware_cursor(&self, renderer: &mut Renderer) {
        let extents = self.surface.extents.get();
        renderer.render_surface(&self.surface, -extents.x1(), -extents.y1(), None);

        struct FrameRequests(u32);
        impl NodeVisitorBase for FrameRequests {
            fn visit_surface(&mut self, node: &Rc<WlSurface>) {
                for mut fr in node.frame_requests.borrow_mut().drain(..) {
                    fr.now = self.0;
                    drop(fr);
                }
                node.presentation_feedback.borrow_mut().clear();
                node.latched_presentation_feedback.borrow_mut().clear();
                node.node_visit_children(self);
            }
        }
        FrameRequests(self.surface.client.state.now_msec() as u32).visit_surface(&self.surface);
    }

    fn extents_at_scale(&self, scale: Scale) -> Rect {
        let rect = self.extents.get();
        if scale == 1 {
            return rect;
        }
        let scale = scale.to_f64();
        Rect::new_saturating(
            (rect.x1() as f64 * scale).ceil() as _,
            (rect.y1() as f64 * scale).ceil() as _,
            (rect.x2() as f64 * scale).ceil() as _,
            (rect.y2() as f64 * scale).ceil() as _,
        )
    }

    fn set_output(&self, output: &Rc<OutputNode>) {
        self.surface
            .set_output(output, NodeLocation::Output(output.id));
    }

    fn handle_set(self: Rc<Self>) {
        self.surface.cursors.insert(self.user.id, self.clone());
        if self.surface.cursors.is_not_empty() {
            self.surface
                .set_visible(self.surface.client.state.root_visible());
        }
    }

    fn handle_unset(&self) {
        self.surface.cursors.remove(&self.user.id);
        if self.surface.cursors.is_empty() {
            self.surface.set_visible(false);
        }
    }

    fn set_visible(&self, visible: bool) {
        self.surface.set_visible(visible);
    }
}
