/* 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-palette-values`][font-palette-values] at-rule.
//!
//! [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values

use crate::error_reporting::ContextualParseError;
#[cfg(feature = "gecko")]
use crate::gecko_bindings::{
    bindings::Gecko_AppendPaletteValueHashEntry,
    bindings::{Gecko_SetFontPaletteBase, Gecko_SetFontPaletteOverride},
    structs::gfx::FontPaletteValueSet,
    structs::gfx::FontPaletteValueSet_PaletteValues_kDark,
    structs::gfx::FontPaletteValueSet_PaletteValues_kLight,
};
use crate::parser::{Parse, ParserContext};
use crate::shared_lock::{SharedRwLockReadGuard, ToCssWithGuard};
use crate::stylesheets::font_feature_values_rule::parse_family_name_list;
use crate::values::computed::font::FamilyName;
use crate::values::specified::Color as SpecifiedColor;
use crate::values::specified::NonNegativeInteger;
use crate::values::DashedIdent;
use cssparser::{
    AtRuleParser, CowRcStr, DeclarationParser, Parser, ParserState, QualifiedRuleParser,
    RuleBodyItemParser, RuleBodyParser, SourceLocation,
};
use selectors::parser::SelectorParseErrorKind;
use std::fmt::{self, Write};
use style_traits::{Comma, OneOrMoreSeparated};
use style_traits::{CssStringWriter, CssWriter, ParseError, StyleParseErrorKind, ToCss};

#[allow(missing_docs)]
#[derive(Clone, Debug, MallocSizeOf, PartialEq, ToShmem)]
pub struct FontPaletteOverrideColor {
    index: NonNegativeInteger,
    color: SpecifiedColor,
}

impl Parse for FontPaletteOverrideColor {
    fn parse<'i, 't>(
        context: &ParserContext,
        input: &mut Parser<'i, 't>,
    ) -> Result<FontPaletteOverrideColor, ParseError<'i>> {
        let index = NonNegativeInteger::parse(context, input)?;
        let location = input.current_source_location();
        let color = SpecifiedColor::parse(context, input)?;
        // Only absolute colors are accepted here:
        //   https://drafts.csswg.org/css-fonts/#override-color
        //   https://drafts.csswg.org/css-color-5/#absolute-color
        // so check that the specified color can be resolved without a context
        // or currentColor value.
        if color.resolve_to_absolute().is_some() {
            // We store the specified color (not the resolved absolute color)
            // because that is what the rule exposes to authors.
            return Ok(FontPaletteOverrideColor { index, color });
        }
        Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError))
    }
}

impl ToCss for FontPaletteOverrideColor {
    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: fmt::Write,
    {
        self.index.to_css(dest)?;
        dest.write_char(' ')?;
        self.color.to_css(dest)
    }
}

impl OneOrMoreSeparated for FontPaletteOverrideColor {
    type S = Comma;
}

impl OneOrMoreSeparated for FamilyName {
    type S = Comma;
}

#[allow(missing_docs)]
#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, ToCss, ToShmem)]
pub enum FontPaletteBase {
    Light,
    Dark,
    Index(NonNegativeInteger),
}

/// The [`@font-palette-values`][font-palette-values] at-rule.
///
/// [font-palette-values]: https://drafts.csswg.org/css-fonts/#font-palette-values
#[derive(Clone, Debug, PartialEq, ToShmem)]
pub struct FontPaletteValuesRule {
    /// Palette name.
    pub name: DashedIdent,
    /// Font family list for @font-palette-values rule.
    /// Family names cannot contain generic families. FamilyName
    /// also accepts only non-generic names.
    pub family_names: Vec<FamilyName>,
    /// The base palette.
    pub base_palette: Option<FontPaletteBase>,
    /// The list of override colors.
    pub override_colors: Vec<FontPaletteOverrideColor>,
    /// The line and column of the rule's source code.
    pub source_location: SourceLocation,
}

impl FontPaletteValuesRule {
    /// Creates an empty FontPaletteValuesRule with given location and name.
    fn new(name: DashedIdent, location: SourceLocation) -> Self {
        FontPaletteValuesRule {
            name,
            family_names: vec![],
            base_palette: None,
            override_colors: vec![],
            source_location: location,
        }
    }

    /// Parses a `FontPaletteValuesRule`.
    pub fn parse(
        context: &ParserContext,
        input: &mut Parser,
        name: DashedIdent,
        location: SourceLocation,
    ) -> Self {
        let mut rule = FontPaletteValuesRule::new(name, location);
        let mut parser = FontPaletteValuesDeclarationParser {
            context,
            rule: &mut rule,
        };
        let mut iter = RuleBodyParser::new(input, &mut parser);
        while let Some(declaration) = iter.next() {
            if let Err((error, slice)) = declaration {
                let location = error.location;
                let error =
                    ContextualParseError::UnsupportedFontPaletteValuesDescriptor(slice, error);
                context.log_css_error(location, error);
            }
        }
        rule
    }

    /// Prints inside of `@font-palette-values` block.
    fn value_to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    where
        W: Write,
    {
        if !self.family_names.is_empty() {
            dest.write_str("font-family: ")?;
            self.family_names.to_css(dest)?;
            dest.write_str("; ")?;
        }
        if let Some(base) = &self.base_palette {
            dest.write_str("base-palette: ")?;
            base.to_css(dest)?;
            dest.write_str("; ")?;
        }
        if !self.override_colors.is_empty() {
            dest.write_str("override-colors: ")?;
            self.override_colors.to_css(dest)?;
            dest.write_str("; ")?;
        }
        Ok(())
    }

    /// Convert to Gecko FontPaletteValueSet.
    #[cfg(feature = "gecko")]
    pub fn to_gecko_palette_value_set(&self, dest: *mut FontPaletteValueSet) {
        for ref family in self.family_names.iter() {
            let family = family.name.to_ascii_lowercase();
            let palette_values = unsafe {
                Gecko_AppendPaletteValueHashEntry(dest, family.as_ptr(), self.name.0.as_ptr())
            };
            if let Some(base_palette) = &self.base_palette {
                unsafe {
                    Gecko_SetFontPaletteBase(
                        palette_values,
                        match &base_palette {
                            FontPaletteBase::Light => FontPaletteValueSet_PaletteValues_kLight,
                            FontPaletteBase::Dark => FontPaletteValueSet_PaletteValues_kDark,
                            FontPaletteBase::Index(i) => i.0.value() as i32,
                        },
                    );
                }
            }
            for c in &self.override_colors {
                // We checked at parse time that the specified color can be resolved
                // in this way, so the unwrap() here will succeed.
                let absolute = c.color.resolve_to_absolute().unwrap();
                unsafe {
                    Gecko_SetFontPaletteOverride(
                        palette_values,
                        c.index.0.value(),
                        (&absolute) as *const _ as *mut _,
                    );
                }
            }
        }
    }
}

impl ToCssWithGuard for FontPaletteValuesRule {
    fn to_css(&self, _guard: &SharedRwLockReadGuard, dest: &mut CssStringWriter) -> fmt::Result {
        dest.write_str("@font-palette-values ")?;
        self.name.to_css(&mut CssWriter::new(dest))?;
        dest.write_str(" { ")?;
        self.value_to_css(&mut CssWriter::new(dest))?;
        dest.write_char('}')
    }
}

/// Parser for declarations in `FontPaletteValuesRule`.
struct FontPaletteValuesDeclarationParser<'a> {
    context: &'a ParserContext<'a>,
    rule: &'a mut FontPaletteValuesRule,
}

impl<'a, 'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
    type Prelude = ();
    type AtRule = ();
    type Error = StyleParseErrorKind<'i>;
}

impl<'a, 'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser<'a> {
    type Prelude = ();
    type QualifiedRule = ();
    type Error = StyleParseErrorKind<'i>;
}

fn parse_override_colors<'i, 't>(
    context: &ParserContext,
    input: &mut Parser<'i, 't>,
) -> Result<Vec<FontPaletteOverrideColor>, ParseError<'i>> {
    input.parse_comma_separated(|i| FontPaletteOverrideColor::parse(context, i))
}

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

    fn parse_value<'t>(
        &mut self,
        name: CowRcStr<'i>,
        input: &mut Parser<'i, 't>,
        _declaration_start: &ParserState,
    ) -> Result<(), ParseError<'i>> {
        match_ignore_ascii_case! { &*name,
            "font-family" => {
                self.rule.family_names = parse_family_name_list(self.context, input)?
            },
            "base-palette" => {
                self.rule.base_palette = Some(input.parse_entirely(|i| FontPaletteBase::parse(self.context, i))?)
            },
            "override-colors" => {
                self.rule.override_colors = parse_override_colors(self.context, input)?
            },
            _ => return Err(input.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
        }
        Ok(())
    }
}

impl<'a, 'i> RuleBodyItemParser<'i, (), StyleParseErrorKind<'i>>
    for FontPaletteValuesDeclarationParser<'a>
{
    fn parse_declarations(&self) -> bool {
        true
    }
    fn parse_qualified(&self) -> bool {
        false
    }
}
