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

//! A [`@scope`][scope] rule.
//!
//! [scope]: https://drafts.csswg.org/css-cascade-6/#scoped-styles

use crate::applicable_declarations::ScopeProximity;
use crate::dom::TElement;
use crate::parser::ParserContext;
use crate::selector_parser::{SelectorImpl, SelectorParser};
use crate::shared_lock::{
    DeepCloneWithLock, Locked, SharedRwLock, SharedRwLockReadGuard, ToCssWithGuard,
};
use crate::simple_buckets_map::SimpleBucketsMap;
use crate::stylesheets::CssRules;
use cssparser::{Parser, SourceLocation, ToCss};
#[cfg(feature = "gecko")]
use malloc_size_of::{
    MallocSizeOfOps, MallocUnconditionalShallowSizeOf, MallocUnconditionalSizeOf,
};
use selectors::context::{MatchingContext, QuirksMode};
use selectors::matching::matches_selector;
use selectors::parser::{Component, ParseRelative, Selector, SelectorList};
use selectors::OpaqueElement;
use servo_arc::Arc;
use std::fmt::{self, Write};
use style_traits::{CssStringWriter, CssWriter, ParseError};

/// A scoped rule.
#[derive(Debug, ToShmem)]
pub struct ScopeRule {
    /// Bounds at which this rule applies.
    pub bounds: ScopeBounds,
    /// The nested rules inside the block.
    pub rules: Arc<Locked<CssRules>>,
    /// The source position where this rule was found.
    pub source_location: SourceLocation,
}

impl DeepCloneWithLock for ScopeRule {
    fn deep_clone_with_lock(&self, lock: &SharedRwLock, guard: &SharedRwLockReadGuard) -> Self {
        let rules = self.rules.read_with(guard);
        Self {
            bounds: self.bounds.clone(),
            rules: Arc::new(lock.wrap(rules.deep_clone_with_lock(lock, guard))),
            source_location: self.source_location.clone(),
        }
    }
}

impl ToCssWithGuard for ScopeRule {
    fn to_css(&self, guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
        dest.write_str("@scope")?;
        {
            let mut writer = CssWriter::new(dest);
            if let Some(start) = self.bounds.start.as_ref() {
                writer.write_str(" (")?;
                start.to_css(&mut writer)?;
                writer.write_char(')')?;
            }
            if let Some(end) = self.bounds.end.as_ref() {
                writer.write_str(" to (")?;
                end.to_css(&mut writer)?;
                writer.write_char(')')?;
            }
        }
        self.rules.read_with(guard).to_css_block(guard, dest)
    }
}

impl ScopeRule {
    /// Measure heap usage.
    #[cfg(feature = "gecko")]
    pub fn size_of(&self, guard: &SharedRwLockReadGuard, ops: &mut MallocSizeOfOps) -> usize {
        self.rules.unconditional_shallow_size_of(ops)
            + self.rules.read_with(guard).size_of(guard, ops)
            + self.bounds.size_of(ops)
    }
}

/// Bounds of the scope.
#[derive(Debug, Clone, ToShmem)]
pub struct ScopeBounds {
    /// Start of the scope.
    pub start: Option<SelectorList<SelectorImpl>>,
    /// End of the scope.
    pub end: Option<SelectorList<SelectorImpl>>,
}

impl ScopeBounds {
    #[cfg(feature = "gecko")]
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        fn bound_size_of(
            bound: &Option<SelectorList<SelectorImpl>>,
            ops: &mut MallocSizeOfOps,
        ) -> usize {
            bound
                .as_ref()
                .map(|list| list.unconditional_size_of(ops))
                .unwrap_or(0)
        }
        bound_size_of(&self.start, ops) + bound_size_of(&self.end, ops)
    }
}

