//! CSS length values.

use super::angle::impl_try_from_angle;
use super::calc::{Calc, MathFunction};
use super::number::CSSNumber;
use super::percentage::DimensionPercentage;
use crate::error::{ParserError, PrinterError};
use crate::printer::Printer;
use crate::targets::Browsers;
use crate::traits::{
  private::{AddInternal, TryAdd},
  Map, Parse, Sign, ToCss, TryMap, TryOp, Zero,
};
use crate::traits::{IsCompatible, TrySign};
#[cfg(feature = "visitor")]
use crate::visitor::Visit;
use const_str;
use cssparser::*;

/// A CSS [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage) value.
/// May be specified as either a length or a percentage that resolves to an length.
pub type LengthPercentage = DimensionPercentage<LengthValue>;

impl LengthPercentage {
  /// Constructs a `LengthPercentage` with the given pixel value.
  pub fn px(val: CSSNumber) -> LengthPercentage {
    LengthPercentage::Dimension(LengthValue::Px(val))
  }

  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      DimensionPercentage::Dimension(d) => d.to_css_unitless(dest),
      _ => self.to_css(dest),
    }
  }
}

impl IsCompatible for LengthPercentage {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      LengthPercentage::Dimension(d) => d.is_compatible(browsers),
      LengthPercentage::Calc(c) => c.is_compatible(browsers),
      LengthPercentage::Percentage(..) => true,
    }
  }
}

/// Either a [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage), or the `auto` keyword.
#[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 LengthPercentageOrAuto {
  /// The `auto` keyword.
  Auto,
  /// A [`<length-percentage>`](https://www.w3.org/TR/css-values-4/#typedef-length-percentage).
  LengthPercentage(LengthPercentage),
}

impl IsCompatible for LengthPercentageOrAuto {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      LengthPercentageOrAuto::LengthPercentage(p) => p.is_compatible(browsers),
      _ => true,
    }
  }
}

const PX_PER_IN: f32 = 96.0;
const PX_PER_CM: f32 = PX_PER_IN / 2.54;
const PX_PER_MM: f32 = PX_PER_CM / 10.0;
const PX_PER_Q: f32 = PX_PER_CM / 40.0;
const PX_PER_PT: f32 = PX_PER_IN / 72.0;
const PX_PER_PC: f32 = PX_PER_IN / 6.0;

