/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Generic types for CSS Motion Path.

use crate::values::animated::ToAnimatedZero;
use crate::values::generics::position::{GenericPosition, GenericPositionOrAuto};
use crate::values::specified::motion::CoordBox;
use serde::Deserializer;
use std::fmt::{self, Write};
use style_traits::{CssWriter, ToCss};

/// The <size> in ray() function.
///
/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-size
#[allow(missing_docs)]
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Copy,
    Debug,
    Deserialize,
    MallocSizeOf,
    Parse,
    PartialEq,
    Serialize,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[repr(u8)]
pub enum RaySize {
    ClosestSide,
    ClosestCorner,
    FarthestSide,
    FarthestCorner,
    Sides,
}

/// The `ray()` function, `ray( [ <angle> && <size> && contain? && [at <position>]? ] )`
///
/// https://drafts.fxtf.org/motion-1/#valdef-offsetpath-ray
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Debug,
    Deserialize,
    MallocSizeOf,
    PartialEq,
    Serialize,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToComputedValue,
    ToResolvedValue,
    ToShmem,
)]
#[repr(C)]
pub struct GenericRayFunction<Angle, Position> {
    /// The bearing angle with `0deg` pointing up and positive angles
    /// representing clockwise rotation.
    pub angle: Angle,
    /// Decide the path length used when `offset-distance` is expressed
    /// as a percentage.
    pub size: RaySize,
    /// Clamp `offset-distance` so that the box is entirely contained
    /// within the path.
    #[animation(constant)]
    pub contain: bool,
    /// The "at <position>" part. If omitted, we use auto to represent it.
    pub position: GenericPositionOrAuto<Position>,
}

pub use self::GenericRayFunction as RayFunction;

impl<Angle, Position> ToCss for RayFunction<Angle, Position>
where
    Angle: ToCss,
    Position: ToCss,
{
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        self.angle.to_css(dest)?;

        if !matches!(self.size, RaySize::ClosestSide) {
            dest.write_char(' ')?;
            self.size.to_css(dest)?;
        }

        if self.contain {
            dest.write_str(" contain")?;
        }

        if !matches!(self.position, GenericPositionOrAuto::Auto) {
            dest.write_str(" at ")?;
            self.position.to_css(dest)?;
        }

        Ok(())
    }
}

/// Return error if we try to deserialize the url, for Gecko IPC purposes.
// Note: we cannot use #[serde(skip_deserializing)] variant attribute, which may cause the fatal
// error when trying to read the parameters because it cannot deserialize the input byte buffer,
// even if the type of OffsetPathFunction is not an url(), in our tests. This may be an issue of
// #[serde(skip_deserializing)] on enum, at least in the version (1.0) we are using. So we have to
// manually implement this deseriailzing function, but return error.
// FIXME: Bug 1847620, fiure out this is a serde issue or a gecko bug.
fn deserialize_url<'de, D, T>(_deserializer: D) -> Result<T, D::Error>
where
    D: Deserializer<'de>,
{
    use crate::serde::de::Error;
    // Return Err() so the IPC will catch it and assert this as a fetal error.
    Err(<D as Deserializer>::Error::custom(
        "we don't support the deserializing for url",
    ))
}

/// The <offset-path> value.
/// <offset-path> = <ray()> | <url> | <basic-shape>
///
/// https://drafts.fxtf.org/motion-1/#typedef-offset-path
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Debug,
    Deserialize,
    MallocSizeOf,
    PartialEq,
    Serialize,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
)]
#[animation(no_bound(U))]
#[repr(C, u8)]
pub enum GenericOffsetPathFunction<Shapes, RayFunction, U> {
    /// ray() function, which defines a path in the polar coordinate system.
    /// Use Box<> to make sure the size of offset-path is not too large.
    #[css(function)]
    Ray(RayFunction),
    /// A URL reference to an SVG shape element. If the URL does not reference a shape element,
    /// this behaves as path("m 0 0") instead.
    #[animation(error)]
    #[serde(deserialize_with = "deserialize_url")]
    #[serde(skip_serializing)]
    Url(U),
    /// The <basic-shape> value.
    Shape(Shapes),
}

pub use self::GenericOffsetPathFunction as OffsetPathFunction;

/// The offset-path property.
/// offset-path: none | <offset-path> || <coord-box>
///
/// https://drafts.fxtf.org/motion-1/#offset-path-property
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Debug,
    Deserialize,
    MallocSizeOf,
    PartialEq,
    Serialize,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
    ToTyped,
)]
#[repr(C, u8)]
pub enum GenericOffsetPath<Function> {
    /// <offset-path> || <coord-box>.
    OffsetPath {
        /// <offset-path> part.
        // Note: Use Box<> to make sure the size of this property doesn't go over the threshold.
        path: Box<Function>,
        /// <coord-box> part.
        #[css(skip_if = "CoordBox::is_default")]
        coord_box: CoordBox,
    },
    /// Only <coord-box>. This represents that <offset-path> is omitted, so we use the default
    /// value, inset(0 round X), where X is the value of border-radius on the element that
    /// establishes the containing block for this element.
    CoordBox(CoordBox),
    /// None value.
    #[animation(error)]
    None,
}

pub use self::GenericOffsetPath as OffsetPath;

impl<Function> OffsetPath<Function> {
    /// Return None.
    #[inline]
    pub fn none() -> Self {
        OffsetPath::None
    }
}

impl<Function> ToAnimatedZero for OffsetPath<Function> {
    #[inline]
    fn to_animated_zero(&self) -> Result<Self, ()> {
        Err(())
    }
}

/// The offset-position property, which specifies the offset starting position that is used by the
/// <offset-path> functions if they don’t specify their own starting position.
///
/// https://drafts.fxtf.org/motion-1/#offset-position-property
#[derive(
    Animate,
    Clone,
    ComputeSquaredDistance,
    Copy,
    Debug,
    Deserialize,
    MallocSizeOf,
    Parse,
    PartialEq,
    Serialize,
    SpecifiedValueInfo,
    ToAnimatedValue,
    ToAnimatedZero,
    ToComputedValue,
    ToCss,
    ToResolvedValue,
    ToShmem,
    ToTyped,
)]
#[repr(C, u8)]
pub enum GenericOffsetPosition<H, V> {
    /// The element does not have an offset starting position.
    Normal,
    /// The offset starting position is the top-left corner of the box.
    Auto,
    /// The offset starting position is the result of using the <position> to position a 0x0 object
    /// area within the box’s containing block.
    Position(
        #[css(field_bound)]
        #[parse(field_bound)]
        GenericPosition<H, V>,
    ),
}

pub use self::GenericOffsetPosition as OffsetPosition;

impl<H, V> OffsetPosition<H, V> {
    /// Returns the initial value, normal.
    #[inline]
    pub fn normal() -> Self {
        Self::Normal
    }
}
