// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

//! Alpha and luminance masks.

use crate::pixmap::Pixmap;
use std::sync::Arc;
use std::vec::Vec;

/// A mask.
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Mask {
    data: Arc<Vec<u8>>,
    width: u16,
    height: u16,
}

impl Mask {
    /// Create a new alpha mask from the pixmap.
    pub(crate) fn new_alpha(pixmap: &Pixmap) -> Self {
        Self::new_with(pixmap, true)
    }

    /// Create a new luminance mask from the pixmap.
    pub(crate) fn new_luminance(pixmap: &Pixmap) -> Self {
        Self::new_with(pixmap, false)
    }

    fn new_with(pixmap: &Pixmap, alpha_mask: bool) -> Self {
        let mut data = Vec::with_capacity(pixmap.width() as usize * pixmap.height() as usize);

        for pixel in pixmap.data() {
            if alpha_mask {
                data.push(pixel.a);
            } else {
                let r = f32::from(pixel.r) / 255.;
                let g = f32::from(pixel.g) / 255.;
                let b = f32::from(pixel.b) / 255.;

                // See CSS Masking Module Level 1 § 7.10.1
                // <https://www.w3.org/TR/css-masking-1/#MaskValues>
                // and Filter Effects Module Level 1 § 9.6
                // <https://www.w3.org/TR/filter-effects-1/#elementdef-fecolormatrix>.
                // Note r, g and b are premultiplied by alpha.
                let luma = r * 0.2126 + g * 0.7152 + b * 0.0722;
                #[expect(clippy::cast_possible_truncation, reason = "This cannot overflow")]
                data.push((luma * 255.0 + 0.5) as u8);
            }
        }

        Self {
            data: Arc::new(data),
            width: pixmap.width(),
            height: pixmap.height(),
        }
    }

    /// Return the width of the mask.
    pub(crate) fn width(&self) -> u16 {
        self.width
    }

    /// Return the height of the mask.
    pub(crate) fn height(&self) -> u16 {
        self.height
    }

    /// Sample the value at a specific location.
    ///
    /// This function might panic or yield a wrong result if the location
    /// is out-of-bounds.
    pub(crate) fn sample(&self, x: u16, y: u16) -> u8 {
        if x >= self.width || y >= self.height {
            255
        } else {
            self.data[y as usize * self.width as usize + x as usize]
        }
    }
}