macro_rules! define_length_units {
  (
    $(
      $(#[$meta: meta])*
      $name: ident $(/ $feature: ident)?,
    )+
  ) => {
    /// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value,
    /// without support for `calc()`. See also: [Length](Length).
    #[derive(Debug, Clone, PartialEq)]
    #[cfg_attr(feature = "visitor", derive(Visit))]
    #[cfg_attr(feature = "visitor", visit(visit_length, LENGTHS))]
    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(tag = "unit", content = "value", rename_all = "kebab-case"))]
    #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
    pub enum LengthValue {
      $(
        $(#[$meta])*
        $name(CSSNumber),
      )+
    }

    impl<'i> Parse<'i> for LengthValue {
      fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
        let location = input.current_source_location();
        let token = input.next()?;
        match *token {
          Token::Dimension { value, ref unit, .. } => {
            Ok(match unit {
              $(
                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(value),
              )+
              _ => return Err(location.new_unexpected_token_error(token.clone())),
            })
          },
          Token::Number { value, .. } => {
            // TODO: quirks mode only?
            Ok(LengthValue::Px(value))
          }
          ref token => return Err(location.new_unexpected_token_error(token.clone())),
        }
      }
    }

    impl<'i> TryFrom<&Token<'i>> for LengthValue {
      type Error = ();

      fn try_from(token: &Token) -> Result<Self, Self::Error> {
        match token {
          Token::Dimension { value, ref unit, .. } => {
            Ok(match unit {
              $(
                s if s.eq_ignore_ascii_case(stringify!($name)) => LengthValue::$name(*value),
              )+
              _ => return Err(()),
            })
          },
          _ => Err(())
        }
      }
    }

    impl LengthValue {
      /// Returns the numeric value and unit string for the length value.
      pub fn to_unit_value(&self) -> (CSSNumber, &str) {
        match self {
          $(
            LengthValue::$name(value) => (*value, const_str::convert_ascii_case!(lower, stringify!($name))),
          )+
        }
      }
    }

    impl IsCompatible for LengthValue {
      fn is_compatible(&self, browsers: Browsers) -> bool {
        macro_rules! is_compatible {
          ($f: ident) => {
            crate::compat::Feature::$f.is_compatible(browsers)
          };
          () => {
            true
          };
        }

        match self {
          $(
            LengthValue::$name(_) => {
              is_compatible!($($feature)?)
            }
          )+
        }
      }
    }

    impl TryAdd<LengthValue> for LengthValue {
      fn try_add(&self, other: &LengthValue) -> Option<LengthValue> {
        use LengthValue::*;
        match (self, other) {
          $(
            ($name(a), $name(b)) => Some($name(a + b)),
          )+
          (a, b) => {
            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
              Some(Px(a + b))
            } else {
              None
            }
          }
        }
      }
    }

    impl std::ops::Mul<CSSNumber> for LengthValue {
      type Output = Self;

      fn mul(self, other: CSSNumber) -> LengthValue {
        use LengthValue::*;
        match self {
          $(
            $name(value) => $name(value * other),
          )+
        }
      }
    }

    impl std::cmp::PartialOrd<LengthValue> for LengthValue {
      fn partial_cmp(&self, other: &LengthValue) -> Option<std::cmp::Ordering> {
        use LengthValue::*;
        match (self, other) {
          $(
            ($name(a), $name(b)) => a.partial_cmp(b),
          )+
          (a, b) => {
            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
              a.partial_cmp(&b)
            } else {
              None
            }
          }
        }
      }
    }

    impl TryOp for LengthValue {
      fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {
        use LengthValue::*;
        match (self, rhs) {
          $(
            ($name(a), $name(b)) => Some($name(op(*a, *b))),
          )+
          (a, b) => {
            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
              Some(Px(op(a, b)))
            } else {
              None
            }
          }
        }
      }

      fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {
        use LengthValue::*;
        match (self, rhs) {
          $(
            ($name(a), $name(b)) => Some(op(*a, *b)),
          )+
          (a, b) => {
            if let (Some(a), Some(b)) = (a.to_px(), b.to_px()) {
              Some(op(a, b))
            } else {
              None
            }
          }
        }
      }
    }

    impl Map for LengthValue {
      fn map<F: FnOnce(f32) -> f32>(&self, op: F) -> Self {
        use LengthValue::*;
        match self {
          $(
            $name(value) => $name(op(*value)),
          )+
        }
      }
    }

    impl Sign for LengthValue {
      fn sign(&self) -> f32 {
        use LengthValue::*;
        match self {
          $(
            $name(value) => value.sign(),
          )+
        }
      }
    }

    impl Zero for LengthValue {
      fn zero() -> Self {
        LengthValue::Px(0.0)
      }

      fn is_zero(&self) -> bool {
        use LengthValue::*;
        match self {
          $(
            $name(value) => value.is_zero(),
          )+
        }
      }
    }

    impl_try_from_angle!(LengthValue);

    #[cfg(feature = "jsonschema")]
    #[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
    impl schemars::JsonSchema for LengthValue {
      fn is_referenceable() -> bool {
        true
      }

      fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
        #[derive(schemars::JsonSchema)]
        #[schemars(rename_all = "lowercase")]
        #[allow(dead_code)]
        enum LengthUnit {
          $(
            $(#[$meta])*
            $name,
          )+
        }

        #[derive(schemars::JsonSchema)]
        #[allow(dead_code)]
        struct LengthValue {
          /// The length unit.
          unit: LengthUnit,
          /// The length value.
          value: CSSNumber
        }

        LengthValue::json_schema(gen)
      }

      fn schema_name() -> String {
        "LengthValue".into()
      }
    }
  };
}

