//! CSS properties related to backgrounds.

use crate::context::PropertyHandlerContext;
use crate::declaration::{DeclarationBlock, DeclarationList};
use crate::error::{ParserError, PrinterError};
use crate::macros::*;
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::properties::{Property, PropertyId, VendorPrefix};
use crate::targets::{Browsers, Targets};
use crate::traits::{FallbackValues, IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
use crate::values::color::ColorFallbackKind;
use crate::values::image::ImageFallback;
use crate::values::{color::CssColor, image::Image, length::LengthPercentageOrAuto, position::*};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;
use itertools::izip;
use smallvec::SmallVec;

/// A value for the [background-size](https://www.w3.org/TR/css-backgrounds-3/#background-size) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum BackgroundSize {
  /// An explicit background size.
  Explicit {
    /// The width of the background.
    width: LengthPercentageOrAuto,
    /// The height of the background.
    height: LengthPercentageOrAuto,
  },
  /// The `cover` keyword. Scales the background image to cover both the width and height of the element.
  Cover,
  /// The `contain` keyword. Scales the background image so that it fits within the element.
  Contain,
}

impl Default for BackgroundSize {
  fn default() -> BackgroundSize {
    BackgroundSize::Explicit {
      width: LengthPercentageOrAuto::Auto,
      height: LengthPercentageOrAuto::Auto,
    }
  }
}

impl<'i> Parse<'i> for BackgroundSize {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    if let Ok(width) = input.try_parse(LengthPercentageOrAuto::parse) {
      let height = input
        .try_parse(LengthPercentageOrAuto::parse)
        .unwrap_or(LengthPercentageOrAuto::Auto);
      return Ok(BackgroundSize::Explicit { width, height });
    }

    let location = input.current_source_location();
    let ident = input.expect_ident()?;
    Ok(match_ignore_ascii_case! { ident,
      "cover" => BackgroundSize::Cover,
      "contain" => BackgroundSize::Contain,
      _ => return Err(location.new_unexpected_token_error(
        cssparser::Token::Ident(ident.clone())
      ))
    })
  }
}

impl ToCss for BackgroundSize {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    use BackgroundSize::*;

    match &self {
      Cover => dest.write_str("cover"),
      Contain => dest.write_str("contain"),
      Explicit { width, height } => {
        width.to_css(dest)?;
        if *height != LengthPercentageOrAuto::Auto {
          dest.write_str(" ")?;
          height.to_css(dest)?;
        }
        Ok(())
      }
    }
  }
}

impl IsCompatible for BackgroundSize {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      BackgroundSize::Explicit { width, height } => {
        width.is_compatible(browsers) && height.is_compatible(browsers)
      }
      BackgroundSize::Cover | BackgroundSize::Contain => true,
    }
  }
}

enum_property! {
  /// A [`<repeat-style>`](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value,
  /// used within the `background-repeat` property to represent how a background image is repeated
  /// in a single direction.
  ///
  /// See [BackgroundRepeat](BackgroundRepeat).
  pub enum BackgroundRepeatKeyword {
    /// The image is repeated in this direction.
    Repeat,
    /// The image is repeated so that it fits, and then spaced apart evenly.
    Space,
    /// The image is scaled so that it repeats an even number of times.
    Round,
    /// The image is placed once and not repeated in this direction.
    NoRepeat,
  }
}

/// A value for the [background-repeat](https://www.w3.org/TR/css-backgrounds-3/#background-repeat) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub struct BackgroundRepeat {
  /// A repeat style for the x direction.
  pub x: BackgroundRepeatKeyword,
  /// A repeat style for the y direction.
  pub y: BackgroundRepeatKeyword,
}

impl Default for BackgroundRepeat {
  fn default() -> BackgroundRepeat {
    BackgroundRepeat {
      x: BackgroundRepeatKeyword::Repeat,
      y: BackgroundRepeatKeyword::Repeat,
    }
  }
}

impl<'i> Parse<'i> for BackgroundRepeat {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    use BackgroundRepeatKeyword::*;
    let state = input.state();
    let ident = input.expect_ident()?;

