//! The `@page` rule.

use super::Location;
use crate::declaration::{parse_declaration, DeclarationBlock};
use crate::error::{ParserError, PrinterError};
use crate::macros::enum_property;
use crate::printer::Printer;
use crate::stylesheet::ParserOptions;
use crate::traits::{Parse, ToCss};
use crate::values::string::CowArcStr;
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;

/// A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector)
/// within a `@page` rule.
///
/// Either a name or at least one pseudo class is required.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct PageSelector<'i> {
  /// An optional named page type.
  #[cfg_attr(feature = "serde", serde(borrow))]
  pub name: Option<CowArcStr<'i>>,
  /// A list of page pseudo classes.
  pub pseudo_classes: Vec<PagePseudoClass>,
}

enum_property! {
  /// A page pseudo class within an `@page` selector.
  ///
  /// See [PageSelector](PageSelector).
  pub enum PagePseudoClass {
    /// The `:left` pseudo class.
    Left,
    /// The `:right` pseudo class.
    Right,
    /// The `:first` pseudo class.
    First,
    /// The `:last` pseudo class.
    Last,
    /// The `:blank` pseudo class.
    Blank,
  }
}

impl<'i> Parse<'i> for PageSelector<'i> {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let name = input.try_parse(|input| input.expect_ident().map(|x| x.into())).ok();
    let mut pseudo_classes = vec![];

    loop {
      // Whitespace is not allowed between pseudo classes
      let state = input.state();
      match input.next_including_whitespace() {
        Ok(Token::Colon) => {
          pseudo_classes.push(PagePseudoClass::parse(input)?);
        }
        _ => {
          input.reset(&state);
          break;
        }
      }
    }

    if name.is_none() && pseudo_classes.is_empty() {
      return Err(input.new_custom_error(ParserError::InvalidPageSelector));
    }

    Ok(PageSelector { name, pseudo_classes })
  }
}

enum_property! {
  /// A [page margin box](https://www.w3.org/TR/css-page-3/#margin-boxes).
  pub enum PageMarginBox {
    /// A fixed-size box defined by the intersection of the top and left margins of the page box.
    TopLeftCorner,
    /// A variable-width box filling the top page margin between the top-left-corner and top-center page-margin boxes.
    TopLeft,
    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the
    /// page top margin between the top-left and top-right page-margin boxes.
    TopCenter,
    /// A variable-width box filling the top page margin between the top-center and top-right-corner page-margin boxes.
    TopRight,
    /// A fixed-size box defined by the intersection of the top and right margins of the page box.
    TopRightCorner,
    /// A variable-height box filling the left page margin between the top-left-corner and left-middle page-margin boxes.
    LeftTop,
    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the
    /// left page margin between the left-top and left-bottom page-margin boxes.
    LeftMiddle,
    /// A variable-height box filling the left page margin between the left-middle and bottom-left-corner page-margin boxes.
    LeftBottom,
    /// A variable-height box filling the right page margin between the top-right-corner and right-middle page-margin boxes.
    RightTop,
    /// A variable-height box centered vertically between the page’s top and bottom border edges and filling the right
    /// page margin between the right-top and right-bottom page-margin boxes.
    RightMiddle,
    /// A variable-height box filling the right page margin between the right-middle and bottom-right-corner page-margin boxes.
    RightBottom,
    /// A fixed-size box defined by the intersection of the bottom and left margins of the page box.
    BottomLeftCorner,
    /// A variable-width box filling the bottom page margin between the bottom-left-corner and bottom-center page-margin boxes.
    BottomLeft,
    /// A variable-width box centered horizontally between the page’s left and right border edges and filling the bottom
    /// page margin between the bottom-left and bottom-right page-margin boxes.
    BottomCenter,
    /// A variable-width box filling the bottom page margin between the bottom-center and bottom-right-corner page-margin boxes.
    BottomRight,
    /// A fixed-size box defined by the intersection of the bottom and right margins of the page box.
    BottomRightCorner,
  }
}

/// A [page margin rule](https://www.w3.org/TR/css-page-3/#margin-at-rules) rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "camelCase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct PageMarginRule<'i> {
  /// The margin box identifier for this rule.
  pub margin_box: PageMarginBox,
  /// The declarations within the rule.
  #[cfg_attr(feature = "serde", serde(borrow))]
  pub declarations: DeclarationBlock<'i>,
  /// The location of the rule in the source file.
  #[cfg_attr(feature = "visitor", skip_visit)]
  pub loc: Location,
}

impl<'i> ToCss for PageMarginRule<'i> {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    #[cfg(feature = "sourcemap")]
    dest.add_mapping(self.loc);
    dest.write_char('@')?;
    self.margin_box.to_css(dest)?;
    self.declarations.to_css_block(dest)
  }
}

/// A [@page](https://www.w3.org/TR/css-page-3/#at-page-rule) rule.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub struct PageRule<'i> {
  /// A list of page selectors.
  #[cfg_attr(feature = "serde", serde(borrow))]
  #[cfg_attr(feature = "visitor", skip_visit)]
  pub selectors: Vec<PageSelector<'i>>,
  /// The declarations within the `@page` rule.
  pub declarations: DeclarationBlock<'i>,
  /// The nested margin rules.
  pub rules: Vec<PageMarginRule<'i>>,
  /// The location of the rule in the source file.
  #[cfg_attr(feature = "visitor", skip_visit)]
  pub loc: Location,
}

