///|
/// Turning a token slice into values, and the `An+B` micro-syntax.
///
/// Both are needed in two places -- inside a pseudo-class's arguments and
/// inside an at-rule's prelude -- which is why they read a slice rather than
/// the main cursor.

///|
/// A token slice as component values, dropping trivia.
///
/// This is the fallback path: it is what an unknown at-rule's prelude and an
/// unknown pseudo-class's arguments become. Nothing is interpreted, so nothing
/// can be interpreted wrongly, and the printer can put back what it was given.
fn Parser::values_of(
  self : Parser,
  toks : ArrayView[@token.Token],
) -> Array[@ast.ComponentValue] {
  ignore(self)
  values_of_view(toks)
}

///|
fn values_of_view(toks : ArrayView[@token.Token]) -> Array[@ast.ComponentValue] {
  let c = { toks, pos: 0, }
  let out = c.values_until(Eof)
  out
}

///|
priv struct ValCursor {
  toks : ArrayView[@token.Token]
  mut pos : Int
}

///|
fn ValCursor::at_end(self : ValCursor) -> Bool {
  self.pos >= self.toks.length()
}

///|
fn ValCursor::values_until(
  self : ValCursor,
  closer : @token.TokenKind,
) -> Array[@ast.ComponentValue] {
  let out : Array[@ast.ComponentValue] = []
  while !self.at_end() {
    let t = self.toks[self.pos]
    if t.kind == closer {
      self.pos = self.pos + 1
      trim_trailing(out)
      return out
    }
    if t.is_trivia() {
      self.pos = self.pos + 1
      continue
    }
    self.pos = self.pos + 1
    let v : @ast.ComponentValue = match t.kind {
      Ident(s) => Ident(s)
      Str(s) => Str(s)
      Url(u) => Url(u)
      Number(r, v, i) => Num({ repr: r, value: v, is_int: i, })
      Percentage(r, v) => Percentage({ repr: r, value: v, is_int: false, })
      Dimension(r, v, i, u) => Dimension({ repr: r, value: v, is_int: i, }, u)
      Hash(d, _) => Hex(d)
      Comma => Comma
      Colon => Delim(":")
      Semicolon => Delim(";")
      Delim('/') => Slash
      Delim(c) => Delim(c.to_string())
      Function(name) => {
        let args = self.values_until(RParen)
        if name.to_lower() == "url" && args.length() == 1 {
          match args[0] {
            Str(s) => Url(s)
            _ => Function(name, args)
          }
        } else {
          Function(name, args)
        }
      }
      LParen => Paren(self.values_until(RParen))
      LBracket => Bracket(self.values_until(RBracket))
      BadStr => Bogus(@ast.Bogus::new(UnterminatedString, t.span, text=""))
      BadUrl => Bogus(@ast.Bogus::new(BadUrl, t.span, text=""))
      _ => Bogus(@ast.Bogus::new(Unexpected("a value"), t.span, text=""))
    }
    out.push(v)
  }
  trim_trailing(out)
  out
}

// ------------------------------------------------------------------- An+B

///|
/// `An+B`, and the optional `of ` after it.
///
/// The shapes are awkward because the tokenizer, correctly, does not know this
/// syntax exists: `2n+1` arrives as a dimension with unit `n` followed by the
/// number `+1`, while `2n - 1` arrives as a dimension, a delimiter and a
/// number. Both mean the same thing, so both are listed.
fn SelCursor::parse_nth(
  self : SelCursor,
  name : String,
  toks : ArrayView[@token.Token],
  from : Int,
  start : Int,
) -> @ast.Qualifier raise @err.CssError {
  // Split off an `of` clause first, so the formula sees only its own tokens.
  let mut of_at = -1
  let mut depth = 0
  for i, t in toks {
    let k = t.kind
    if k.is_opener() {
      depth = depth + 1
    } else if k == RParen || k == RBracket {
      if depth > 0 {
        depth = depth - 1
      }
    } else if depth == 0 {
      match k {
        Ident(w) => if w.to_lower() == "of" && of_at < 0 { of_at = i }
        _ => ()
      }
    }
  }
  let (formula, of_toks) = if of_at >= 0 {
    (toks[0:of_at], Some(toks[of_at + 1:]))
  } else {
    (toks, None)
  }
  match parse_anb(formula) {
    Some(anb) => {
      let of_ = match of_toks {
        Some(t) => Some(self.owner.parse_selector_list(t, start))
        None => None
      }
      Pseudo(Nth(name, anb, of_))
    }
    None => {
      let at = self.here()
      let _ = self.owner.bogus(BadAnB, from, at)
      Pseudo(Unknown(name, values_of_view(toks)))
    }
  }
}