    match_ignore_ascii_case! { ident,
      "repeat-x" => return Ok(BackgroundRepeat { x: Repeat, y: NoRepeat }),
      "repeat-y" => return Ok(BackgroundRepeat { x: NoRepeat, y: Repeat }),
      _ => {}
    }

    input.reset(&state);

    let x = BackgroundRepeatKeyword::parse(input)?;
    let y = input.try_parse(BackgroundRepeatKeyword::parse).unwrap_or(x.clone());
    Ok(BackgroundRepeat { x, y })
  }
}

impl ToCss for BackgroundRepeat {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    use BackgroundRepeatKeyword::*;
    match (&self.x, &self.y) {
      (Repeat, NoRepeat) => dest.write_str("repeat-x"),
      (NoRepeat, Repeat) => dest.write_str("repeat-y"),
      (x, y) => {
        x.to_css(dest)?;
        if y != x {
          dest.write_str(" ")?;
          y.to_css(dest)?;
        }
        Ok(())
      }
    }
  }
}

impl IsCompatible for BackgroundRepeat {
  fn is_compatible(&self, _browsers: Browsers) -> bool {
    true
  }
}

enum_property! {
  /// A value for the [background-attachment](https://www.w3.org/TR/css-backgrounds-3/#background-attachment) property.
  pub enum BackgroundAttachment {
    /// The background scrolls with the container.
    Scroll,
    /// The background is fixed to the viewport.
    Fixed,
    /// The background is fixed with regard to the element’s contents.
    Local,
  }
}

impl Default for BackgroundAttachment {
  fn default() -> BackgroundAttachment {
    BackgroundAttachment::Scroll
  }
}

enum_property! {
  /// A value for the [background-origin](https://www.w3.org/TR/css-backgrounds-3/#background-origin) property.
  pub enum BackgroundOrigin {
    /// The position is relative to the border box.
    BorderBox,
    /// The position is relative to the padding box.
    PaddingBox,
    /// The position is relative to the content box.
    ContentBox,
  }
}

enum_property! {
  /// A value for the [background-clip](https://drafts.csswg.org/css-backgrounds-4/#background-clip) property.
  pub enum BackgroundClip {
    /// The background is clipped to the border box.
    BorderBox,
    /// The background is clipped to the padding box.
    PaddingBox,
    /// The background is clipped to the content box.
    ContentBox,
    /// The background is clipped to the area painted by the border.
    Border,
    /// The background is clipped to the text content of the element.
    Text,
  }
}

impl PartialEq<BackgroundOrigin> for BackgroundClip {
  fn eq(&self, other: &BackgroundOrigin) -> bool {
    match (self, other) {
      (BackgroundClip::BorderBox, BackgroundOrigin::BorderBox)
      | (BackgroundClip::PaddingBox, BackgroundOrigin::PaddingBox)
      | (BackgroundClip::ContentBox, BackgroundOrigin::ContentBox) => true,
      _ => false,
    }
  }
}

impl Into<BackgroundClip> for BackgroundOrigin {
  fn into(self) -> BackgroundClip {
    match self {
      BackgroundOrigin::BorderBox => BackgroundClip::BorderBox,
      BackgroundOrigin::PaddingBox => BackgroundClip::PaddingBox,
      BackgroundOrigin::ContentBox => BackgroundClip::ContentBox,
    }
  }
}

impl Default for BackgroundClip {
  fn default() -> BackgroundClip {
    BackgroundClip::BorderBox
  }
}

impl BackgroundClip {
  fn is_background_box(&self) -> bool {
    matches!(
      self,
      BackgroundClip::BorderBox | BackgroundClip::PaddingBox | BackgroundClip::ContentBox
    )
  }
}

define_list_shorthand! {
  /// A value for the [background-position](https://drafts.csswg.org/css-backgrounds/#background-position) shorthand property.
  pub struct BackgroundPosition {
    /// The x-position.
    x: BackgroundPositionX(HorizontalPosition),
    /// The y-position.
    y: BackgroundPositionY(VerticalPosition),
  }
}

impl From<Position> for BackgroundPosition {
  fn from(pos: Position) -> Self {
    BackgroundPosition { x: pos.x, y: pos.y }
  }
}

impl Into<Position> for &BackgroundPosition {
  fn into(self) -> Position {
    Position {
      x: self.x.clone(),
      y: self.y.clone(),
    }
  }
}

