//! Per-`wl_surface` render state: committed buffer, texture, damage and the
//! viewport/scale bookkeeping.

use super::*;

pub struct ForgeSurfaceData {
    pub opaque_region: Vec<(RectangleKind, Rectangle<i32, Logical>)>,
    pub preferred_fractional_scale: f64,
    pub texture: Option<RenderTexture>,
    pub buffer_size: Size<i32, Logical>,
    pub src_rect: Rectangle<f64, Logical>,
    pub dst_size: Size<i32, Logical>,
    pub inhibits_shortcuts: bool,
    pub buffer: Option<Buffer>,
    pub transform: Transform,
    pub damage: Damage,
    pub scale: i32,
    pub location: Point<i32, Logical>,
}

impl Default for ForgeSurfaceData {
    fn default() -> Self {
        Self {
            preferred_fractional_scale: 1.0,
            scale: 1,
            inhibits_shortcuts: false,
            opaque_region: Vec::new(),
            buffer_size: Size::default(),
            transform: Transform::default(),
            src_rect: Rectangle::default(),
            dst_size: Size::default(),
            location: Point::default(),
            texture: None,
            buffer: None,
            damage: Damage::default(),
        }
    }
}

impl ForgeSurfaceData {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn update_buffer(
        &mut self,
        surface_data: &SurfaceData,
        attributes: &mut SurfaceAttributes,
        assignment: BufferAssignment,
    ) {
        match assignment {
            BufferAssignment::NewBuffer(buffer) => {
                let old_size = self.buffer_size;

                self.scale = attributes.buffer_scale;
                self.transform = attributes.buffer_transform.into();
                self.buffer_size = renderer::buffer_dimensions(&buffer)
                    .unwrap_or_default()
                    .to_logical(self.scale, self.transform);
                self.buffer = Some(Buffer::with_implicit(buffer));
                self.texture = None;

                let viewport_valid =
                    viewporter::ensure_viewport_valid(surface_data, self.buffer_size);
                let (viewport_src, viewport_dst) = viewport_valid
                    .then(|| {
                        let mut state = surface_data.cached_state.get::<ViewportCachedState>();
                        let current = state.current();
                        (current.src, current.size())
                    })
                    .unwrap_or_default();

                self.src_rect =
                    viewport_src.unwrap_or_else(|| Rectangle::from_size(self.buffer_size.to_f64()));
                self.dst_size = viewport_dst.unwrap_or(self.buffer_size);

                if old_size != self.buffer_size {
                    self.damage.tracker.reset();
                }
            }
            BufferAssignment::Removed => *self = Self::default(),
        }

        let viewporter_scale = Scale::from((
            self.dst_size.w as f64 / self.src_rect.size.w,
            self.dst_size.h as f64 / self.src_rect.size.h,
        ));

        let buffer_clip = self
            .buffer
            .as_ref()
            .and_then(|b| renderer::buffer_dimensions(b))
            .map(Rectangle::from_size)
            .unwrap_or_default();

        let buffer_damage = attributes.damage.drain(..).filter_map(|damage| {
            let rect = match damage {
                SurfaceDamage::Buffer(b) => b,
                SurfaceDamage::Surface(mut logical) => {
                    logical.loc += self.src_rect.loc.to_i32_round();
                    logical
                        .to_buffer(self.scale, self.transform, &self.buffer_size)
                        .to_f64()
                        .downscale(viewporter_scale)
                        .to_i32_round()
                }
            };

            let rect = rect.intersection(buffer_clip)?;
            self.damage.buffer.push(rect);
            Some(rect)
        });

        self.damage.tracker.add(buffer_damage);
    }
}

#[derive(Default)]
pub struct Damage {
    buffer: Vec<Rectangle<i32, BufferSpace>>,
    pub(super) tracker: DamageBag<i32, BufferSpace>,
}

impl Damage {
    pub fn buffer(&self) -> &[Rectangle<i32, BufferSpace>] {
        &self.buffer
    }

    pub fn clear(&mut self) {
        self.buffer.clear();
    }
}
