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

//! The [`@font-face`][ff] at-rule.
//!
//! [ff]: https://drafts.csswg.org/css-fonts/#at-font-face-rule

use crate::error_reporting::ContextualParseError;
use crate::parser::{Parse, ParserContext};
#[cfg(feature = "gecko")]
use crate::properties::longhands::font_language_override;
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::str::CssStringWriter;
use crate::values::computed::font::FamilyName;
use crate::values::generics::font::FontStyle as GenericFontStyle;
#[cfg(feature = "gecko")]
use crate::values::specified::font::SpecifiedFontFeatureSettings;
use crate::values::specified::font::SpecifiedFontStyle;
#[cfg(feature = "gecko")]
use crate::values::specified::font::SpecifiedFontVariationSettings;
use crate::values::specified::font::{AbsoluteFontWeight, FontStretch};
use crate::values::specified::url::SpecifiedUrl;
use crate::values::specified::Angle;
#[cfg(feature = "gecko")]
use cssparser::UnicodeRange;
use cssparser::{AtRuleParser, DeclarationListParser, DeclarationParser, Parser};
use cssparser::{CowRcStr, SourceLocation};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use style_traits::values::SequenceWriter;
use style_traits::{Comma, CssWriter, OneOrMoreSeparated, ParseError};
use style_traits::{StyleParseErrorKind, ToCss};

/// A source for a font-face rule.
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToCss, ToShmem)]
pub enum Source {
    /// A `url()` source.
    Url(UrlSource),
    /// A `local()` source.
    #[css(function)]
    Local(FamilyName),
}

impl OneOrMoreSeparated for Source {
    type S = Comma;
}

/// A POD representation for Gecko. All pointers here are non-owned and as such
/// can't outlive the rule they came from, but we can't enforce that via C++.
///
/// All the strings are of course utf8.
#[cfg(feature = "gecko")]
#[repr(u8)]
#[allow(missing_docs)]
pub enum FontFaceSourceListComponent {
    Url(*const crate::gecko_bindings::structs::mozilla::css::URLValue),
    Local(*mut crate::gecko_bindings::structs::nsAtom),
    FormatHint {
        length: usize,
        utf8_bytes: *const u8,
    },
}

/// A `UrlSource` represents a font-face source that has been specified with a
/// `url()` function.
///
/// <https://drafts.csswg.org/css-fonts/#src-desc>
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(Clone, Debug, Eq, PartialEq, ToShmem)]
pub struct UrlSource {
    /// The specified url.
    pub url: SpecifiedUrl,
    /// The format hints specified with the `format()` function.
    pub format_hints: Vec<String>,
}

impl ToCss for UrlSource {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        self.url.to_css(dest)?;
        if !self.format_hints.is_empty() {
            dest.write_str(" format(")?;
            {
                let mut writer = SequenceWriter::new(dest, ", ");
                for hint in self.format_hints.iter() {
                    writer.item(hint)?;
                }
            }
            dest.write_char(')')?;
        }
        Ok(())
    }
}

/// A font-display value for a @font-face rule.
/// The font-display descriptor determines how a font face is displayed based
/// on whether and when it is downloaded and ready to use.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
#[derive(
    Clone, Copy, Debug, Eq, MallocSizeOf, Parse, PartialEq, ToComputedValue, ToCss, ToShmem,
)]
#[repr(u8)]
pub enum FontDisplay {
    Auto,
    Block,
    Swap,
    Fallback,
    Optional,
}

macro_rules! impl_range {
    ($range:ident, $component:ident) => {
        impl Parse for $range {
            fn parse<'i, 't>(
                context: &ParserContext,
                input: &mut Parser<'i, 't>,
            ) -> Result<Self, ParseError<'i>> {
                let first = $component::parse(context, input)?;
                let second = input
                    .try(|input| $component::parse(context, input))
                    .unwrap_or_else(|_| first.clone());
                Ok($range(first, second))
            }
        }
        impl ToCss for $range {
            fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
            where
                W: fmt::Write,
            {
                self.0.to_css(dest)?;
                if self.0 != self.1 {
                    dest.write_str(" ")?;
                    self.1.to_css(dest)?;
                }
                Ok(())
            }
        }
    };
}

/// The font-weight descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-weight
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontWeightRange(pub AbsoluteFontWeight, pub AbsoluteFontWeight);
impl_range!(FontWeightRange, AbsoluteFontWeight);