impl Default for BackgroundPosition {
  fn default() -> Self {
    Position::default().into()
  }
}

impl<'i> Parse<'i> for BackgroundPosition {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let pos = Position::parse(input)?;
    Ok(pos.into())
  }
}

impl ToCss for BackgroundPosition {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    let pos: Position = self.into();
    pos.to_css(dest)
  }
}

/// A value for the [background](https://www.w3.org/TR/css-backgrounds-3/#background) shorthand property.
#[derive(Debug, Clone, PartialEq)]
#[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 Background<'i> {
  /// The background image.
  #[cfg_attr(feature = "serde", serde(borrow))]
  pub image: Image<'i>,
  /// The background color.
  pub color: CssColor,
  /// The background position.
  pub position: BackgroundPosition,
  /// How the background image should repeat.
  pub repeat: BackgroundRepeat,
  /// The size of the background image.
  pub size: BackgroundSize,
  /// The background attachment.
  pub attachment: BackgroundAttachment,
  /// The background origin.
  pub origin: BackgroundOrigin,
  /// How the background should be clipped.
  pub clip: BackgroundClip,
}

impl<'i> Parse<'i> for Background<'i> {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    let mut color: Option<CssColor> = None;
    let mut position: Option<BackgroundPosition> = None;
    let mut size: Option<BackgroundSize> = None;
    let mut image: Option<Image> = None;
    let mut repeat: Option<BackgroundRepeat> = None;
    let mut attachment: Option<BackgroundAttachment> = None;
    let mut origin: Option<BackgroundOrigin> = None;
    let mut clip: Option<BackgroundClip> = None;

    loop {
      // TODO: only allowed on the last background.
      if color.is_none() {
        if let Ok(value) = input.try_parse(CssColor::parse) {
          color = Some(value);
          continue;
        }
      }

      if position.is_none() {
        if let Ok(value) = input.try_parse(BackgroundPosition::parse) {
          position = Some(value);

          size = input
            .try_parse(|input| {
              input.expect_delim('/')?;
              BackgroundSize::parse(input)
            })
            .ok();

          continue;
        }
      }

      if image.is_none() {
        if let Ok(value) = input.try_parse(Image::parse) {
          image = Some(value);
          continue;
        }
      }

      if repeat.is_none() {
        if let Ok(value) = input.try_parse(BackgroundRepeat::parse) {
          repeat = Some(value);
          continue;
        }
      }

      if attachment.is_none() {
        if let Ok(value) = input.try_parse(BackgroundAttachment::parse) {
          attachment = Some(value);
          continue;
        }
      }

      if origin.is_none() {
        if let Ok(value) = input.try_parse(BackgroundOrigin::parse) {
          origin = Some(value);
          continue;
        }
      }

      if clip.is_none() {
        if let Ok(value) = input.try_parse(BackgroundClip::parse) {
          clip = Some(value);
          continue;
        }
      }

      break;
    }

    if clip.is_none() {
      if let Some(origin) = origin {
        clip = Some(origin.into());
      }
    }

    Ok(Background {
      image: image.unwrap_or_default(),
      color: color.unwrap_or_default(),
      position: position.unwrap_or_default(),
      repeat: repeat.unwrap_or_default(),
      size: size.unwrap_or_default(),
      attachment: attachment.unwrap_or_default(),
      origin: origin.unwrap_or(BackgroundOrigin::PaddingBox),
      clip: clip.unwrap_or(BackgroundClip::BorderBox),
    })
  }
}

impl<'i> ToCss for Background<'i> {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    let mut has_output = false;

    if self.color != CssColor::default() {
      self.color.to_css(dest)?;
      has_output = true;
    }

    if self.image != Image::default() {
      if has_output {
        dest.write_str(" ")?;
      }
      self.image.to_css(dest)?;
      has_output = true;
    }

    let position: Position = (&self.position).into();
    if !position.is_zero() || self.size != BackgroundSize::default() {
      if has_output {
        dest.write_str(" ")?;
      }
      position.to_css(dest)?;

      if self.size != BackgroundSize::default() {
        dest.delim('/', true)?;
        self.size.to_css(dest)?;
      }

      has_output = true;
    }