///|
/// The formula itself.
fn parse_anb(toks : ArrayView[@token.Token]) -> @ast.AnB? {
  // Meaningful tokens only; whitespace never changes what a formula means.
  let ts : Array[@token.TokenKind] = []
  for t in toks {
    if !t.is_trivia() {
      ts.push(t.kind)
    }
  }
  match ts {
    [Ident(w)] => {
      let l = w.to_lower()
      if l == "odd" {
        Some({ a: 2, b: 1, })
      } else if l == "even" {
        Some({ a: 2, b: 0, })
      } else {
        // A bare `n`, `-n`, or `+n` -- or `n-1` and `-n-1`, which are single
        // identifiers because `-` and digits are identifier characters.
        match n_coefficient(l) {
          Some(a) => Some({ a, b: 0, })
          None =>
            match ndash_ident(l) {
              Some(pair) => Some({ a: pair.0, b: -pair.1, })
              None => None
            }
        }
      }
    }
    [Number(_, v, true)] => Some({ a: 0, b: v.to_int(), })
    [Dimension(_, v, true, u)] => {
      let unit = u.to_lower()
      if unit == "n" {
        Some({ a: v.to_int(), b: 0, })
      } else {
        // `2n-1` is ONE dimension token whose unit is `n-1`: `-` and digits
        // are identifier characters, so the tokenizer takes them, and it is
        // right to. The An+B grammar calls this an  and
        // re-splits it here, which is the only place that can.
        match ndashdigit(unit) {
          Some(b) => Some({ a: v.to_int(), b: -b, })
          None => None
        }
      }
    }
    // `2n+1`, `2n-1` when the sign fused with the second number.
    [Dimension(_, v, true, u), Number(r, b, true)] =>
      if u.to_lower() == "n" && has_sign(r) {
        Some({ a: v.to_int(), b: b.to_int(), })
      } else {
        None
      }
    // `n+1`, `-n+3`.
    [Ident(w), Number(r, b, true)] =>
      match n_coefficient(w.to_lower()) {
        Some(a) if has_sign(r) => Some({ a, b: b.to_int(), })
        _ => None
      }
    // `2n - 1`, with the sign as its own delimiter.
    [Dimension(_, v, true, u), Delim(s), Number(_, b, true)] =>
      if u.to_lower() == "n" && (s == '+' || s == '-') {
        let bb = if s == '-' { -b.to_int() } else { b.to_int() }
        Some({ a: v.to_int(), b: bb, })
      } else {
        None
      }
    [Ident(w), Delim(s), Number(_, b, true)] =>
      match n_coefficient(w.to_lower()) {
        Some(a) if s == '+' || s == '-' => {
          let bb = if s == '-' { -b.to_int() } else { b.to_int() }
          Some({ a, b: bb, })
        }
        _ => None
      }
    _ => None
  }
}

///|
/// The digits of an `n-` unit, if that is what this is.
fn ndashdigit(unit : String) -> Int? {
  if !unit.has_prefix("n-") {
    return None
  }
  digits_of(unit.clamped_view(start=2).to_owned())
}

///|
/// `n-1` and `-n-1` as a coefficient and its digits.
fn ndash_ident(w : String) -> (Int, Int)? {
  if w.has_prefix("-n-") {
    match digits_of(w.clamped_view(start=3).to_owned()) {
      Some(d) => Some((-1, d))
      None => None
    }
  } else if w.has_prefix("n-") {
    match digits_of(w.clamped_view(start=2).to_owned()) {
      Some(d) => Some((1, d))
      None => None
    }
  } else {
    None
  }
}

///|
/// A run of decimal digits, and nothing else.
fn digits_of(s : String) -> Int? {
  if s.length() == 0 {
    return None
  }
  let mut n = 0
  for c in s {
    if c < '0' || c > '9' {
      return None
    }
    n = n * 10 + (c.to_int() - 48)
  }
  Some(n)
}

///|
/// `n` is 1, `-n` is -1, `+n` is 1; anything else is not a coefficient.
fn n_coefficient(w : String) -> Int? {
  if w == "n" {
    Some(1)
  } else if w == "-n" {
    Some(-1)
  } else if w == "+n" {
    Some(1)
  } else {
    None
  }
}

///|
/// Whether a number's source spelling carried an explicit sign.
///
/// It has to, in `2n+1`: without a sign the `1` is a separate value and the
/// formula is malformed, which is exactly the distinction the tokenizer keeps
/// `repr` around for.
fn has_sign(repr : String) -> Bool {
  repr.has_prefix("+") || repr.has_prefix("-")
}