/* 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/. */

#![forbid(unsafe_code)]

use crate::properties::Importance;
use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheets::Origin;

/// The cascade level these rules are relevant at, as per[1][2][3].
///
/// Presentational hints for SVG and HTML are in the "author-level
/// zero-specificity" level, that is, right after user rules, and before author
/// rules.
///
/// The order of variants declared here is significant, and must be in
/// _ascending_ order of precedence.
///
/// See also [4] for the Shadow DOM bits. We rely on the invariant that rules
/// from outside the tree the element is in can't affect the element.
///
/// The opposite is not true (i.e., :host and ::slotted) from an "inner" shadow
/// tree may affect an element connected to the document or an "outer" shadow
/// tree.
///
/// [1]: https://drafts.csswg.org/css-cascade/#cascade-origin
/// [2]: https://drafts.csswg.org/css-cascade/#preshint
/// [3]: https://html.spec.whatwg.org/multipage/#presentational-hints
/// [4]: https://drafts.csswg.org/css-scoping/#shadow-cascading
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd)]
pub enum CascadeLevel {
    /// Normal User-Agent rules.
    UANormal,
    /// User normal rules.
    UserNormal,
    /// Presentational hints.
    PresHints,
    /// Shadow DOM styles from author styles.
    AuthorNormal {
        /// The order in the shadow tree hierarchy. This number is relative to
        /// the tree of the element, and thus the only invariants that need to
        /// be preserved is:
        ///
        ///  * Zero is the same tree as the element that matched the rule. This
        ///    is important so that we can optimize style attribute insertions.
        ///
        ///  * The levels are ordered in accordance with
        ///    https://drafts.csswg.org/css-scoping/#shadow-cascading
        shadow_cascade_order: ShadowCascadeOrder,
    },
    /// SVG SMIL animations.
    SMILOverride,
    /// CSS animations and script-generated animations.
    Animations,
    /// Author-supplied important rules.
    AuthorImportant {
        /// The order in the shadow tree hierarchy, inverted, so that PartialOrd
        /// does the right thing.
        shadow_cascade_order: ShadowCascadeOrder,
    },
    /// User important rules.
    UserImportant,
    /// User-agent important rules.
    UAImportant,
    /// Transitions
    Transitions,
}

impl CascadeLevel {
    /// Pack this cascade level in a single byte.
    ///
    /// We have 10 levels, which we can represent with 4 bits, and then a
    /// cascade order optionally, which we can clamp to three bits max, and
    /// represent with a fourth bit for the sign.
    ///
    /// So this creates: SOOODDDD
    ///
    /// Where `S` is the sign of the order (one if negative, 0 otherwise), `O`
    /// is the absolute value of the order, and `D`s are the discriminant.
    #[inline]
    pub fn to_byte_lossy(&self) -> u8 {
        let (discriminant, order) = match *self {
            Self::UANormal => (0, 0),
            Self::UserNormal => (1, 0),
            Self::PresHints => (2, 0),
            Self::AuthorNormal {
                shadow_cascade_order,
            } => (3, shadow_cascade_order.0),
            Self::SMILOverride => (4, 0),
            Self::Animations => (5, 0),
            Self::AuthorImportant {
                shadow_cascade_order,
            } => (6, shadow_cascade_order.0),
            Self::UserImportant => (7, 0),
            Self::UAImportant => (8, 0),
            Self::Transitions => (9, 0),
        };

        debug_assert_eq!(discriminant & 0xf, discriminant);
        if order == 0 {
            return discriminant;
        }

        let negative = order < 0;
        let value = std::cmp::min(order.abs() as u8, 0b111);
        (negative as u8) << 7 | value << 4 | discriminant
    }

    /// Convert back from the single-byte representation of the cascade level
    /// explained above.
    #[inline]
    pub fn from_byte(b: u8) -> Self {
        let order = {
            let abs = ((b & 0b01110000) >> 4) as i8;
            let negative = b & 0b10000000 != 0;
            if negative {
                -abs
            } else {
                abs
            }
        };
        let discriminant = b & 0xf;
        let level = match discriminant {
            0 => Self::UANormal,
            1 => Self::UserNormal,
            2 => Self::PresHints,
            3 => {
                return Self::AuthorNormal {
                    shadow_cascade_order: ShadowCascadeOrder(order),
                }
            },
            4 => Self::SMILOverride,
            5 => Self::Animations,
            6 => {
                return Self::AuthorImportant {
                    shadow_cascade_order: ShadowCascadeOrder(order),
                }
            },
            7 => Self::UserImportant,
            8 => Self::UAImportant,
            9 => Self::Transitions,
            _ => unreachable!("Didn't expect {} as a discriminant", discriminant),
        };
        debug_assert_eq!(order, 0, "Didn't expect an order value for {:?}", level);
        level
    }