define_length_units! {
  // https://www.w3.org/TR/css-values-4/#absolute-lengths
  /// A length in pixels.
  Px,
  /// A length in inches. 1in = 96px.
  In,
  /// A length in centimeters. 1cm = 96px / 2.54.
  Cm,
  /// A length in millimeters. 1mm = 1/10th of 1cm.
  Mm,
  /// A length in quarter-millimeters. 1Q = 1/40th of 1cm.
  Q / QUnit,
  /// A length in points. 1pt = 1/72nd of 1in.
  Pt,
  /// A length in picas. 1pc = 1/6th of 1in.
  Pc,

  // https://www.w3.org/TR/css-values-4/#font-relative-lengths
  /// A length in the `em` unit. An `em` is equal to the computed value of the
  /// font-size property of the element on which it is used.
  Em,
  /// A length in the `rem` unit. A `rem` is equal to the computed value of the
  /// `em` unit on the root element.
  Rem / RemUnit,
  /// A length in `ex` unit. An `ex` is equal to the x-height of the font.
  Ex / ExUnit,
  /// A length in the `rex` unit. A `rex` is equal to the value of the `ex` unit on the root element.
  Rex,
  /// A length in the `ch` unit. A `ch` is equal to the width of the zero ("0") character in the current font.
  Ch / ChUnit,
  /// A length in the `rch` unit. An `rch` is equal to the value of the `ch` unit on the root element.
  Rch,
  /// A length in the `cap` unit. A `cap` is equal to the cap-height of the font.
  Cap / CapUnit,
  /// A length in the `rcap` unit. An `rcap` is equal to the value of the `cap` unit on the root element.
  Rcap,
  /// A length in the `ic` unit. An `ic` is equal to the width of the “水” (CJK water ideograph) character in the current font.
  Ic / IcUnit,
  /// A length in the `ric` unit. An `ric` is equal to the value of the `ic` unit on the root element.
  Ric,
  /// A length in the `lh` unit. An `lh` is equal to the computed value of the `line-height` property.
  Lh / LhUnit,
  /// A length in the `rlh` unit. An `rlh` is equal to the value of the `lh` unit on the root element.
  Rlh / RlhUnit,

  // https://www.w3.org/TR/css-values-4/#viewport-relative-units
  /// A length in the `vw` unit. A `vw` is equal to 1% of the [viewport width](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size).
  Vw / VwUnit,
  /// A length in the `lvw` unit. An `lvw` is equal to 1% of the [large viewport width](https://www.w3.org/TR/css-values-4/#large-viewport-size).
  Lvw / ViewportPercentageUnitsLarge,
  /// A length in the `svw` unit. An `svw` is equal to 1% of the [small viewport width](https://www.w3.org/TR/css-values-4/#small-viewport-size).
  Svw / ViewportPercentageUnitsSmall,
  /// A length in the `dvw` unit. An `dvw` is equal to 1% of the [dynamic viewport width](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size).
  Dvw / ViewportPercentageUnitsDynamic,
  /// A length in the `cqw` unit. An `cqw` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) width.
  Cqw / ContainerQueryLengthUnits,

  /// A length in the `vh` unit. A `vh` is equal to 1% of the [viewport height](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size).
  Vh / VhUnit,
  /// A length in the `lvh` unit. An `lvh` is equal to 1% of the [large viewport height](https://www.w3.org/TR/css-values-4/#large-viewport-size).
  Lvh / ViewportPercentageUnitsLarge,
  /// A length in the `svh` unit. An `svh` is equal to 1% of the [small viewport height](https://www.w3.org/TR/css-values-4/#small-viewport-size).
  Svh / ViewportPercentageUnitsSmall,
  /// A length in the `dvh` unit. An `dvh` is equal to 1% of the [dynamic viewport height](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size).
  Dvh / ViewportPercentageUnitsDynamic,
  /// A length in the `cqh` unit. An `cqh` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) height.
  Cqh / ContainerQueryLengthUnits,

  /// A length in the `vi` unit. A `vi` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size)
  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
  Vi / ViUnit,
  /// A length in the `svi` unit. A `svi` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size)
  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
  Svi / ViewportPercentageUnitsSmall,
  /// A length in the `lvi` unit. A `lvi` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size)
  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
  Lvi / ViewportPercentageUnitsLarge,
  /// A length in the `dvi` unit. A `dvi` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size)
  /// in the box's [inline axis](https://www.w3.org/TR/css-writing-modes-4/#inline-axis).
  Dvi / ViewportPercentageUnitsDynamic,
  /// A length in the `cqi` unit. An `cqi` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) inline size.
  Cqi / ContainerQueryLengthUnits,

  /// A length in the `vb` unit. A `vb` is equal to 1% of the [viewport size](https://www.w3.org/TR/css-values-4/#ua-default-viewport-size)
  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
  Vb / VbUnit,
  /// A length in the `svb` unit. A `svb` is equal to 1% of the [small viewport size](https://www.w3.org/TR/css-values-4/#small-viewport-size)
  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
  Svb / ViewportPercentageUnitsSmall,
  /// A length in the `lvb` unit. A `lvb` is equal to 1% of the [large viewport size](https://www.w3.org/TR/css-values-4/#large-viewport-size)
  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
  Lvb / ViewportPercentageUnitsLarge,
  /// A length in the `dvb` unit. A `dvb` is equal to 1% of the [dynamic viewport size](https://www.w3.org/TR/css-values-4/#dynamic-viewport-size)
  /// in the box's [block axis](https://www.w3.org/TR/css-writing-modes-4/#block-axis).
  Dvb / ViewportPercentageUnitsDynamic,
  /// A length in the `cqb` unit. An `cqb` is equal to 1% of the [query container](https://drafts.csswg.org/css-contain-3/#query-container) block size.
  Cqb / ContainerQueryLengthUnits,

  /// A length in the `vmin` unit. A `vmin` is equal to the smaller of `vw` and `vh`.
  Vmin / VminUnit,
  /// A length in the `svmin` unit. An `svmin` is equal to the smaller of `svw` and `svh`.
  Svmin / ViewportPercentageUnitsSmall,
  /// A length in the `lvmin` unit. An `lvmin` is equal to the smaller of `lvw` and `lvh`.
  Lvmin / ViewportPercentageUnitsLarge,
  /// A length in the `dvmin` unit. A `dvmin` is equal to the smaller of `dvw` and `dvh`.
  Dvmin / ViewportPercentageUnitsDynamic,
  /// A length in the `cqmin` unit. An `cqmin` is equal to the smaller of `cqi` and `cqb`.
  Cqmin / ContainerQueryLengthUnits,

  /// A length in the `vmax` unit. A `vmax` is equal to the larger of `vw` and `vh`.
  Vmax / VmaxUnit,
  /// A length in the `svmax` unit. An `svmax` is equal to the larger of `svw` and `svh`.
  Svmax / ViewportPercentageUnitsSmall,
  /// A length in the `lvmax` unit. An `lvmax` is equal to the larger of `lvw` and `lvh`.
  Lvmax / ViewportPercentageUnitsLarge,
  /// A length in the `dvmax` unit. An `dvmax` is equal to the larger of `dvw` and `dvh`.
  Dvmax / ViewportPercentageUnitsDynamic,
  /// A length in the `cqmax` unit. An `cqmin` is equal to the larger of `cqi` and `cqb`.
  Cqmax / ContainerQueryLengthUnits,
}