    if self.repeat != BackgroundRepeat::default() {
      if has_output {
        dest.write_str(" ")?;
      }

      self.repeat.to_css(dest)?;
      has_output = true;
    }

    if self.attachment != BackgroundAttachment::default() {
      if has_output {
        dest.write_str(" ")?;
      }

      self.attachment.to_css(dest)?;
      has_output = true;
    }

    let output_padding_box = self.origin != BackgroundOrigin::PaddingBox
      || (self.clip != BackgroundOrigin::BorderBox && self.clip.is_background_box());
    if output_padding_box {
      if has_output {
        dest.write_str(" ")?;
      }

      self.origin.to_css(dest)?;
      has_output = true;
    }

    if (output_padding_box && self.clip != self.origin) || self.clip != BackgroundOrigin::BorderBox {
      if has_output {
        dest.write_str(" ")?;
      }

      self.clip.to_css(dest)?;
      has_output = true;
    }

    // If nothing was output, then this is the initial value, e.g. background: transparent
    if !has_output {
      if dest.minify {
        // `0 0` is the shortest valid background value
        self.position.to_css(dest)?;
      } else {
        dest.write_str("none")?;
      }
    }

    Ok(())
  }
}

impl<'i> ImageFallback<'i> for Background<'i> {
  #[inline]
  fn get_image(&self) -> &Image<'i> {
    &self.image
  }

  #[inline]
  fn with_image(&self, image: Image<'i>) -> Self {
    Background { image, ..self.clone() }
  }

  #[inline]
  fn get_necessary_fallbacks(&self, targets: Targets) -> ColorFallbackKind {
    self.color.get_necessary_fallbacks(targets) | self.get_image().get_necessary_fallbacks(targets)
  }

  #[inline]
  fn get_fallback(&self, kind: ColorFallbackKind) -> Self {
    Background {
      color: self.color.get_fallback(kind),
      image: self.image.get_fallback(kind),
      ..self.clone()
    }
  }
}