    /// Select a lock guard for this level
    pub fn guard<'a>(&self, guards: &'a StylesheetGuards<'a>) -> &'a SharedRwLockReadGuard<'a> {
        match *self {
            Self::UANormal | Self::UserNormal | Self::UserImportant | Self::UAImportant => {
                guards.ua_or_user
            },
            _ => guards.author,
        }
    }

    /// Returns the cascade level for author important declarations from the
    /// same tree as the element.
    #[inline]
    pub fn same_tree_author_important() -> Self {
        Self::AuthorImportant {
            shadow_cascade_order: ShadowCascadeOrder::for_same_tree(),
        }
    }

    /// Returns the cascade level for author normal declarations from the same
    /// tree as the element.
    #[inline]
    pub fn same_tree_author_normal() -> Self {
        Self::AuthorNormal {
            shadow_cascade_order: ShadowCascadeOrder::for_same_tree(),
        }
    }

    /// Returns whether this cascade level represents important rules of some
    /// sort.
    #[inline]
    pub fn is_important(&self) -> bool {
        match *self {
            Self::AuthorImportant { .. } | Self::UserImportant | Self::UAImportant => true,
            _ => false,
        }
    }

    /// Returns the importance relevant for this rule. Pretty similar to
    /// `is_important`.
    #[inline]
    pub fn importance(&self) -> Importance {
        if self.is_important() {
            Importance::Important
        } else {
            Importance::Normal
        }
    }

    /// Returns the cascade origin of the rule.
    #[inline]
    pub fn origin(&self) -> Origin {
        match *self {
            Self::UAImportant | Self::UANormal => Origin::UserAgent,
            Self::UserImportant | Self::UserNormal => Origin::User,
            Self::PresHints |
            Self::AuthorNormal { .. } |
            Self::AuthorImportant { .. } |
            Self::SMILOverride |
            Self::Animations |
            Self::Transitions => Origin::Author,
        }
    }

    /// Returns whether this cascade level represents an animation rules.
    #[inline]
    pub fn is_animation(&self) -> bool {
        match *self {
            Self::SMILOverride | Self::Animations | Self::Transitions => true,
            _ => false,
        }
    }
}

/// A counter to track how many shadow root rules deep we are. This is used to
/// handle:
///
/// https://drafts.csswg.org/css-scoping/#shadow-cascading
///
/// See the static functions for the meaning of different values.
#[derive(Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Ord, PartialEq, PartialOrd)]
pub struct ShadowCascadeOrder(i8);

impl ShadowCascadeOrder {
    /// A level for the outermost shadow tree (the shadow tree we own, and the
    /// ones from the slots we're slotted in).
    #[inline]
    pub fn for_outermost_shadow_tree() -> Self {
        Self(-1)
    }

    /// A level for the element's tree.
    #[inline]
    fn for_same_tree() -> Self {
        Self(0)
    }

    /// A level for the innermost containing tree (the one closest to the
    /// element).
    #[inline]
    pub fn for_innermost_containing_tree() -> Self {
        Self(1)
    }

    /// Decrement the level, moving inwards. We should only move inwards if
    /// we're traversing slots.
    #[inline]
    pub fn dec(&mut self) {
        debug_assert!(self.0 < 0);
        self.0 = self.0.saturating_sub(1);
    }

    /// The level, moving inwards. We should only move inwards if we're
    /// traversing slots.
    #[inline]
    pub fn inc(&mut self) {
        debug_assert_ne!(self.0, -1);
        self.0 = self.0.saturating_add(1);
    }
}

impl std::ops::Neg for ShadowCascadeOrder {
    type Output = Self;
    #[inline]
    fn neg(self) -> Self {
        Self(self.0.neg())
    }
}