//! CSS properties used in SVG.

use crate::error::{ParserError, PrinterError};
use crate::macros::enum_property;
use crate::printer::Printer;
use crate::targets::{Browsers, Targets};
use crate::traits::{FallbackValues, IsCompatible, Parse, ToCss};
use crate::values::length::LengthPercentage;
use crate::values::{color::CssColor, url::Url};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use cssparser::*;

/// An SVG [`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value
/// used in the `fill` and `stroke` properties.
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[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(tag = "type", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum SVGPaint<'i> {
  /// A URL reference to a paint server element, e.g. `linearGradient`, `radialGradient`, and `pattern`.
  Url {
    #[cfg_attr(feature = "serde", serde(borrow))]
    /// The url of the paint server.
    url: Url<'i>,
    /// A fallback to be used used in case the paint server cannot be resolved.
    fallback: Option<SVGPaintFallback>,
  },
  /// A solid color paint.
  #[cfg_attr(feature = "serde", serde(with = "crate::serialization::ValueWrapper::<CssColor>"))]
  Color(CssColor),
  /// Use the paint value of fill from a context element.
  ContextFill,
  /// Use the paint value of stroke from a context element.
  ContextStroke,
  /// No paint.
  None,
}

/// A fallback for an SVG paint in case a paint server `url()` cannot be resolved.
///
/// See [SVGPaint](SVGPaint).
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum SVGPaintFallback {
  /// No fallback.
  None,
  /// A solid color.
  Color(CssColor),
}

impl<'i> FallbackValues for SVGPaint<'i> {
  fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
    match self {
      SVGPaint::Color(color) => color
        .get_fallbacks(targets)
        .into_iter()
        .map(|color| SVGPaint::Color(color))
        .collect(),
      SVGPaint::Url {
        url,
        fallback: Some(SVGPaintFallback::Color(color)),
      } => color
        .get_fallbacks(targets)
        .into_iter()
        .map(|color| SVGPaint::Url {
          url: url.clone(),
          fallback: Some(SVGPaintFallback::Color(color)),
        })
        .collect(),
      _ => Vec::new(),
    }
  }
}

impl IsCompatible for SVGPaint<'_> {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      SVGPaint::Color(c)
      | SVGPaint::Url {
        fallback: Some(SVGPaintFallback::Color(c)),
        ..
      } => c.is_compatible(browsers),
      SVGPaint::Url { .. } | SVGPaint::None | SVGPaint::ContextFill | SVGPaint::ContextStroke => true,
    }
  }
}

enum_property! {
  /// A value for the [stroke-linecap](https://www.w3.org/TR/SVG2/painting.html#LineCaps) property.
  pub enum StrokeLinecap {
    /// The stroke does not extend beyond its endpoints.
    Butt,
    /// The ends of the stroke are rounded.
    Round,
    /// The ends of the stroke are squared.
    Square,
  }
}

enum_property! {
  /// A value for the [stroke-linejoin](https://www.w3.org/TR/SVG2/painting.html#LineJoin) property.
  pub enum StrokeLinejoin {
    /// A sharp corner is to be used to join path segments.
    Miter,
    /// Same as `miter` but clipped beyond `stroke-miterlimit`.
    MiterClip,
    /// A round corner is to be used to join path segments.
    Round,
    /// A bevelled corner is to be used to join path segments.
    Bevel,
    /// An arcs corner is to be used to join path segments.
    Arcs,
  }
}

/// A value for the [stroke-dasharray](https://www.w3.org/TR/SVG2/painting.html#StrokeDashing) property.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum StrokeDasharray {
  /// No dashing is used.
  None,
  /// Specifies a dashing pattern to use.
  Values(Vec<LengthPercentage>),
}

impl<'i> Parse<'i> for StrokeDasharray {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
      return Ok(StrokeDasharray::None);
    }

    input.skip_whitespace();
    let mut results = vec![LengthPercentage::parse(input)?];
    loop {
      input.skip_whitespace();
      let comma_location = input.current_source_location();
      let comma = input.try_parse(|i| i.expect_comma()).is_ok();
      if let Ok(item) = input.try_parse(LengthPercentage::parse) {
        results.push(item);
      } else if comma {
        return Err(comma_location.new_unexpected_token_error(Token::Comma));
      } else {
        break;
      }
    }

    Ok(StrokeDasharray::Values(results))
  }
}

impl ToCss for StrokeDasharray {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      StrokeDasharray::None => dest.write_str("none"),
      StrokeDasharray::Values(values) => {
        let mut first = true;
        for value in values {
          if first {
            first = false;
          } else {
            dest.write_char(' ')?;
          }
          value.to_css_unitless(dest)?;
        }
        Ok(())
      }
    }
  }
}

/// A value for the [marker](https://www.w3.org/TR/SVG2/painting.html#VertexMarkerProperties) properties.
#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
#[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(tag = "type", content = "value", rename_all = "kebab-case")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum Marker<'i> {
  /// No marker.
  None,
  /// A url reference to a `<marker>` element.
  #[cfg_attr(feature = "serde", serde(borrow))]
  Url(Url<'i>),
}

/// A value for the [color-interpolation](https://www.w3.org/TR/SVG2/painting.html#ColorInterpolation) property.
#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ColorInterpolation {
  /// The UA can choose between sRGB or linearRGB.
  Auto,
  /// Color interpolation occurs in the sRGB color space.
  SRGB,
  /// Color interpolation occurs in the linearized RGB color space
  LinearRGB,
}

/// A value for the [color-rendering](https://www.w3.org/TR/SVG2/painting.html#ColorRendering) property.
#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ColorRendering {
  /// The UA can choose a tradeoff between speed and quality.
  Auto,
  /// The UA shall optimize speed over quality.
  OptimizeSpeed,
  /// The UA shall optimize quality over speed.
  OptimizeQuality,
}

/// A value for the [shape-rendering](https://www.w3.org/TR/SVG2/painting.html#ShapeRendering) property.
#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ShapeRendering {
  /// The UA can choose an appropriate tradeoff.
  Auto,
  /// The UA shall optimize speed.
  OptimizeSpeed,
  /// The UA shall optimize crisp edges.
  CrispEdges,
  /// The UA shall optimize geometric precision.
  GeometricPrecision,
}

/// A value for the [text-rendering](https://www.w3.org/TR/SVG2/painting.html#TextRendering) property.
#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum TextRendering {
  /// The UA can choose an appropriate tradeoff.
  Auto,
  /// The UA shall optimize speed.
  OptimizeSpeed,
  /// The UA shall optimize legibility.
  OptimizeLegibility,
  /// The UA shall optimize geometric precision.
  GeometricPrecision,
}

/// A value for the [image-rendering](https://www.w3.org/TR/SVG2/painting.html#ImageRendering) property.
#[derive(Debug, Clone, Copy, PartialEq, Parse, ToCss)]
#[cfg_attr(feature = "visitor", derive(Visit))]
#[cfg_attr(
  feature = "serde",
  derive(serde::Serialize, serde::Deserialize),
  serde(rename_all = "lowercase")
)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
pub enum ImageRendering {
  /// The UA can choose a tradeoff between speed and quality.
  Auto,
  /// The UA shall optimize speed over quality.
  OptimizeSpeed,
  /// The UA shall optimize quality over speed.
  OptimizeQuality,
}