/// The computed representation of the above so Gecko can read them easily.
///
/// This one is needed because cbindgen doesn't know how to generate
/// specified::Number.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontWeightRange(f32, f32);

#[inline]
fn sort_range<T: PartialOrd>(a: T, b: T) -> (T, T) {
    if a > b {
        (b, a)
    } else {
        (a, b)
    }
}

impl FontWeightRange {
    /// Returns a computed font-stretch range.
    pub fn compute(&self) -> ComputedFontWeightRange {
        let (min, max) = sort_range(self.0.compute().0, self.1.compute().0);
        ComputedFontWeightRange(min, max)
    }
}

/// The font-stretch descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-stretch
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontStretchRange(pub FontStretch, pub FontStretch);
impl_range!(FontStretchRange, FontStretch);

/// The computed representation of the above, so that
/// Gecko can read them easily.
#[repr(C)]
#[allow(missing_docs)]
pub struct ComputedFontStretchRange(f32, f32);

impl FontStretchRange {
    /// Returns a computed font-stretch range.
    pub fn compute(&self) -> ComputedFontStretchRange {
        fn compute_stretch(s: &FontStretch) -> f32 {
            match *s {
                FontStretch::Keyword(ref kw) => kw.compute().0,
                FontStretch::Stretch(ref p) => p.get(),
                FontStretch::System(..) => unreachable!(),
            }
        }

        let (min, max) = sort_range(compute_stretch(&self.0), compute_stretch(&self.1));
        ComputedFontStretchRange(min, max)
    }
}

/// The font-style descriptor:
///
/// https://drafts.csswg.org/css-fonts-4/#descdef-font-face-font-style
#[derive(Clone, Debug, PartialEq, ToShmem)]
#[allow(missing_docs)]
pub enum FontStyle {
    Normal,
    Italic,
    Oblique(Angle, Angle),
}

/// The computed representation of the above, with angles in degrees, so that
/// Gecko can read them easily.
#[repr(u8)]
#[allow(missing_docs)]
pub enum ComputedFontStyleDescriptor {
    Normal,
    Italic,
    Oblique(f32, f32),
}

impl Parse for FontStyle {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Self, ParseError<'i>> {
        let style = SpecifiedFontStyle::parse(context, input)?;
        Ok(match style {
            GenericFontStyle::Normal => FontStyle::Normal,
            GenericFontStyle::Italic => FontStyle::Italic,
            GenericFontStyle::Oblique(angle) => {
                let second_angle = input
                    .try(|input| SpecifiedFontStyle::parse_angle(context, input))
                    .unwrap_or_else(|_| angle.clone());

                FontStyle::Oblique(angle, second_angle)
            },
        })
    }
}

impl ToCss for FontStyle {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        match *self {
            FontStyle::Normal => dest.write_str("normal"),
            FontStyle::Italic => dest.write_str("italic"),
            FontStyle::Oblique(ref first, ref second) => {
                dest.write_str("oblique")?;
                if *first != SpecifiedFontStyle::default_angle() || first != second {
                    dest.write_char(' ')?;
                    first.to_css(dest)?;
                }
                if first != second {
                    dest.write_char(' ')?;
                    second.to_css(dest)?;
                }
                Ok(())
            },
        }
    }
}

impl FontStyle {
    /// Returns a computed font-style descriptor.
    pub fn compute(&self) -> ComputedFontStyleDescriptor {
        match *self {
            FontStyle::Normal => ComputedFontStyleDescriptor::Normal,
            FontStyle::Italic => ComputedFontStyleDescriptor::Italic,
            FontStyle::Oblique(ref first, ref second) => {
                let (min, max) = sort_range(
                    SpecifiedFontStyle::compute_angle_degrees(first),
                    SpecifiedFontStyle::compute_angle_degrees(second),
                );
                ComputedFontStyleDescriptor::Oblique(min, max)
            },
        }
    }
}

/// Parse the block inside a `@font-face` rule.
///
/// Note that the prelude parsing code lives in the `stylesheets` module.
pub fn parse_font_face_block(
    context: &ParserContext,
    input: &mut Parser,
    location: SourceLocation,
) -> FontFaceRuleData {
    let mut rule = FontFaceRuleData::empty(location);
    {
        let parser = FontFaceRuleParser {
            context: context,
            rule: &mut rule,
        };
        let mut iter = DeclarationListParser::new(input, parser);
        while let Some(declaration) = iter.next() {
            if let Err((error, slice)) = declaration {
                let location = error.location;
                let error = ContextualParseError::UnsupportedFontFaceDescriptor(slice, error);
                context.log_css_error(location, error)
            }
        }
    }
    rule
}

