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

use bytemuck::{Pod, Zeroable};
use peniko::{
    BlendMode,
    color::{AlphaColor, ColorSpace, DynamicColor, OpaqueColor, PremulColor, Srgb},
};

use super::Monoid;

/// Draw tag representation.
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable)]
#[repr(C)]
pub struct DrawTag(pub u32);

impl DrawTag {
    /// No operation.
    pub const NOP: Self = Self(0);

    /// Color fill.
    pub const COLOR: Self = Self(0x44);

    /// Linear gradient fill.
    pub const LINEAR_GRADIENT: Self = Self(0x114);

    /// Radial gradient fill.
    pub const RADIAL_GRADIENT: Self = Self(0x29c);

    /// Sweep gradient fill.
    pub const SWEEP_GRADIENT: Self = Self(0x254);

    /// Image fill.
    pub const IMAGE: Self = Self(0x28C); // info: 10, scene: 3

    /// Blurred rounded rectangle.
    pub const BLUR_RECT: Self = Self(0x2d4); // info: 11, scene: 5 (DrawBlurRoundedRect)

    /// Begin layer/clip.
    pub const BEGIN_CLIP: Self = Self(0x49);

    /// End layer/clip.
    pub const END_CLIP: Self = Self(0x21);
}

impl DrawTag {
    /// Returns the size of the info buffer (in u32s) used by this tag.
    pub const fn info_size(self) -> u32 {
        (self.0 >> 6) & 0xf
    }
}

/// The first word of each draw info stream entry contains the flags.
///
/// This is not part of the draw object stream but gets used after the draw
/// objects get reduced on the GPU. `0` represents a non-zero fill.
/// `1` represents an even-odd fill.
pub const DRAW_INFO_FLAGS_FILL_RULE_BIT: u32 = 1;

/// Draw object bounding box.
#[derive(Copy, Clone, Pod, Zeroable, Debug, Default)]
#[repr(C)]
pub struct DrawBbox {
    pub bbox: [f32; 4],
}

/// Draw data for a solid color.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawColor {
    /// Packed little-endian RGBA premultiplied color with the red component in the low byte, i.e.,
    /// with `r` the least significant byte and `a` the most significant.
    pub rgba: u32,
}

impl<CS: ColorSpace> From<AlphaColor<CS>> for DrawColor {
    fn from(color: AlphaColor<CS>) -> Self {
        Self {
            rgba: color.convert::<Srgb>().premultiply().to_rgba8().to_u32(),
        }
    }
}

impl From<DynamicColor> for DrawColor {
    fn from(color: DynamicColor) -> Self {
        Self {
            rgba: color
                .to_alpha_color::<Srgb>()
                .premultiply()
                .to_rgba8()
                .to_u32(),
        }
    }
}

impl<CS: ColorSpace> From<OpaqueColor<CS>> for DrawColor {
    fn from(color: OpaqueColor<CS>) -> Self {
        Self {
            rgba: color
                .convert::<Srgb>()
                .with_alpha(1.)
                .premultiply()
                .to_rgba8()
                .to_u32(),
        }
    }
}

impl<CS: ColorSpace> From<PremulColor<CS>> for DrawColor {
    fn from(color: PremulColor<CS>) -> Self {
        Self {
            rgba: color.convert::<Srgb>().to_rgba8().to_u32(),
        }
    }
}

/// Draw data for a linear gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawLinearGradient {
    /// Ramp index.
    pub index: u32,
    /// Start point.
    pub p0: [f32; 2],
    /// End point.
    pub p1: [f32; 2],
}

/// Draw data for a radial gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawRadialGradient {
    /// Ramp index.
    pub index: u32,
    /// Start point.
    pub p0: [f32; 2],
    /// End point.
    pub p1: [f32; 2],
    /// Start radius.
    pub r0: f32,
    /// End radius.
    pub r1: f32,
}

/// Draw data for a sweep gradient.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawSweepGradient {
    /// Ramp index.
    pub index: u32,
    /// Center point.
    pub p0: [f32; 2],
    /// Normalized start angle.
    pub t0: f32,
    /// Normalized end angle.
    pub t1: f32,
}