fn parse_scope<'a>(
    context: &ParserContext,
    input: &mut Parser<'a, '_>,
    parse_relative: ParseRelative,
    for_end: bool,
) -> Result<Option<SelectorList<SelectorImpl>>, ParseError<'a>> {
    input.try_parse(|input| {
        if for_end {
            // scope-end not existing is valid.
            if input.try_parse(|i| i.expect_ident_matching("to")).is_err() {
                return Ok(None);
            }
        }
        let parens = input.try_parse(|i| i.expect_parenthesis_block());
        if for_end {
            // `@scope to {}` is NOT valid.
            parens?;
        } else if parens.is_err() {
            // `@scope {}` is valid.
            return Ok(None);
        }
        input.parse_nested_block(|input| {
            let selector_parser = SelectorParser {
                stylesheet_origin: context.stylesheet_origin,
                namespaces: &context.namespaces,
                url_data: context.url_data,
                for_supports_rule: false,
            };
            let parse_relative = if for_end {
                ParseRelative::ForScope
            } else {
                parse_relative
            };
            Ok(Some(SelectorList::parse_disallow_pseudo(
                &selector_parser,
                input,
                parse_relative,
            )?))
        })
    })
}

impl ScopeBounds {
    /// Parse a container condition.
    pub fn parse<'a>(
        context: &ParserContext,
        input: &mut Parser<'a, '_>,
        parse_relative: ParseRelative,
    ) -> Result<Self, ParseError<'a>> {
        let start = parse_scope(context, input, parse_relative, false)?;
        let end = parse_scope(context, input, parse_relative, true)?;
        Ok(Self { start, end })
    }
}

/// Types of implicit scope root.
#[derive(Debug, Copy, Clone, MallocSizeOf)]
pub enum ImplicitScopeRoot {
    /// This implicit scope root is in the light tree.
    InLightTree(OpaqueElement),
    /// This implicit scope root is the document element, regardless of which (light|shadow) tree
    /// the element being matched is. This is the case for e.g. if you specified an implicit scope
    /// within a user stylesheet.
    DocumentElement,
    /// The implicit scope root is in a constructed stylesheet - the scope root the element
    /// under consideration's shadow root (If one exists).
    Constructed,
    /// This implicit scope root is in the shadow tree.
    InShadowTree(OpaqueElement),
    /// This implicit scope root is the shadow host of the stylesheet-containing shadow tree.
    ShadowHost(OpaqueElement),
}

impl ImplicitScopeRoot {
    /// Return true if this matches the shadow host.
    pub fn matches_shadow_host(&self) -> bool {
        match self {
            Self::InLightTree(..) | Self::InShadowTree(..) | Self::DocumentElement => false,
            Self::ShadowHost(..) | Self::Constructed => true,
        }
    }

    /// Return the implicit scope root element.
    pub fn element(&self, current_host: Option<OpaqueElement>) -> ImplicitScopeTarget {
        match self {
            Self::InLightTree(e) | Self::InShadowTree(e) | Self::ShadowHost(e) => {
                ImplicitScopeTarget::Element(*e)
            },
            Self::Constructed | Self::DocumentElement => {
                if matches!(self, Self::Constructed) {
                    if let Some(host) = current_host {
                        return ImplicitScopeTarget::Element(host);
                    }
                }
                ImplicitScopeTarget::DocumentElement
            },
        }
    }
}

/// Target of this implicit scope.
pub enum ImplicitScopeTarget {
    /// Target matches only the specified element.
    Element(OpaqueElement),
    /// Implicit scope whose target is the document element.
    DocumentElement,
}

impl ImplicitScopeTarget {
    /// Check if this element is the implicit scope.
    fn check<E: TElement>(&self, element: E) -> bool {
        match self {
            Self::Element(e) => element.opaque() == *e,
            Self::DocumentElement => element.is_root(),
        }
    }
}

/// Target of this scope.
pub enum ScopeTarget<'a> {
    /// Target matches an element matching the specified selector list.
    Selector(&'a SelectorList<SelectorImpl>),
    /// Target matches an implicit scope target.
    Implicit(ImplicitScopeTarget),
}

