///|
/// Normalized argv tokens, before grammar matching.
pub enum ArgumentToken {
  OptionToken(String, Value)
  Positional(String)
} derive(Eq, @debug.Debug)

///|
pub extend ArgumentToken with Eq::{equal, not_equal}

///|
pub extend ArgumentToken with @debug.Debug::{to_repr}

///|
priv struct ArgvCursor {
  args : Array[String]
  mut position : Int32
}

///|
fn ArgvCursor::take(self : ArgvCursor) -> String? {
  if self.position >= self.args.length() {
    None
  } else {
    let token = self.args[self.position]
    self.position += 1
    Some(token)
  }
}

///|
fn ArgvCursor::option_value(
  self : ArgvCursor,
  key : String,
) -> String raise DocoptError {
  match self.take() {
    Some(v) if v != "--" => v
    _ => raise Arguments(key + " requires an argument")
  }
}

///|
fn long_argument(
  token : String,
  cursor : ArgvCursor,
  specs : Array[OptionSpec],
) -> ArgumentToken raise DocoptError {
  let eq = token.find("=")
  let key = token[:eq.unwrap_or(token.length())].to_owned()
  let exact = specs.filter(fn(s) { s.long == Some(key) })
  let candidates = if exact.length() > 0 {
    exact
  } else {
    specs.filter(fn(s) {
      match s.long {
        Some(n) => n.has_prefix(key)
        None => false
      }
    })
  }
  guard candidates.length() <= 1 else {
    raise Arguments("ambiguous option " + key)
  }
  let spec = if candidates.length() == 1 {
    candidates[0]
  } else {
    let unknown = {
      short: None,
      long: Some(key),
      takes_value: eq != None,
      default_value: None,
    }
    specs.push(unknown)
    unknown
  }
  let value = if spec.takes_value {
    Text(
      match eq {
        Some(i) => token[i + 1:].to_owned()
        None => cursor.option_value(key)
      },
    )
  } else {
    guard eq == None else {
      raise Arguments(key + " must not have an argument")
    }
    Flag(true)
  }
  OptionToken(spec.key(), value)
}

///|
/// Normalization is separate from matching for diagnostics and embedding.
/// argv excludes the executable name. A literal -- is retained, as in docopt.
pub fn Parser::normalize(
  self : Parser,
  argv : Array[String],
  options_first~ : Bool = false,
) -> Array[ArgumentToken] raise DocoptError {
  guard argv.length() <= self.limits.argv_count else {
    raise Limit("too many argv items")
  }
  let mut remaining = self.limits.argv_units
  for arg in argv {
    guard arg.length() <= remaining else { raise Limit("argv text too large") }
    remaining -= arg.length()
  }
  let cursor = { args: argv.copy(), position: 0, }
  let specs = self.specs.copy()
  let out = []
  let mut positional_only = false
  while cursor.take() is Some(token) {
    if positional_only {
      out.push(Positional(token))
    } else if token == "--" {
      positional_only = true
      out.push(Positional(token))
    } else if token.has_prefix("--") {
      out.push(long_argument(token, cursor, specs))
    } else if token.has_prefix("-") && token != "-" {
      for item in short_arguments(token, cursor, specs) {
        out.push(item)
      }
    } else {
      out.push(Positional(token))
      if options_first {
        positional_only = true
      }
    }
  }
  out
}

///|
/// A value-taking short option consumes the rest of its cluster, or one argv.
fn short_arguments(
  token : String,
  cursor : ArgvCursor,
  specs : Array[OptionSpec],
) -> Array[ArgumentToken] raise DocoptError {
  let out = []
  let mut i = 1
  while i < token.length() {
    let key = "-" + token[i:i + 1].to_owned()
    i += 1
    let spec = match find_spec(specs, key) {
      Some(n) => specs[n]
      None => {
        let unknown = {
          short: Some(key),
          long: None,
          takes_value: false,
          default_value: None,
        }
        specs.push(unknown)
        unknown
      }
    }
    if spec.takes_value {
      let value = if i < token.length() {
        token[i:].to_owned()
      } else {
        cursor.option_value(key)
      }
      out.push(OptionToken(spec.key(), Text(value)))
      break
    } else {
      out.push(OptionToken(spec.key(), Flag(true)))
    }
  }
  out
}