impl ToCss for LengthValue {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    let (value, unit) = self.to_unit_value();

    // The unit can be omitted if the value is zero, except inside calc()
    // expressions, where unitless numbers won't be parsed as dimensions.
    if !dest.in_calc && value == 0.0 {
      return dest.write_char('0');
    }

    serialize_dimension(value, unit, dest)
  }
}

impl LengthValue {
  pub(crate) fn to_css_unitless<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      LengthValue::Px(value) => value.to_css(dest),
      _ => self.to_css(dest),
    }
  }
}

pub(crate) fn serialize_dimension<W>(value: f32, unit: &str, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
  W: std::fmt::Write,
{
  use cssparser::ToCss;
  let int_value = if value.fract() == 0.0 { Some(value as i32) } else { None };
  let token = Token::Dimension {
    has_sign: value < 0.0,
    value,
    int_value,
    unit: CowRcStr::from(unit),
  };
  if value != 0.0 && value.abs() < 1.0 {
    let mut s = String::new();
    token.to_css(&mut s)?;
    if value < 0.0 {
      if let Some(stripped) = s.strip_prefix("-0") {
        dest.write_char('-')?;
        dest.write_str(stripped)
      } else {
        dest.write_str(&s)
      }
    } else {
      dest.write_str(s.trim_start_matches('0'))
    }
  } else {
    token.to_css(dest)?;
    Ok(())
  }
}

impl LengthValue {
  /// Attempts to convert the value to pixels.
  /// Returns `None` if the conversion is not possible.
  pub fn to_px(&self) -> Option<CSSNumber> {
    use LengthValue::*;
    match self {
      Px(value) => Some(*value),
      In(value) => Some(value * PX_PER_IN),
      Cm(value) => Some(value * PX_PER_CM),
      Mm(value) => Some(value * PX_PER_MM),
      Q(value) => Some(value * PX_PER_Q),
      Pt(value) => Some(value * PX_PER_PT),
      Pc(value) => Some(value * PX_PER_PC),
      _ => None,
    }
  }
}

/// A CSS [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) value, with support for `calc()`.
#[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 Length {
  /// An explicitly specified length value.
  Value(LengthValue),
  /// A computed length value using `calc()`.
  #[cfg_attr(feature = "visitor", skip_type)]
  Calc(Box<Calc<Length>>),
}