impl<'i> PageRule<'i> {
  pub(crate) fn parse<'t, 'o>(
    selectors: Vec<PageSelector<'i>>,
    input: &mut Parser<'i, 't>,
    loc: Location,
    options: &ParserOptions<'i>,
  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let mut declarations = DeclarationBlock::new();
    let mut rules = Vec::new();
    let mut rule_parser = PageRuleParser {
      declarations: &mut declarations,
      rules: &mut rules,
      options: &options,
    };
    let mut parser = RuleBodyParser::new(input, &mut rule_parser);

    while let Some(decl) = parser.next() {
      if let Err((err, _)) = decl {
        if parser.parser.options.error_recovery {
          parser.parser.options.warn(err);
          continue;
        }
        return Err(err);
      }
    }

    Ok(PageRule {
      selectors,
      declarations,
      rules,
      loc,
    })
  }
}

impl<'i> ToCss for PageRule<'i> {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    #[cfg(feature = "sourcemap")]
    dest.add_mapping(self.loc);
    dest.write_str("@page")?;
    if let Some(first) = self.selectors.first() {
      // Space is only required if the first selector has a name.
      if !dest.minify || first.name.is_some() {
        dest.write_char(' ')?;
      }
      let mut first = true;
      for selector in &self.selectors {
        if first {
          first = false;
        } else {
          dest.delim(',', false)?;
        }
        selector.to_css(dest)?;
      }
    }

    dest.whitespace()?;
    dest.write_char('{')?;
    dest.indent();

    let mut i = 0;
    let len = self.declarations.len() + self.rules.len();

    macro_rules! write {
      ($decls: expr, $important: literal) => {
        for decl in &$decls {
          dest.newline()?;
          decl.to_css(dest, $important)?;
          if i != len - 1 || !dest.minify {
            dest.write_char(';')?;
          }
          i += 1;
        }
      };
    }

    write!(self.declarations.declarations, false);
    write!(self.declarations.important_declarations, true);

    if !self.rules.is_empty() {
      if !dest.minify && self.declarations.len() > 0 {
        dest.write_char('\n')?;
      }
      dest.newline()?;

      let mut first = true;
      for rule in &self.rules {
        if first {
          first = false;
        } else {
          if !dest.minify {
            dest.write_char('\n')?;
          }
          dest.newline()?;
        }
        rule.to_css(dest)?;
      }
    }

    dest.dedent();
    dest.newline()?;
    dest.write_char('}')
  }
}

impl<'i> ToCss for PageSelector<'i> {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    if let Some(name) = &self.name {
      dest.write_str(&name)?;
    }

    for pseudo in &self.pseudo_classes {
      dest.write_char(':')?;
      pseudo.to_css(dest)?;
    }

    Ok(())
  }
}

struct PageRuleParser<'a, 'i> {
  declarations: &'a mut DeclarationBlock<'i>,
  rules: &'a mut Vec<PageMarginRule<'i>>,
  options: &'a ParserOptions<'i>,
}

impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PageRuleParser<'a, 'i> {
  type Declaration = ();
  type Error = ParserError<'i>;

  fn parse_value<'t>(
    &mut self,
    name: CowRcStr<'i>,
    input: &mut cssparser::Parser<'i, 't>,
    _declaration_start: &ParserState,
  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
    parse_declaration(
      name,
      input,
      &mut self.declarations.declarations,
      &mut self.declarations.important_declarations,
      &self.options,
    )
  }
}

impl<'a, 'i> AtRuleParser<'i> for PageRuleParser<'a, 'i> {
  type Prelude = PageMarginBox;
  type AtRule = ();
  type Error = ParserError<'i>;

  fn parse_prelude<'t>(
    &mut self,
    name: CowRcStr<'i>,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::Prelude, ParseError<'i, Self::Error>> {
    let loc = input.current_source_location();
    PageMarginBox::parse_string(&name)
      .map_err(|_| loc.new_custom_error(ParserError::AtRuleInvalid(name.clone().into())))
  }

  fn parse_block<'t>(
    &mut self,
    prelude: Self::Prelude,
    start: &ParserState,
    input: &mut Parser<'i, 't>,
  ) -> Result<Self::AtRule, ParseError<'i, Self::Error>> {
    let loc = start.source_location();
    let declarations = DeclarationBlock::parse(input, self.options)?;
    self.rules.push(PageMarginRule {
      margin_box: prelude,
      declarations,
      loc: Location {
        source_index: self.options.source_index,
        line: loc.line,
        column: loc.column,
      },
    });
    Ok(())
  }
}

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

impl<'a, 'i> RuleBodyItemParser<'i, (), ParserError<'i>> for PageRuleParser<'a, 'i> {
  fn parse_qualified(&self) -> bool {
    false
  }

  fn parse_declarations(&self) -> bool {
    true
  }
}
