///|
/// Describes an invalid message template and its character offset.
pub enum MessageParseError {
  UnexpectedEnd(Int, String)
  UnexpectedCharacter(Int, Char)
  ExpectedToken(Int, String)
  EmptyArgument(Int)
  UnknownArgumentType(Int, String)
  MissingOtherCase(Int)
  DuplicateCase(Int, String)
  InvalidOffset(Int, String)
  TrailingContent(Int)
} derive(Eq, Debug)

///|
pub fn MessageParseError::position(self : MessageParseError) -> Int {
  match self {
    UnexpectedEnd(position, _)
    | UnexpectedCharacter(position, _)
    | ExpectedToken(position, _)
    | EmptyArgument(position)
    | UnknownArgumentType(position, _)
    | MissingOtherCase(position)
    | DuplicateCase(position, _)
    | InvalidOffset(position, _)
    | TrailingContent(position) => position
  }
}

///|
pub fn MessageParseError::message(self : MessageParseError) -> String {
  match self {
    UnexpectedEnd(position, expected) =>
      "unexpected end at \{position}; expected \{expected}"
    UnexpectedCharacter(position, character) =>
      "unexpected character '\{character}' at \{position}"
    ExpectedToken(position, token) => "expected \{token} at \{position}"
    EmptyArgument(position) => "empty argument at \{position}"
    UnknownArgumentType(position, argument_type) =>
      "unknown argument type '\{argument_type}' at \{position}"
    MissingOtherCase(position) =>
      "select or plural argument at \{position} requires an other case"
    DuplicateCase(position, selector) =>
      "duplicate case '\{selector}' at \{position}"
    InvalidOffset(position, value) =>
      "invalid plural offset '\{value}' at \{position}"
    TrailingContent(position) => "unexpected trailing content at \{position}"
  }
}

///|
priv struct MessageParser {
  chars : Array[Char]
  mut index : Int
}

///|
fn MessageParser::new(source : String) -> MessageParser {
  let chars : Array[Char] = []
  for character in source {
    chars.push(character)
  }
  { chars, index: 0 }
}

///|
fn MessageParser::at_end(self : MessageParser) -> Bool {
  self.index >= self.chars.length()
}

///|
fn MessageParser::peek(self : MessageParser) -> Char? {
  if self.at_end() {
    None
  } else {
    Some(self.chars[self.index])
  }
}

///|
fn MessageParser::advance(self : MessageParser) -> Char? {
  if self.at_end() {
    None
  } else {
    let value = self.chars[self.index]
    self.index = self.index + 1
    Some(value)
  }
}

///|
fn MessageParser::skip_spaces(self : MessageParser) -> Unit {
  while !self.at_end() && self.chars[self.index].is_ascii_whitespace() {
    self.index = self.index + 1
  }
}

///|
fn message_is_name_char(character : Char) -> Bool {
  character.is_ascii_alphabetic() ||
  character.is_ascii_digit() ||
  character == '_' ||
  character == '-' ||
  character == '.'
}

///|
fn chars_to_string(chars : Array[Char]) -> String {
  let output = StringBuilder::new()
  for character in chars {
    output.write_char(character)
  }
  output.to_string()
}

///|
fn MessageParser::read_name(self : MessageParser) -> String {
  self.skip_spaces()
  let output : Array[Char] = []
  while !self.at_end() && message_is_name_char(self.chars[self.index]) {
    output.push(self.chars[self.index])
    self.index = self.index + 1
  }
  chars_to_string(output)
}

///|
fn MessageParser::expect(
  self : MessageParser,
  expected : Char,
) -> Result[Unit, MessageParseError] {
  self.skip_spaces()
  match self.advance() {
    Some(actual) =>
      if actual == expected {
        Ok(())
      } else {
        Err(UnexpectedCharacter(self.index - 1, actual))
      }
    None => Err(UnexpectedEnd(self.index, "'\{expected}'"))
  }
}