impl<'i> Parse<'i> for Length {
  fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
    match input.try_parse(Calc::parse) {
      Ok(Calc::Value(v)) => return Ok(*v),
      Ok(calc) => return Ok(Length::Calc(Box::new(calc))),
      _ => {}
    }

    let len = LengthValue::parse(input)?;
    Ok(Length::Value(len))
  }
}

impl ToCss for Length {
  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
  where
    W: std::fmt::Write,
  {
    match self {
      Length::Value(a) => a.to_css(dest),
      Length::Calc(c) => c.to_css(dest),
    }
  }
}

impl std::ops::Mul<CSSNumber> for Length {
  type Output = Self;

  fn mul(self, other: CSSNumber) -> Length {
    match self {
      Length::Value(a) => Length::Value(a * other),
      Length::Calc(a) => Length::Calc(Box::new(*a * other)),
    }
  }
}

impl std::ops::Add<Length> for Length {
  type Output = Self;

  fn add(self, other: Length) -> Length {
    // Unwrap calc(...) functions so we can add inside.
    // Then wrap the result in a calc(...) again if necessary.
    let a = unwrap_calc(self);
    let b = unwrap_calc(other);
    let res = AddInternal::add(a, b);
    match res {
      Length::Calc(c) => match *c {
        Calc::Value(l) => *l,
        Calc::Function(f) if !matches!(*f, MathFunction::Calc(_)) => Length::Calc(Box::new(Calc::Function(f))),
        c => Length::Calc(Box::new(Calc::Function(Box::new(MathFunction::Calc(c))))),
      },
      _ => res,
    }
  }
}

fn unwrap_calc(length: Length) -> Length {
  match length {
    Length::Calc(c) => match *c {
      Calc::Function(f) => match *f {
        MathFunction::Calc(c) => Length::Calc(Box::new(c)),
        c => Length::Calc(Box::new(Calc::Function(Box::new(c)))),
      },
      _ => Length::Calc(c),
    },
    _ => length,
  }
}

impl AddInternal for Length {
  fn add(self, other: Self) -> Self {
    match self.try_add(&other) {
      Some(r) => r,
      None => self.add(other),
    }
  }
}

impl Length {
  /// Constructs a length with the given pixel value.
  pub fn px(px: CSSNumber) -> Length {
    Length::Value(LengthValue::Px(px))
  }

  /// Attempts to convert the length to pixels.
  /// Returns `None` if the conversion is not possible.
  pub fn to_px(&self) -> Option<CSSNumber> {
    match self {
      Length::Value(a) => a.to_px(),
      _ => None,
    }
  }

  fn add(self, other: Length) -> Length {
    let mut a = self;
    let mut b = other;

    if a.is_zero() {
      return b;
    }

    if b.is_zero() {
      return a;
    }

    if a.is_sign_negative() && b.is_sign_positive() {
      std::mem::swap(&mut a, &mut b);
    }

    match (a, b) {
      (Length::Calc(a), Length::Calc(b)) => return Length::Calc(Box::new(a.add(*b).unwrap())),
      (Length::Calc(calc), b) => {
        if let Calc::Value(a) = *calc {
          a.add(b)
        } else {
          Length::Calc(Box::new(Calc::Sum(Box::new((*calc).into()), Box::new(b.into()))))
        }
      }
      (a, Length::Calc(calc)) => {
        if let Calc::Value(b) = *calc {
          a.add(*b)
        } else {
          Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new((*calc).into()))))
        }
      }
      (a, b) => Length::Calc(Box::new(Calc::Sum(Box::new(a.into()), Box::new(b.into())))),
    }
  }
}

impl IsCompatible for Length {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      Length::Value(v) => v.is_compatible(browsers),
      Length::Calc(calc) => calc.is_compatible(browsers),
    }
  }
}