impl<'i> Shorthand<'i> for SmallVec<[Background<'i>; 1]> {
  fn from_longhands(decls: &DeclarationBlock<'i>, vendor_prefix: VendorPrefix) -> Option<(Self, bool)> {
    let mut color = None;
    let mut images = None;
    let mut x_positions = None;
    let mut y_positions = None;
    let mut repeats = None;
    let mut sizes = None;
    let mut attachments = None;
    let mut origins = None;
    let mut clips = None;

    let mut count = 0;
    let mut important_count = 0;
    let mut length = None;
    for (property, important) in decls.iter() {
      let len = match property {
        Property::BackgroundColor(value) => {
          color = Some(value.clone());
          count += 1;
          if important {
            important_count += 1;
          }
          continue;
        }
        Property::BackgroundImage(value) => {
          images = Some(value.clone());
          value.len()
        }
        Property::BackgroundPosition(value) => {
          x_positions = Some(value.iter().map(|v| v.x.clone()).collect());
          y_positions = Some(value.iter().map(|v| v.y.clone()).collect());
          value.len()
        }
        Property::BackgroundPositionX(value) => {
          x_positions = Some(value.clone());
          value.len()
        }
        Property::BackgroundPositionY(value) => {
          y_positions = Some(value.clone());
          value.len()
        }
        Property::BackgroundRepeat(value) => {
          repeats = Some(value.clone());
          value.len()
        }
        Property::BackgroundSize(value) => {
          sizes = Some(value.clone());
          value.len()
        }
        Property::BackgroundAttachment(value) => {
          attachments = Some(value.clone());
          value.len()
        }
        Property::BackgroundOrigin(value) => {
          origins = Some(value.clone());
          value.len()
        }
        Property::BackgroundClip(value, vp) => {
          if *vp != vendor_prefix {
            return None;
          }
          clips = Some(value.clone());
          value.len()
        }
        Property::Background(val) => {
          color = Some(val.last().unwrap().color.clone());
          images = Some(val.iter().map(|b| b.image.clone()).collect());
          x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
          y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
          repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
          sizes = Some(val.iter().map(|b| b.size.clone()).collect());
          attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
          origins = Some(val.iter().map(|b| b.origin.clone()).collect());
          clips = Some(val.iter().map(|b| b.clip.clone()).collect());
          val.len()
        }
        _ => continue,
      };

      // Lengths must be equal.
      if length.is_none() {
        length = Some(len);
      } else if length.unwrap() != len {
        return None;
      }

      count += 1;
      if important {
        important_count += 1;
      }
    }

    // !important flags must match to produce a shorthand.
    if important_count > 0 && important_count != count {
      return None;
    }

    if color.is_some()
      && images.is_some()
      && x_positions.is_some()
      && y_positions.is_some()
      && repeats.is_some()
      && sizes.is_some()
      && attachments.is_some()
      && origins.is_some()
      && clips.is_some()
    {
      let length = length.unwrap();
      let values = izip!(
        images.unwrap().drain(..),
        x_positions.unwrap().drain(..),
        y_positions.unwrap().drain(..),
        repeats.unwrap().drain(..),
        sizes.unwrap().drain(..),
        attachments.unwrap().drain(..),
        origins.unwrap().drain(..),
        clips.unwrap().drain(..),
      )
      .enumerate()
      .map(
        |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
          color: if i == length - 1 {
            color.clone().unwrap()
          } else {
            CssColor::default()
          },
          image,
          position: BackgroundPosition {
            x: x_position,
            y: y_position,
          },
          repeat,
          size,
          attachment,
          origin,
          clip: clip,
        },
      )
      .collect();
      return Some((values, important_count > 0));
    }

    None
  }

  fn longhands(vendor_prefix: VendorPrefix) -> Vec<PropertyId<'static>> {
    vec![
      PropertyId::BackgroundColor,
      PropertyId::BackgroundImage,
      PropertyId::BackgroundPositionX,
      PropertyId::BackgroundPositionY,
      PropertyId::BackgroundRepeat,
      PropertyId::BackgroundSize,
      PropertyId::BackgroundAttachment,
      PropertyId::BackgroundOrigin,
      PropertyId::BackgroundClip(vendor_prefix),
    ]
  }

  fn longhand(&self, property_id: &PropertyId) -> Option<Property<'i>> {
    match property_id {
      PropertyId::BackgroundColor => Some(Property::BackgroundColor(self.last().unwrap().color.clone())),
      PropertyId::BackgroundImage => Some(Property::BackgroundImage(
        self.iter().map(|v| v.image.clone()).collect(),
      )),
      PropertyId::BackgroundPositionX => Some(Property::BackgroundPositionX(
        self.iter().map(|v| v.position.x.clone()).collect(),
      )),
      PropertyId::BackgroundPositionY => Some(Property::BackgroundPositionY(
        self.iter().map(|v| v.position.y.clone()).collect(),
      )),
      PropertyId::BackgroundRepeat => Some(Property::BackgroundRepeat(
        self.iter().map(|v| v.repeat.clone()).collect(),
      )),
      PropertyId::BackgroundSize => Some(Property::BackgroundSize(self.iter().map(|v| v.size.clone()).collect())),
      PropertyId::BackgroundAttachment => Some(Property::BackgroundAttachment(
        self.iter().map(|v| v.attachment.clone()).collect(),
      )),
      PropertyId::BackgroundOrigin => Some(Property::BackgroundOrigin(
        self.iter().map(|v| v.origin.clone()).collect(),
      )),
      PropertyId::BackgroundClip(vp) => Some(Property::BackgroundClip(
        self.iter().map(|v| v.clip.clone()).collect(),
        *vp,
      )),
      _ => None,
    }
  }

  fn set_longhand(&mut self, property: &Property<'i>) -> Result<(), ()> {
    macro_rules! longhand {
      ($value: ident, $key: ident $(.$k: ident)*) => {{
        if $value.len() != self.len() {
          return Err(());
        }
        for (i, item) in self.iter_mut().enumerate() {
          item.$key$(.$k)* = $value[i].clone();
        }
      }};
    }

    match property {
      Property::BackgroundColor(value) => self.last_mut().unwrap().color = value.clone(),
      Property::BackgroundImage(value) => longhand!(value, image),
      Property::BackgroundPositionX(value) => longhand!(value, position.x),
      Property::BackgroundPositionY(value) => longhand!(value, position.y),
      Property::BackgroundPosition(value) => longhand!(value, position),
      Property::BackgroundRepeat(value) => longhand!(value, repeat),
      Property::BackgroundSize(value) => longhand!(value, size),
      Property::BackgroundAttachment(value) => longhand!(value, attachment),
      Property::BackgroundOrigin(value) => longhand!(value, origin),
      Property::BackgroundClip(value, _vp) => longhand!(value, clip),
      _ => return Err(()),
    }

    Ok(())
  }
}