/// A @font-face rule that is known to have font-family and src declarations.
#[cfg(feature = "servo")]
pub struct FontFace<'a>(&'a FontFaceRuleData);

/// A list of effective sources that we send over through IPC to the font cache.
#[cfg(feature = "servo")]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
pub struct EffectiveSources(Vec<Source>);

#[cfg(feature = "servo")]
impl<'a> FontFace<'a> {
    /// Returns the list of effective sources for that font-face, that is the
    /// sources which don't list any format hint, or the ones which list at
    /// least "truetype" or "opentype".
    pub fn effective_sources(&self) -> EffectiveSources {
        EffectiveSources(
            self.sources()
                .iter()
                .rev()
                .filter(|source| {
                    if let Source::Url(ref url_source) = **source {
                        let hints = &url_source.format_hints;
                        // We support only opentype fonts and truetype is an alias for
                        // that format. Sources without format hints need to be
                        // downloaded in case we support them.
                        hints.is_empty() ||
                            hints.iter().any(|hint| {
                                hint == "truetype" || hint == "opentype" || hint == "woff"
                            })
                    } else {
                        true
                    }
                })
                .cloned()
                .collect(),
        )
    }
}

#[cfg(feature = "servo")]
impl Iterator for EffectiveSources {
    type Item = Source;
    fn next(&mut self) -> Option<Source> {
        self.0.pop()
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.0.len(), Some(self.0.len()))
    }
}

struct FontFaceRuleParser<'a, 'b: 'a> {
    context: &'a ParserContext<'b>,
    rule: &'a mut FontFaceRuleData,
}

/// Default methods reject all at rules.
impl<'a, 'b, 'i> AtRuleParser<'i> for FontFaceRuleParser<'a, 'b> {
    type PreludeNoBlock = ();
    type PreludeBlock = ();
    type AtRule = ();
    type Error = StyleParseErrorKind<'i>;
}

impl Parse for Source {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<Source, ParseError<'i>> {
        if input
            .try(|input| input.expect_function_matching("local"))
            .is_ok()
        {
            return input
                .parse_nested_block(|input| FamilyName::parse(context, input))
                .map(Source::Local);
        }

        let url = SpecifiedUrl::parse(context, input)?;

        // Parsing optional format()
        let format_hints = if input
            .try(|input| input.expect_function_matching("format"))
            .is_ok()
        {
            input.parse_nested_block(|input| {
                input.parse_comma_separated(|input| Ok(input.expect_string()?.as_ref().to_owned()))
            })?
        } else {
            vec![]
        };

        Ok(Source::Url(UrlSource {
            url: url,
            format_hints: format_hints,
        }))
    }
}

macro_rules! is_descriptor_enabled {
    ("font-display") => {
        unsafe {
            use crate::gecko_bindings::structs::mozilla;
            mozilla::StaticPrefs_sVarCache_layout_css_font_display_enabled
        }
    };
    ("font-variation-settings") => {
        unsafe {
            use crate::gecko_bindings::structs::mozilla;
            mozilla::StaticPrefs_sVarCache_layout_css_font_variations_enabled != 0
        }
    };
    ($name:tt) => {
        true
    };
}