impl Zero for Length {
  fn zero() -> Length {
    Length::Value(LengthValue::Px(0.0))
  }

  fn is_zero(&self) -> bool {
    match self {
      Length::Value(v) => v.is_zero(),
      _ => false,
    }
  }
}

impl TryAdd<Length> for Length {
  fn try_add(&self, other: &Length) -> Option<Length> {
    match (self, other) {
      (Length::Value(a), Length::Value(b)) => {
        if let Some(res) = a.try_add(b) {
          Some(Length::Value(res))
        } else {
          None
        }
      }
      (Length::Calc(a), other) => match &**a {
        Calc::Value(v) => v.try_add(other),
        Calc::Sum(a, b) => {
          if let Some(res) = Length::Calc(Box::new(*a.clone())).try_add(other) {
            return Some(res.add(Length::from(*b.clone())));
          }

          if let Some(res) = Length::Calc(Box::new(*b.clone())).try_add(other) {
            return Some(Length::from(*a.clone()).add(res));
          }

          None
        }
        _ => None,
      },
      (other, Length::Calc(b)) => match &**b {
        Calc::Value(v) => other.try_add(&*v),
        Calc::Sum(a, b) => {
          if let Some(res) = other.try_add(&Length::Calc(Box::new(*a.clone()))) {
            return Some(res.add(Length::from(*b.clone())));
          }

          if let Some(res) = other.try_add(&Length::Calc(Box::new(*b.clone()))) {
            return Some(Length::from(*a.clone()).add(res));
          }

          None
        }
        _ => None,
      },
    }
  }
}

impl std::convert::Into<Calc<Length>> for Length {
  fn into(self) -> Calc<Length> {
    match self {
      Length::Calc(c) => *c,
      b => Calc::Value(Box::new(b)),
    }
  }
}

impl std::convert::From<Calc<Length>> for Length {
  fn from(calc: Calc<Length>) -> Length {
    Length::Calc(Box::new(calc))
  }
}

impl std::cmp::PartialOrd<Length> for Length {
  fn partial_cmp(&self, other: &Length) -> Option<std::cmp::Ordering> {
    match (self, other) {
      (Length::Value(a), Length::Value(b)) => a.partial_cmp(b),
      _ => None,
    }
  }
}

impl TryOp for Length {
  fn try_op<F: FnOnce(f32, f32) -> f32>(&self, rhs: &Self, op: F) -> Option<Self> {
    match (self, rhs) {
      (Length::Value(a), Length::Value(b)) => a.try_op(b, op).map(Length::Value),
      _ => None,
    }
  }

  fn try_op_to<T, F: FnOnce(f32, f32) -> T>(&self, rhs: &Self, op: F) -> Option<T> {
    match (self, rhs) {
      (Length::Value(a), Length::Value(b)) => a.try_op_to(b, op),
      _ => None,
    }
  }
}

impl TryMap for Length {
  fn try_map<F: FnOnce(f32) -> f32>(&self, op: F) -> Option<Self> {
    match self {
      Length::Value(v) => v.try_map(op).map(Length::Value),
      _ => None,
    }
  }
}

impl TrySign for Length {
  fn try_sign(&self) -> Option<f32> {
    match self {
      Length::Value(v) => Some(v.sign()),
      Length::Calc(c) => c.try_sign(),
    }
  }
}

impl_try_from_angle!(Length);

/// Either a [`<length>`](https://www.w3.org/TR/css-values-4/#lengths) or a [`<number>`](https://www.w3.org/TR/css-values-4/#numbers).
#[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 LengthOrNumber {
  /// A number.
  Number(CSSNumber),
  /// A length.
  Length(Length),
}

impl Default for LengthOrNumber {
  fn default() -> LengthOrNumber {
    LengthOrNumber::Number(0.0)
  }
}

impl Zero for LengthOrNumber {
  fn zero() -> Self {
    LengthOrNumber::Number(0.0)
  }

  fn is_zero(&self) -> bool {
    match self {
      LengthOrNumber::Length(l) => l.is_zero(),
      LengthOrNumber::Number(v) => v.is_zero(),
    }
  }
}

impl IsCompatible for LengthOrNumber {
  fn is_compatible(&self, browsers: Browsers) -> bool {
    match self {
      LengthOrNumber::Length(l) => l.is_compatible(browsers),
      LengthOrNumber::Number(..) => true,
    }
  }
}