property_bitflags! {
  #[derive(Default)]
  struct BackgroundProperty: u16 {
    const BackgroundColor = 1 << 0;
    const BackgroundImage = 1 << 1;
    const BackgroundPositionX = 1 << 2;
    const BackgroundPositionY = 1 << 3;
    const BackgroundPosition = Self::BackgroundPositionX.bits() | Self::BackgroundPositionY.bits();
    const BackgroundRepeat = 1 << 4;
    const BackgroundSize = 1 << 5;
    const BackgroundAttachment = 1 << 6;
    const BackgroundOrigin = 1 << 7;
    const BackgroundClip(_vp) = 1 << 8;
    const Background = Self::BackgroundColor.bits() | Self::BackgroundImage.bits() | Self::BackgroundPosition.bits() | Self::BackgroundRepeat.bits() | Self::BackgroundSize.bits() | Self::BackgroundAttachment.bits() | Self::BackgroundOrigin.bits() | Self::BackgroundClip.bits();
  }
}

#[derive(Default)]
pub(crate) struct BackgroundHandler<'i> {
  color: Option<CssColor>,
  images: Option<SmallVec<[Image<'i>; 1]>>,
  has_prefix: bool,
  x_positions: Option<SmallVec<[HorizontalPosition; 1]>>,
  y_positions: Option<SmallVec<[VerticalPosition; 1]>>,
  repeats: Option<SmallVec<[BackgroundRepeat; 1]>>,
  sizes: Option<SmallVec<[BackgroundSize; 1]>>,
  attachments: Option<SmallVec<[BackgroundAttachment; 1]>>,
  origins: Option<SmallVec<[BackgroundOrigin; 1]>>,
  clips: Option<(SmallVec<[BackgroundClip; 1]>, VendorPrefix)>,
  decls: Vec<Property<'i>>,
  flushed_properties: BackgroundProperty,
  has_any: bool,
}

