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

//! Data needed to style a Gecko document.

use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs::{
    self, ServoStyleSetSizes, StyleSheet as DomStyleSheet, StyleSheetInfo,
};
use crate::invalidation::stylesheets::StylesheetInvalidationSet;
use crate::media_queries::{Device, MediaList};
use crate::properties::ComputedValues;
use crate::shared_lock::{SharedRwLockReadGuard, StylesheetGuards};
use crate::stylesheets::scope_rule::ImplicitScopeRoot;
use crate::stylesheets::{StylesheetContents, StylesheetInDocument};
use crate::stylist::Stylist;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use malloc_size_of::MallocSizeOfOps;
use selectors::Element;
use servo_arc::Arc;
use std::fmt;

use super::wrapper::GeckoElement;

/// Little wrapper to a Gecko style sheet.
#[derive(Eq, PartialEq)]
pub struct GeckoStyleSheet(*const DomStyleSheet);

// NOTE(emilio): These are kind of a lie. We allow to make these Send + Sync so that other data
// structures can also be Send and Sync, but Gecko's stylesheets are main-thread-reference-counted.
//
// We assert that we reference-count in the right thread (in the Addref/Release implementations).
// Sending these to a different thread can't really happen (it could theoretically really happen if
// we allowed @import rules inside a nested style rule, but that can't happen per spec and would be
// a parser bug, caught by the asserts).
unsafe impl Send for GeckoStyleSheet {}
unsafe impl Sync for GeckoStyleSheet {}

impl fmt::Debug for GeckoStyleSheet {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        let contents = self.raw_contents();
        formatter
            .debug_struct("GeckoStyleSheet")
            .field("origin", &contents.origin)
            .field("url_data", &contents.url_data)
            .finish()
    }
}

impl GeckoStyleSheet {
    /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer.
    #[inline]
    pub unsafe fn new(s: *const DomStyleSheet) -> Self {
        debug_assert!(!s.is_null());
        bindings::Gecko_StyleSheet_AddRef(s);
        Self::from_addrefed(s)
    }

    /// Create a `GeckoStyleSheet` from a raw `DomStyleSheet` pointer that
    /// already holds a strong reference.
    #[inline]
    pub unsafe fn from_addrefed(s: *const DomStyleSheet) -> Self {
        assert!(!s.is_null());
        GeckoStyleSheet(s)
    }

    /// HACK(emilio): This is so that we can avoid crashing release due to
    /// bug 1719963 and can hopefully get a useful report from fuzzers.
    #[inline]
    pub fn hack_is_null(&self) -> bool {
        self.0.is_null()
    }

    /// Get the raw `StyleSheet` that we're wrapping.
    pub fn raw(&self) -> &DomStyleSheet {
        unsafe { &*self.0 }
    }

    fn inner(&self) -> &StyleSheetInfo {
        unsafe { &*(self.raw().mInner as *const StyleSheetInfo) }
    }

    fn raw_contents(&self) -> &StylesheetContents {
        debug_assert!(!self.inner().mContents.mRawPtr.is_null());
        unsafe { &*self.inner().mContents.mRawPtr }
    }
}

impl Drop for GeckoStyleSheet {
    fn drop(&mut self) {
        unsafe { bindings::Gecko_StyleSheet_Release(self.0) };
    }
}

impl Clone for GeckoStyleSheet {
    fn clone(&self) -> Self {
        unsafe { bindings::Gecko_StyleSheet_AddRef(self.0) };
        GeckoStyleSheet(self.0)
    }
}

impl StylesheetInDocument for GeckoStyleSheet {
    fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
        use crate::gecko_bindings::structs::mozilla::dom::MediaList as DomMediaList;
        unsafe {
            let dom_media_list = self.raw().mMedia.mRawPtr as *const DomMediaList;
            if dom_media_list.is_null() {
                return None;
            }
            let list = &*(*dom_media_list).mRawList.mRawPtr;
            Some(list.read_with(guard))
        }
    }

    // All the stylesheets Servo knows about are enabled, because that state is
    // handled externally by Gecko.
    #[inline]
    fn enabled(&self) -> bool {
        true
    }

    #[inline]
    fn contents<'a>(&'a self, _: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
        self.raw_contents()
    }

    fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
        unsafe {
            let result = bindings::Gecko_StyleSheet_ImplicitScopeRoot(self.0);
            if result.mRoot.is_null() {
                return if result.mConstructed {
                    Some(ImplicitScopeRoot::Constructed)
                } else {
                    // Could be genuinely not attached, like user stylesheet.
                    None
                };
            }

            let root = GeckoElement(result.mRoot.as_ref().unwrap()).opaque();
            Some(if !result.mHost.is_null() {
                let host = GeckoElement(result.mHost.as_ref().unwrap()).opaque();
                if host == root {
                    ImplicitScopeRoot::ShadowHost(root)
                } else {
                    ImplicitScopeRoot::InShadowTree(root)
                }
            } else {
                ImplicitScopeRoot::InLightTree(root)
            })
        }
    }
}

/// The container for data that a Servo-backed Gecko document needs to style
/// itself.
pub struct PerDocumentStyleDataImpl {
    /// Rule processor.
    pub stylist: Stylist,

    /// A cache from element to resolved style.
    pub undisplayed_style_cache: crate::traversal::UndisplayedStyleCache,

    /// The generation for which our cache is valid.
    pub undisplayed_style_cache_generation: u64,
}

/// The data itself is an `AtomicRefCell`, which guarantees the proper semantics
/// and unexpected races while trying to mutate it.
#[derive(Deref)]
pub struct PerDocumentStyleData(AtomicRefCell<PerDocumentStyleDataImpl>);

impl PerDocumentStyleData {
    /// Create a `PerDocumentStyleData`.
    pub fn new(document: *const structs::Document) -> Self {
        let device = Device::new(document);
        let quirks_mode = device.document().mCompatMode;

        PerDocumentStyleData(AtomicRefCell::new(PerDocumentStyleDataImpl {
            stylist: Stylist::new(device, quirks_mode.into()),
            undisplayed_style_cache: Default::default(),
            undisplayed_style_cache_generation: 0,
        }))
    }

    /// Get an immutable reference to this style data.
    pub fn borrow(&self) -> AtomicRef<'_, PerDocumentStyleDataImpl> {
        self.0.borrow()
    }

    /// Get an mutable reference to this style data.
    pub fn borrow_mut(&self) -> AtomicRefMut<'_, PerDocumentStyleDataImpl> {
        self.0.borrow_mut()
    }
}

impl PerDocumentStyleDataImpl {
    /// Recreate the style data if the stylesheets have changed.
    pub fn flush_stylesheets(
        &mut self,
        guard: &SharedRwLockReadGuard,
    ) -> StylesheetInvalidationSet {
        self.stylist.flush(&StylesheetGuards::same(guard))
    }

    /// Get the default computed values for this document.
    pub fn default_computed_values(&self) -> &Arc<ComputedValues> {
        self.stylist.device().default_computed_values_arc()
    }

    /// Measure heap usage.
    pub fn add_size_of(&self, ops: &mut MallocSizeOfOps, sizes: &mut ServoStyleSetSizes) {
        self.stylist.add_size_of(ops, sizes);
    }
}

/// The gecko-specific AuthorStyles instantiation.
pub type AuthorStyles = crate::author_styles::AuthorStyles<GeckoStyleSheet>;
