//! The popup tree and surface hit-testing.

use super::*;

impl<S: Surface + 'static> Window<S> {
    pub fn surface_at(
        &self,
        output_scale: f64,
        position: Point<f64, Logical>,
    ) -> Option<InputSurface> {
        self.surface_at_internal(output_scale, position, Point::default())
    }

    fn surface_at_internal(
        &self,
        output_scale: f64,
        mut position: Point<f64, Logical>,
        popup_offset: Point<f64, Logical>,
    ) -> Option<InputSurface> {
        let window_scale = self.window_scale(output_scale);
        let bounds = self.bounds(output_scale);
        let bounds_loc = bounds.loc.scale(output_scale / window_scale).to_f64() + popup_offset;

        let popup = self
            .popups
            .iter()
            .find_map(|p| p.surface_at_internal(output_scale, position, bounds_loc.to_f64()));
        if popup.is_some() {
            return popup;
        }

        position = position.upscale(output_scale / window_scale);

        let geometry = self.surface.geometry().unwrap_or_default();
        let origin_delta = self.texture_cache.origin_delta;

        let result = RefCell::new(None);
        compositor::with_surface_tree_downward(
            self.surface.surface(),
            bounds_loc,
            |_, surface_data, location| {
                if result.borrow().is_some() {
                    return TraversalAction::SkipChildren;
                }

                let mut location = *location;
                if surface_data.role == Some("subsurface") {
                    let mut cached = surface_data.cached_state.get::<SubsurfaceCachedState>();
                    location += cached.current().location.to_f64();
                }

                TraversalAction::DoChildren(location)
            },
            |wl_surface, surface_data, location| {
                if result.borrow().is_some() {
                    return;
                }

                let mut location = *location;
                if surface_data.role == Some("subsurface") {
                    let mut cached = surface_data.cached_state.get::<SubsurfaceCachedState>();
                    location += cached.current().location.to_f64();
                }

                let size = surface_data
                    .data_map
                    .get::<RefCell<ForgeSurfaceData>>()
                    .map_or_else(Size::default, |d| d.borrow().dst_size);
                let surface_loc = location - geometry.loc.to_f64() - origin_delta.to_f64();
                let surface_rect = Rectangle::new(surface_loc, size.to_f64());

                let mut attrs = surface_data.cached_state.get::<SurfaceAttributes>();
                let input_region = attrs.current().input_region.as_ref();
                let input_position = (position - surface_rect.loc).to_i32_round();

                if surface_rect.contains(position)
                    && input_region.is_none_or(|r| r.contains(input_position))
                {
                    let surface = InputSurface::new(wl_surface.clone(), surface_loc, window_scale);
                    *result.borrow_mut() = Some(surface);
                }
            },
            |_, _, _| result.borrow().is_none(),
        );
        result.into_inner()
    }

    pub fn owns_surface(&self, surface: &WlSurface) -> bool {
        let mut owns = false;
        self.with_surfaces(|ws, _| owns |= ws == surface);
        owns
    }

    pub fn add_popup(
        &mut self,
        mut popup: Window<PopupSurface>,
        parent: &WlSurface,
    ) -> Option<Window<PopupSurface>> {
        if self.surface.surface() == parent {
            popup.app_id.clone_from(&self.app_id);
            popup.visible = self.visible;
            popup.scale = self.scale;

            self.popups.push(popup);
            return None;
        }

        for window in &mut self.popups {
            popup = window.add_popup(popup, parent)?;
        }

        Some(popup)
    }

    pub fn popup_surface_commit(
        &mut self,
        output_scale: f64,
        output_size: Size<i32, Logical>,
        parent_abs_loc: Point<i32, Logical>,
        root_surface: &WlSurface,
        surface: &WlSurface,
    ) -> bool {
        let window_scale = self.window_scale(output_scale);
        let bounds = Rectangle::new(
            Point::from((-parent_abs_loc.x, -parent_abs_loc.y)),
            output_size,
        );

        self.popups.iter_mut().any(|popup| {
            if popup.surface.surface() == root_surface {
                let popup_loc = popup.constrained_location(bounds);
                popup.output_rectangle.loc = popup_loc.scale(window_scale / output_scale);

                popup.surface.with_pending_state(|state| {
                    state.geometry.loc = popup.output_rectangle.loc;
                });

                popup.surface_commit_common(output_scale, bounds.size, &[], surface);

                return true;
            }

            let child_parent_loc = parent_abs_loc + popup.output_rectangle.loc;
            popup.popup_surface_commit(
                output_scale,
                output_size,
                child_parent_loc,
                root_surface,
                surface,
            )
        })
    }

    pub fn refresh_popups(&mut self) {
        for i in (0..self.popups.len()).rev() {
            self.popups[i].refresh_popups();

            if !self.popups[i].alive() {
                self.popups.swap_remove(i);
            }
        }
    }

    pub fn reposition_popup(
        &mut self,
        output_scale: f64,
        output_size: Size<i32, Logical>,
        parent_abs_loc: Point<i32, Logical>,
        surface: &PopupSurface,
        token: u32,
    ) {
        let window_scale = self.window_scale(output_scale);
        let bounds = Rectangle::new(
            Point::from((-parent_abs_loc.x, -parent_abs_loc.y)),
            output_size,
        );

        for popup in &mut self.popups {
            if &popup.surface != surface {
                let child_parent_loc = parent_abs_loc + popup.output_rectangle.loc;
                popup.reposition_popup(output_scale, output_size, child_parent_loc, surface, token);
                continue;
            }

            let popup_loc = popup.constrained_location(bounds);
            popup.output_rectangle.loc = popup_loc.scale(window_scale / output_scale);

            popup.surface.with_pending_state(|state| {
                state.geometry.loc = popup.output_rectangle.loc;
            });

            surface.send_repositioned(token);
        }
    }

}

impl Window<PopupSurface> {
    pub fn parent(&self) -> Option<WlSurface> {
        compositor::with_states(self.surface.surface(), |states| {
            let attrs = states
                .data_map
                .get::<Mutex<XdgPopupSurfaceRoleAttributes>>()?;
            attrs.lock().ok()?.parent.clone()
        })
    }

    fn constrained_location(&self, bounds: Rectangle<i32, Logical>) -> Point<i32, Logical> {
        self.surface
            .with_pending_state(|state| state.positioner.get_unconstrained_geometry(bounds).loc)
    }
}