impl<'a> ScopeTarget<'a> {
    /// Check if the given element is the scope.
    fn check<E: TElement>(
        &self,
        element: E,
        scope: Option<OpaqueElement>,
        scope_subject_map: &ScopeSubjectMap,
        context: &mut MatchingContext<E::Impl>,
    ) -> bool {
        match self {
            Self::Selector(list) => context.nest_for_scope_condition(scope, |context| {
                if scope_subject_map.early_reject(element, context.quirks_mode()) {
                    return false;
                }
                for selector in list.slice().iter() {
                    if matches_selector(selector, 0, None, &element, context) {
                        return true;
                    }
                }
                false
            }),
            Self::Implicit(t) => t.check(element),
        }
    }
}

/// A scope root candidate.
#[derive(Clone, Copy, Debug)]
pub struct ScopeRootCandidate {
    /// This candidate's scope root.
    pub root: OpaqueElement,
    /// Ancestor hop from the element under consideration to this scope root.
    pub proximity: ScopeProximity,
}

impl ScopeRootCandidate {
    /// Get the element corresponding to this scope root candidate.
    pub fn get_scope_root_element<E>(&self, originating_element: E) -> Option<E>
    where
        E: TElement,
    {
        // Could just unsafe-convert from opaque element - technically
        // faster as well, but it doesn't seem worth having to manually
        // assure safety every time.
        let mut e = originating_element;
        let hops = self.proximity.get()?;
        for _ in 0..hops {
            e = e.parent_element()?;
        }
        debug_assert_eq!(e.opaque(), self.root);
        Some(e)
    }
}

/// Collect potential scope roots for a given element and its scope target.
/// The check may not pass the ceiling, if specified.
pub fn collect_scope_roots<E>(
    element: E,
    ceiling: Option<OpaqueElement>,
    context: &mut MatchingContext<E::Impl>,
    target: &ScopeTarget,
    matches_shadow_host: bool,
    scope_subject_map: &ScopeSubjectMap,
) -> Vec<ScopeRootCandidate>
where
    E: TElement,
{
    let mut result = vec![];
    let mut parent = Some(element);
    let mut proximity = 0usize;
    while let Some(p) = parent {
        if target.check(p, ceiling, scope_subject_map, context) {
            result.push(ScopeRootCandidate {
                root: p.opaque(),
                proximity: ScopeProximity::new(proximity),
            });
            // Note that we can't really break here - we need to consider
            // ALL scope roots to figure out whch one didn't end.
        }
        if ceiling == Some(p.opaque()) {
            break;
        }
        parent = p.parent_element();
        proximity += 1;
        // We we got to the top of the shadow tree - keep going
        // if we may match the shadow host.
        if parent.is_none() && matches_shadow_host {
            parent = p.containing_shadow_host();
        }
    }
    result
}

/// Given the scope-end selector, check if the element is outside of the scope.
/// That is, check if any ancestor to the root matches the scope-end selector.
pub fn element_is_outside_of_scope<E>(
    selector: &Selector<E::Impl>,
    element: E,
    root: OpaqueElement,
    context: &mut MatchingContext<E::Impl>,
    root_may_be_shadow_host: bool,
) -> bool
where
    E: TElement,
{
    let mut parent = Some(element);
    context.nest_for_scope_condition(Some(root), |context| {
        while let Some(p) = parent {
            if matches_selector(selector, 0, None, &p, context) {
                return true;
            }
            if p.opaque() == root {
                // Reached the top, not lying outside of scope.
                break;
            }
            parent = p.parent_element();
            if parent.is_none() && root_may_be_shadow_host {
                if let Some(host) = p.containing_shadow_host() {
                    // Pretty much an edge case where user specified scope-start and -end of :host
                    return host.opaque() == root;
                }
            }
        }
        return false;
    })
}

/// A map containing simple selectors in subjects of scope selectors.
/// This allows fast-rejecting scopes before running the full match.
#[derive(Clone, Debug, Default, MallocSizeOf)]
pub struct ScopeSubjectMap {
    buckets: SimpleBucketsMap<()>,
    any: bool,
}