///|
fn append_text_node(nodes : Array[MessageNode], text : StringBuilder) -> Unit {
  let value = text.to_string()
  if value.length() > 0 {
    nodes.push(Text(value))
  }
}

///|
fn MessageParser::parse_quoted_text(
  self : MessageParser,
  text : StringBuilder,
) -> Result[Unit, MessageParseError] {
  let quote_position = self.index
  self.index = self.index + 1
  if !self.at_end() && self.chars[self.index] == '\'' {
    text.write_char('\'')
    self.index = self.index + 1
    return Ok(())
  }
  while !self.at_end() {
    let character = self.chars[self.index]
    self.index = self.index + 1
    if character == '\'' {
      if !self.at_end() && self.chars[self.index] == '\'' {
        text.write_char('\'')
        self.index = self.index + 1
      } else {
        return Ok(())
      }
    } else {
      text.write_char(character)
    }
  }
  Err(UnexpectedEnd(quote_position, "closing apostrophe"))
}

///|
fn MessageParser::parse_sequence(
  self : MessageParser,
  nested : Bool,
) -> Result[Array[MessageNode], MessageParseError] {
  let nodes : Array[MessageNode] = []
  let mut text = StringBuilder::new()
  while !self.at_end() {
    let character = self.chars[self.index]
    if character == '{' {
      append_text_node(nodes, text)
      text = StringBuilder::new()
      match self.parse_argument() {
        Ok(node) => nodes.push(node)
        Err(error) => return Err(error)
      }
    } else if character == '}' {
      if nested {
        append_text_node(nodes, text)
        return Ok(nodes)
      }
      return Err(UnexpectedCharacter(self.index, character))
    } else if character == '\'' {
      match self.parse_quoted_text(text) {
        Ok(_) => ()
        Err(error) => return Err(error)
      }
    } else {
      text.write_char(character)
      self.index = self.index + 1
    }
  }
  if nested {
    Err(UnexpectedEnd(self.index, "'}'"))
  } else {
    append_text_node(nodes, text)
    Ok(nodes)
  }
}

///|
fn MessageParser::parse_argument(
  self : MessageParser,
) -> Result[MessageNode, MessageParseError] {
  let opening_position = self.index
  self.index = self.index + 1
  let name = self.read_name()
  if name.length() == 0 {
    return Err(EmptyArgument(opening_position))
  }
  self.skip_spaces()
  match self.peek() {
    Some('}') => {
      self.index = self.index + 1
      Ok(Argument(name))
    }
    Some(',') => {
      self.index = self.index + 1
      let type_position = self.index
      let argument_type = self.read_name()
      if argument_type.length() == 0 {
        return Err(ExpectedToken(type_position, "argument type"))
      }
      match argument_type {
        "select" => self.parse_select_argument(name, opening_position)
        "plural" => self.parse_plural_argument(name, Cardinal, opening_position)
        "selectordinal" =>
          self.parse_plural_argument(name, Ordinal, opening_position)
        _ => Err(UnknownArgumentType(type_position, argument_type))
      }
    }
    Some(character) => Err(UnexpectedCharacter(self.index, character))
    None => Err(UnexpectedEnd(self.index, "',' or '}'"))
  }
}