/// Draw data for an image.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawImage {
    /// Packed atlas coordinates.
    pub xy: u32,
    /// Packed image dimensions.
    pub width_height: u32,
    /// Packed quality, extend mode and 8-bit alpha (bits `qqxxyyaaaaaaaa`,
    /// 18 unused prefix bits).
    pub sample_alpha: u32,
}

/// Draw data for a blurred rounded rectangle.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawBlurRoundedRect {
    /// Solid color brush.
    pub color: DrawColor,
    /// Rectangle width.
    pub width: f32,
    /// Rectangle height.
    pub height: f32,
    /// Rectangle corner radius.
    pub radius: f32,
    /// Standard deviation of gaussian filter.
    pub std_dev: f32,
}

/// Draw data for a clip or layer.
#[derive(Clone, Copy, Debug, Default, Zeroable, Pod)]
#[repr(C)]
pub struct DrawBeginClip {
    /// Blend mode.
    pub blend_mode: u32,
    /// Group alpha.
    pub alpha: f32,
}

impl DrawBeginClip {
    /// The `blend_mode` used to indicate that a layer should be
    /// treated as a luminance mask.
    ///
    /// The least significant 16 bits are reserved for Mix + Compose
    /// combinations.
    pub const LUMINANCE_MASK_BLEND_MODE: u32 = 0x10000;
    /// The `blend_mode` used to indicate that a layer should be
    /// treated as a clip.
    ///
    /// This is equivalent to `Compose::SrcOver` with a `Mix` of 128,
    /// for legacy reasons.
    /// We expect this to change in the future.
    pub const CLIP_BLEND_MODE: u32 = 0x8003;

    /// Creates new clip draw data for a Porter-Duff blend mode.
    pub fn new(blend_mode: BlendMode, alpha: f32) -> Self {
        Self {
            blend_mode: ((blend_mode.mix as u32) << 8) | blend_mode.compose as u32,
            alpha,
        }
    }

    /// Creates a new clip draw data for a luminance mask.
    pub fn luminance_mask(alpha: f32) -> Self {
        Self {
            blend_mode: Self::LUMINANCE_MASK_BLEND_MODE,
            alpha,
        }
    }

    /// Creates the clip draw data for a clip-only layer.
    pub fn clip() -> Self {
        Self {
            blend_mode: Self::CLIP_BLEND_MODE,
            alpha: 1.0,
        }
    }
}

/// Monoid for the draw tag stream.
#[derive(Copy, Clone, PartialEq, Eq, Pod, Zeroable, Default, Debug)]
#[repr(C)]
pub struct DrawMonoid {
    // The number of paths preceding this draw object.
    pub path_ix: u32,
    // The number of clip operations preceding this draw object.
    pub clip_ix: u32,
    // The offset of the encoded draw object in the scene (u32s).
    pub scene_offset: u32,
    // The offset of the associated info.
    pub info_offset: u32,
}

impl Monoid for DrawMonoid {
    type SourceValue = DrawTag;

    fn new(tag: DrawTag) -> Self {
        Self {
            path_ix: (tag != DrawTag::NOP) as u32,
            clip_ix: tag.0 & 1,
            scene_offset: (tag.0 >> 2) & 0x7,
            info_offset: (tag.0 >> 6) & 0xf,
        }
    }

    fn combine(&self, other: &Self) -> Self {
        Self {
            path_ix: self.path_ix + other.path_ix,
            clip_ix: self.clip_ix + other.clip_ix,
            scene_offset: self.scene_offset + other.scene_offset,
            info_offset: self.info_offset + other.info_offset,
        }
    }
}

#[cfg(test)]
mod tests {
    use peniko::Color;

    use super::DrawColor;

    #[test]
    fn draw_color_endianness() {
        // `DrawColor` should be packed little-endian with red the least significant byte.
        //
        // If this changes intentionally, the `DrawColor` docs also need updating.
        let c = Color::from_rgba8(0x00, 0xca, 0xfe, 0xff);
        assert_eq!(
            bytemuck::bytes_of(&DrawColor::from(c)),
            [0x00, 0xca, 0xfe, 0xff]
        );
    }

    #[test]
    fn draw_color_premultiplied() {
        // If this changes intentionally, the `DrawColor` docs also need updating.
        let c = Color::from_rgba8(0x00, 0xca, 0xfe, 0x00);
        assert_eq!(DrawColor::from(c).rgba, 0);
    }
}