impl ScopeSubjectMap {
    /// Add the `<scope-start>` of a scope.
    pub fn add_bound_start(
        &mut self,
        selectors: &SelectorList<SelectorImpl>,
        quirks_mode: QuirksMode,
    ) {
        if self.add_selector_list(selectors, quirks_mode) {
            self.any = true;
        }
    }

    fn add_selector_list(
        &mut self,
        selectors: &SelectorList<SelectorImpl>,
        quirks_mode: QuirksMode,
    ) -> bool {
        let mut is_any = false;
        for selector in selectors.slice().iter() {
            is_any = is_any || self.add_selector(selector, quirks_mode);
        }
        is_any
    }

    fn add_selector(&mut self, selector: &Selector<SelectorImpl>, quirks_mode: QuirksMode) -> bool {
        let mut is_any = true;
        let mut iter = selector.iter();
        while let Some(c) = iter.next() {
            let component_any = match c {
                Component::Class(cls) => {
                    match self.buckets.classes.try_entry(cls.0.clone(), quirks_mode) {
                        Ok(e) => {
                            e.or_insert(());
                            false
                        },
                        Err(_) => true,
                    }
                },
                Component::ID(id) => match self.buckets.ids.try_entry(id.0.clone(), quirks_mode) {
                    Ok(e) => {
                        e.or_insert(());
                        false
                    },
                    Err(_) => true,
                },
                Component::LocalName(local_name) => {
                    self.buckets
                        .local_names
                        .insert(local_name.lower_name.clone(), ());
                    false
                },
                Component::Is(ref list) | Component::Where(ref list) => {
                    self.add_selector_list(list, quirks_mode)
                },
                _ => true,
            };

            is_any = is_any && component_any;
        }
        is_any
    }

    /// Shrink the map as much as possible.
    pub fn shrink_if_needed(&mut self) {
        self.buckets.shrink_if_needed();
    }

    /// Clear the map.
    pub fn clear(&mut self) {
        self.buckets.clear();
        self.any = false;
    }

    /// Could a given element possibly be a scope root?
    fn early_reject<E: TElement>(&self, element: E, quirks_mode: QuirksMode) -> bool {
        if self.any {
            return false;
        }

        if let Some(id) = element.id() {
            if self.buckets.ids.get(id, quirks_mode).is_some() {
                return false;
            }
        }

        let mut found = false;
        element.each_class(|cls| {
            if self.buckets.classes.get(cls, quirks_mode).is_some() {
                found = true;
            }
        });
        if found {
            return false;
        }

        if self.buckets.local_names.get(element.local_name()).is_some() {
            return false;
        }

        true
    }
}

/// Determine if this selector list, when used as a scope bound selector, is considered trivial.
pub fn scope_selector_list_is_trivial(list: &SelectorList<SelectorImpl>) -> bool {
    fn scope_selector_is_trivial(selector: &Selector<SelectorImpl>) -> bool {
        // A selector is trivial if:
        // * There is no selector conditional on its siblings and/or descendant to match, and
        // * There is no dependency on sibling relations, and
        // * There's no ID selector in the selector. A more correct approach may be to ensure that
        //   scoping roots of the style sharing candidates and targets have matching IDs, but that
        //   requires re-plumbing what we pass around for scope roots.
        let mut iter = selector.iter();
        loop {
            while let Some(c) = iter.next() {
                match c {
                    Component::ID(_)
                    | Component::Nth(_)
                    | Component::NthOf(_)
                    | Component::Has(_) => return false,
                    Component::Is(ref list)
                    | Component::Where(ref list)
                    | Component::Negation(ref list) => {
                        if !scope_selector_list_is_trivial(list) {
                            return false;
                        }
                    },
                    _ => (),
                }
            }

            match iter.next_sequence() {
                Some(c) => {
                    if c.is_sibling() {
                        return false;
                    }
                },
                None => return true,
            }
        }
    }

    list.slice().iter().all(|s| scope_selector_is_trivial(s))
}