impl<'i> PropertyHandler<'i> for BackgroundHandler<'i> {
  fn handle_property(
    &mut self,
    property: &Property<'i>,
    dest: &mut DeclarationList<'i>,
    context: &mut PropertyHandlerContext<'i, '_>,
  ) -> bool {
    macro_rules! background_image {
      ($val: expr) => {
        flush!(images, $val);

        // Store prefixed properties. Clear if we hit an unprefixed property and we have
        // targets. In this case, the necessary prefixes will be generated.
        self.has_prefix = $val.iter().any(|x| x.has_vendor_prefix());
        if self.has_prefix {
          self.decls.push(property.clone())
        } else if context.targets.browsers.is_some() {
          self.decls.clear();
        }
      };
    }

    macro_rules! flush {
      ($key: ident, $val: expr) => {{
        if self.$key.is_some() && self.$key.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
          self.flush(dest, context);
        }
      }};
    }

    match &property {
      Property::BackgroundColor(val) => {
        flush!(color, val);
        self.color = Some(val.clone());
      }
      Property::BackgroundImage(val) => {
        background_image!(val);
        self.images = Some(val.clone())
      }
      Property::BackgroundPosition(val) => {
        self.x_positions = Some(val.iter().map(|p| p.x.clone()).collect());
        self.y_positions = Some(val.iter().map(|p| p.y.clone()).collect());
      }
      Property::BackgroundPositionX(val) => self.x_positions = Some(val.clone()),
      Property::BackgroundPositionY(val) => self.y_positions = Some(val.clone()),
      Property::BackgroundRepeat(val) => self.repeats = Some(val.clone()),
      Property::BackgroundSize(val) => self.sizes = Some(val.clone()),
      Property::BackgroundAttachment(val) => self.attachments = Some(val.clone()),
      Property::BackgroundOrigin(val) => self.origins = Some(val.clone()),
      Property::BackgroundClip(val, vendor_prefix) => {
        if let Some((clips, vp)) = &mut self.clips {
          if vendor_prefix != vp && val != clips {
            self.flush(dest, context);
            self.clips = Some((val.clone(), *vendor_prefix))
          } else {
            if val != clips {
              *clips = val.clone();
            }
            *vp |= *vendor_prefix;
          }
        } else {
          self.clips = Some((val.clone(), *vendor_prefix))
        }
      }
      Property::Background(val) => {
        let images: SmallVec<[Image; 1]> = val.iter().map(|b| b.image.clone()).collect();
        background_image!(&images);
        let color = val.last().unwrap().color.clone();
        flush!(color, &color);
        let clips = val.iter().map(|b| b.clip.clone()).collect();
        let mut clips_vp = VendorPrefix::None;
        if let Some((cur_clips, cur_clips_vp)) = &mut self.clips {
          if clips_vp != *cur_clips_vp && *cur_clips != clips {
            self.flush(dest, context);
          } else {
            clips_vp |= *cur_clips_vp;
          }
        }
        self.color = Some(color);
        self.images = Some(images);
        self.x_positions = Some(val.iter().map(|b| b.position.x.clone()).collect());
        self.y_positions = Some(val.iter().map(|b| b.position.y.clone()).collect());
        self.repeats = Some(val.iter().map(|b| b.repeat.clone()).collect());
        self.sizes = Some(val.iter().map(|b| b.size.clone()).collect());
        self.attachments = Some(val.iter().map(|b| b.attachment.clone()).collect());
        self.origins = Some(val.iter().map(|b| b.origin.clone()).collect());
        self.clips = Some((clips, clips_vp));
      }
      Property::Unparsed(val) if is_background_property(&val.property_id) => {
        self.flush(dest, context);
        let mut unparsed = val.clone();
        context.add_unparsed_fallbacks(&mut unparsed);
        self
          .flushed_properties
          .insert(BackgroundProperty::try_from(&unparsed.property_id).unwrap());
        dest.push(Property::Unparsed(unparsed));
      }
      _ => return false,
    }

    self.has_any = true;
    true
  }

  fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
    // If the last declaration is prefixed, pop the last value
    // so it isn't duplicated when we flush.
    if self.has_prefix {
      self.decls.pop();
    }

    dest.extend(self.decls.drain(..));
    self.flush(dest, context);
    self.flushed_properties = BackgroundProperty::empty();
  }
}

impl<'i> BackgroundHandler<'i> {
  fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
    if !self.has_any {
      return;
    }

    self.has_any = false;

    macro_rules! push {
      ($prop: ident, $val: expr) => {
        dest.push(Property::$prop($val));
        self.flushed_properties.insert(BackgroundProperty::$prop);
      };
    }

    let color = std::mem::take(&mut self.color);
    let mut images = std::mem::take(&mut self.images);
    let mut x_positions = std::mem::take(&mut self.x_positions);
    let mut y_positions = std::mem::take(&mut self.y_positions);
    let mut repeats = std::mem::take(&mut self.repeats);
    let mut sizes = std::mem::take(&mut self.sizes);
    let mut attachments = std::mem::take(&mut self.attachments);
    let mut origins = std::mem::take(&mut self.origins);
    let mut clips = std::mem::take(&mut self.clips);