///|
fn MessageParser::parse_select_argument(
  self : MessageParser,
  name : String,
  opening_position : Int,
) -> Result[MessageNode, MessageParseError] {
  match self.expect(',') {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  match self.parse_cases(false, false) {
    Ok((_, cases)) => {
      match self.expect('}') {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      if !has_message_case(cases, "other") {
        return Err(MissingOtherCase(opening_position))
      }
      Ok(Select(name, cases))
    }
    Err(error) => Err(error)
  }
}

///|
fn MessageParser::parse_plural_argument(
  self : MessageParser,
  name : String,
  kind : PluralKind,
  opening_position : Int,
) -> Result[MessageNode, MessageParseError] {
  match self.expect(',') {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  match self.parse_cases(true, true) {
    Ok((offset, cases)) => {
      match self.expect('}') {
        Err(error) => return Err(error)
        Ok(_) => ()
      }
      if !has_message_case(cases, "other") {
        return Err(MissingOtherCase(opening_position))
      }
      Ok(Plural(name, offset, kind, cases))
    }
    Err(error) => Err(error)
  }
}

///|
fn MessageParser::read_selector(
  self : MessageParser,
  allow_exact : Bool,
) -> String {
  self.skip_spaces()
  let output : Array[Char] = []
  if allow_exact && self.peek() == Some('=') {
    output.push('=')
    self.index = self.index + 1
  }
  while !self.at_end() && message_is_name_char(self.chars[self.index]) {
    output.push(self.chars[self.index])
    self.index = self.index + 1
  }
  chars_to_string(output)
}

///|
fn MessageParser::read_nonnegative_int(
  self : MessageParser,
) -> Result[Int, MessageParseError] {
  let start = self.index
  let mut value = 0
  let mut found = false
  while !self.at_end() && self.chars[self.index].is_ascii_digit() {
    found = true
    value = value * 10 + self.chars[self.index].to_int() - '0'.to_int()
    self.index = self.index + 1
  }
  if found {
    Ok(value)
  } else {
    Err(InvalidOffset(start, ""))
  }
}

///|
fn MessageParser::try_read_offset(
  self : MessageParser,
) -> Result[Int, MessageParseError] {
  self.skip_spaces()
  let start = self.index
  let word = self.read_name()
  if word != "offset" {
    self.index = start
    return Ok(0)
  }
  match self.expect(':') {
    Err(error) => return Err(error)
    Ok(_) => ()
  }
  self.skip_spaces()
  self.read_nonnegative_int()
}

///|
fn MessageParser::parse_cases(
  self : MessageParser,
  allow_exact : Bool,
  allow_offset : Bool,
) -> Result[(Int, Array[MessageCase]), MessageParseError] {
  let mut offset = 0
  if allow_offset {
    match self.try_read_offset() {
      Ok(value) => offset = value
      Err(error) => return Err(error)
    }
  }
  let cases : Array[MessageCase] = []
  while true {
    self.skip_spaces()
    match self.peek() {
      Some('}') => break
      None => return Err(UnexpectedEnd(self.index, "case or '}'"))
      _ => ()
    }
    let selector_position = self.index
    let selector = self.read_selector(allow_exact)
    if selector.length() == 0 {
      return Err(ExpectedToken(selector_position, "case selector"))
    }
    if has_message_case(cases, selector) {
      return Err(DuplicateCase(selector_position, selector))
    }
    match self.expect('{') {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
    match self.parse_sequence(true) {
      Err(error) => return Err(error)
      Ok(nodes) => {
        match self.expect('}') {
          Err(error) => return Err(error)
          Ok(_) => ()
        }
        cases.push({ selector, nodes })
      }
    }
  }
  Ok((offset, cases))
}

///|
fn has_message_case(cases : Array[MessageCase], selector : String) -> Bool {
  for branch in cases {
    if branch.selector == selector {
      return true
    }
  }
  false
}

///|
/// Parses an ICU-inspired message template.
///
/// Supported arguments are simple variables, `select`, `plural`, and
/// `selectordinal`. Plural branches accept exact selectors such as `=0` and
/// optional non-negative offsets. Apostrophes quote syntax characters.
pub fn MessageTemplate::parse(
  source : String,
) -> Result[MessageTemplate, MessageParseError] {
  let parser = MessageParser::new(source)
  match parser.parse_sequence(false) {
    Ok(nodes) =>
      if parser.at_end() {
        Ok({ source, nodes })
      } else {
        Err(TrailingContent(parser.index))
      }
    Err(error) => Err(error)
  }
}