macro_rules! font_face_descriptors_common {
    (
        $( #[$doc: meta] $name: tt $ident: ident / $gecko_ident: ident: $ty: ty, )*
    ) => {
        /// Data inside a `@font-face` rule.
        ///
        /// <https://drafts.csswg.org/css-fonts/#font-face-rule>
        #[derive(Clone, Debug, PartialEq, ToShmem)]
        pub struct FontFaceRuleData {
            $(
                #[$doc]
                pub $ident: Option<$ty>,
            )*
            /// Line and column of the @font-face rule source code.
            pub source_location: SourceLocation,
        }

        impl FontFaceRuleData {
            /// Create an empty font-face rule
            pub fn empty(location: SourceLocation) -> Self {
                FontFaceRuleData {
                    $(
                        $ident: None,
                    )*
                    source_location: location,
                }
            }

            /// Serialization of declarations in the FontFaceRule
            pub fn decl_to_css(&self, dest: &mut CssStringWriter) -> fmt::Result {
                $(
                    if let Some(ref value) = self.$ident {
                        dest.write_str(concat!("  ", $name, ": "))?;
                        ToCss::to_css(value, &mut CssWriter::new(dest))?;
                        dest.write_str(";\n")?;
                    }
                )*
                Ok(())
            }
        }

       impl<'a, 'b, 'i> DeclarationParser<'i> for FontFaceRuleParser<'a, 'b> {
           type Declaration = ();
           type Error = StyleParseErrorKind<'i>;

           fn parse_value<'t>(&mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>)
                              -> Result<(), ParseError<'i>> {
                match_ignore_ascii_case! { &*name,
                    $(
                        $name if is_descriptor_enabled!($name) => {
                            // DeclarationParser also calls parse_entirely
                            // so we’d normally not need to,
                            // but in this case we do because we set the value as a side effect
                            // rather than returning it.
                            let value = input.parse_entirely(|i| Parse::parse(self.context, i))?;
                            self.rule.$ident = Some(value)
                        }
                    )*
                    _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
                }
                Ok(())
            }
        }
    }
}

impl ToCssWithGuard for FontFaceRuleData {
    // Serialization of FontFaceRule is not specced.
    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
        dest.write_str("@font-face {\n")?;
        self.decl_to_css(dest)?;
        dest.write_str("}")
    }
}

macro_rules! font_face_descriptors {
    (
        mandatory descriptors = [
            $( #[$m_doc: meta] $m_name: tt $m_ident: ident / $m_gecko_ident: ident: $m_ty: ty, )*
        ]
        optional descriptors = [
            $( #[$o_doc: meta] $o_name: tt $o_ident: ident / $o_gecko_ident: ident: $o_ty: ty, )*
        ]
    ) => {
        font_face_descriptors_common! {
            $( #[$m_doc] $m_name $m_ident / $m_gecko_ident: $m_ty, )*
            $( #[$o_doc] $o_name $o_ident / $o_gecko_ident: $o_ty, )*
        }

        impl FontFaceRuleData {
            /// Per https://github.com/w3c/csswg-drafts/issues/1133 an @font-face rule
            /// is valid as far as the CSS parser is concerned even if it doesn’t have
            /// a font-family or src declaration.
            ///
            /// However both are required for the rule to represent an actual font face.
            #[cfg(feature = "servo")]
            pub fn font_face(&self) -> Option<FontFace> {
                if $( self.$m_ident.is_some() )&&* {
                    Some(FontFace(self))
                } else {
                    None
                }
            }
        }

        #[cfg(feature = "servo")]
        impl<'a> FontFace<'a> {
            $(
                #[$m_doc]
                pub fn $m_ident(&self) -> &$m_ty {
                    self.0 .$m_ident.as_ref().unwrap()
                }
            )*
        }
    }
}

#[cfg(feature = "gecko")]
font_face_descriptors! {
    mandatory descriptors = [
        /// The name of this font face
        "font-family" family / mFamily: FamilyName,

        /// The alternative sources for this font face.
        "src" sources / mSrc: Vec<Source>,
    ]
    optional descriptors = [
        /// The style of this font face.
        "font-style" style / mStyle: FontStyle,

        /// The weight of this font face.
        "font-weight" weight / mWeight: FontWeightRange,

        /// The stretch of this font face.
        "font-stretch" stretch / mStretch: FontStretchRange,

        /// The display of this font face.
        "font-display" display / mDisplay: FontDisplay,

        /// The ranges of code points outside of which this font face should not be used.
        "unicode-range" unicode_range / mUnicodeRange: Vec<UnicodeRange>,

        /// The feature settings of this font face.
        "font-feature-settings" feature_settings / mFontFeatureSettings: SpecifiedFontFeatureSettings,

        /// The variation settings of this font face.
        "font-variation-settings" variation_settings / mFontVariationSettings: SpecifiedFontVariationSettings,

        /// The language override of this font face.
        "font-language-override" language_override / mFontLanguageOverride: font_language_override::SpecifiedValue,
    ]
}

#[cfg(feature = "servo")]
font_face_descriptors! {
    mandatory descriptors = [
        /// The name of this font face
        "font-family" family / mFamily: FamilyName,

        /// The alternative sources for this font face.
        "src" sources / mSrc: Vec<Source>,
    ]
    optional descriptors = [
    ]
}