    if let (
      Some(color),
      Some(images),
      Some(x_positions),
      Some(y_positions),
      Some(repeats),
      Some(sizes),
      Some(attachments),
      Some(origins),
      Some(clips),
    ) = (
      &color,
      &mut images,
      &mut x_positions,
      &mut y_positions,
      &mut repeats,
      &mut sizes,
      &mut attachments,
      &mut origins,
      &mut clips,
    ) {
      // Only use shorthand syntax if the number of layers matches on all properties.
      let len = images.len();
      if x_positions.len() == len
        && y_positions.len() == len
        && repeats.len() == len
        && sizes.len() == len
        && attachments.len() == len
        && origins.len() == len
        && clips.0.len() == len
      {
        let clip_prefixes = if clips.0.iter().any(|clip| *clip == BackgroundClip::Text) {
          context.targets.prefixes(clips.1, Feature::BackgroundClip)
        } else {
          clips.1
        };

        let clip_property = if clip_prefixes != VendorPrefix::None {
          Some(Property::BackgroundClip(clips.0.clone(), clip_prefixes))
        } else {
          None
        };

        let mut backgrounds: SmallVec<[Background<'i>; 1]> = izip!(
          images.drain(..),
          x_positions.drain(..),
          y_positions.drain(..),
          repeats.drain(..),
          sizes.drain(..),
          attachments.drain(..),
          origins.drain(..),
          clips.0.drain(..)
        )
        .enumerate()
        .map(
          |(i, (image, x_position, y_position, repeat, size, attachment, origin, clip))| Background {
            color: if i == len - 1 {
              color.clone()
            } else {
              CssColor::default()
            },
            image,
            position: BackgroundPosition {
              x: x_position,
              y: y_position,
            },
            repeat,
            size,
            attachment,
            origin,
            clip: if clip_prefixes == VendorPrefix::None {
              clip
            } else {
              BackgroundClip::default()
            },
          },
        )
        .collect();

        if !self.flushed_properties.intersects(BackgroundProperty::Background) {
          for fallback in backgrounds.get_fallbacks(context.targets) {
            push!(Background, fallback);
          }
        }

        push!(Background, backgrounds);

        if let Some(clip) = clip_property {
          dest.push(clip);
          self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
        }

        self.reset();
        return;
      }
    }

    if let Some(mut color) = color {
      if !self.flushed_properties.contains(BackgroundProperty::BackgroundColor) {
        for fallback in color.get_fallbacks(context.targets) {
          push!(BackgroundColor, fallback);
        }
      }

      push!(BackgroundColor, color);
    }

    if let Some(mut images) = images {
      if !self.flushed_properties.contains(BackgroundProperty::BackgroundImage) {
        for fallback in images.get_fallbacks(context.targets) {
          push!(BackgroundImage, fallback);
        }
      }

      push!(BackgroundImage, images);
    }

    match (&mut x_positions, &mut y_positions) {
      (Some(x_positions), Some(y_positions)) if x_positions.len() == y_positions.len() => {
        let positions = izip!(x_positions.drain(..), y_positions.drain(..))
          .map(|(x, y)| BackgroundPosition { x, y })
          .collect();
        push!(BackgroundPosition, positions);
      }
      _ => {
        if let Some(x_positions) = x_positions {
          push!(BackgroundPositionX, x_positions);
        }

        if let Some(y_positions) = y_positions {
          push!(BackgroundPositionY, y_positions);
        }
      }
    }

    if let Some(repeats) = repeats {
      push!(BackgroundRepeat, repeats);
    }

    if let Some(sizes) = sizes {
      push!(BackgroundSize, sizes);
    }

    if let Some(attachments) = attachments {
      push!(BackgroundAttachment, attachments);
    }

    if let Some(origins) = origins {
      push!(BackgroundOrigin, origins);
    }

    if let Some((clips, vp)) = clips {
      let prefixes = if clips.iter().any(|clip| *clip == BackgroundClip::Text) {
        context.targets.prefixes(vp, Feature::BackgroundClip)
      } else {
        vp
      };
      dest.push(Property::BackgroundClip(clips, prefixes));
      self.flushed_properties.insert(BackgroundProperty::BackgroundClip);
    }

    self.reset();
  }

  fn reset(&mut self) {
    self.color = None;
    self.images = None;
    self.x_positions = None;
    self.y_positions = None;
    self.repeats = None;
    self.sizes = None;
    self.attachments = None;
    self.origins = None;
    self.clips = None
  }
}

#[inline]
fn is_background_property(property_id: &PropertyId) -> bool {
  match property_id {
    PropertyId::BackgroundColor
    | PropertyId::BackgroundImage
    | PropertyId::BackgroundPosition
    | PropertyId::BackgroundPositionX
    | PropertyId::BackgroundPositionY
    | PropertyId::BackgroundRepeat
    | PropertyId::BackgroundSize
    | PropertyId::BackgroundAttachment
    | PropertyId::BackgroundOrigin
    | PropertyId::BackgroundClip(_)
    | PropertyId::Background => true,
    _ => false,
  }
}
