// Matchers for strings.

///|
/// Assert a String equals `expected` when case is ignored.
#callsite(autofill(loc))
pub fn Expectation::to_equal_ignoring_case(
  self : Expectation[String],
  expected : String,
  loc~ : SourceLoc,
) -> Unit raise Error {
  self.assert_that(
    self.actual.to_lower() == expected.to_lower(),
    "to_equal_ignoring_case",
    args="expected",
    expected=() => "\{show(expected)}, ignoring case",
    received=() => show(self.actual),
    loc~,
  )
}

///|
/// The text without whitespace characters.
fn remove_whitespace(text : String) -> String {
  String::from_iter(text.iter().filter(c => !c.is_whitespace()))
}

///|
/// Assert a String equals `expected` when all whitespace is removed from
/// both. Whitespace is every character for which `Char::is_whitespace` is
/// true, such as spaces, tabs and newlines.
#callsite(autofill(loc))
pub fn Expectation::to_equal_ignoring_whitespace(
  self : Expectation[String],
  expected : String,
  loc~ : SourceLoc,
) -> Unit raise Error {
  self.assert_that(
    remove_whitespace(self.actual) == remove_whitespace(expected),
    "to_equal_ignoring_whitespace",
    args="expected",
    expected=() => "\{show(expected)}, ignoring whitespace",
    received=() => show(self.actual),
    loc~,
  )
}

///|
/// Assert a String is empty or holds only whitespace.
#callsite(autofill(loc))
pub fn Expectation::to_be_blank(
  self : Expectation[String],
  loc~ : SourceLoc,
) -> Unit raise Error {
  self.assert_that(
    self.actual.iter().all(c => c.is_whitespace()),
    "to_be_blank",
    expected=() => "empty or only whitespace",
    received=() => show(self.actual),
    loc~,
  )
}

///|
/// Assert a String contains `text` exactly `count` times. Occurrences do
/// not overlap: `"aaaa"` contains `"aa"` twice.
#callsite(autofill(loc))
pub fn Expectation::to_contain_times(
  self : Expectation[String],
  text : String,
  count : Int,
  loc~ : SourceLoc,
) -> Unit raise Error {
  if text.is_empty() {
    self.report(
      "to_contain_times",
      "text, count",
      [("Error", "the text to count must not be empty")],
      loc,
    )
  }
  let mut actual_count = 0
  let mut rest = self.actual[:]
  while rest.find(text) is Some(index) {
    actual_count += 1
    rest = rest.view(start_offset=index + text.length())
  }
  self.assert_that(
    actual_count == count,
    "to_contain_times",
    args="text, count",
    expected=() => "containing \{show(text)} \{count} times",
    received=() => show(self.actual),
    details=() => [("Count", actual_count.to_string())],
    loc~,
  )
}

///|
/// Assert a String contains the given parts in this order, with other text
/// allowed between them.
#callsite(autofill(loc))
pub fn Expectation::to_contain_substrings_in_order(
  self : Expectation[String],
  parts : Array[String],
  loc~ : SourceLoc,
) -> Unit raise Error {
  // The first part that is not found, and the part before it.
  let mut missing : (String, String?)? = None
  let mut rest = self.actual[:]
  let mut previous : String? = None
  for part in parts {
    match rest.find(part) {
      Some(index) => {
        rest = rest.view(start_offset=index + part.length())
        previous = Some(part)
      }
      None => {
        missing = Some((part, previous))
        break
      }
    }
  }
  self.assert_that(
    missing is None,
    "to_contain_substrings_in_order",
    args="parts",
    expected=() => "containing \{show(parts)} in this order",
    received=() => show(self.actual),
    details=() => {
      match missing {
        Some((part, Some(before))) if !self.negated =>
          [("Missing", "\{show(part)}, after \{show(before)}")]
        Some((part, None)) if !self.negated => [("Missing", show(part))]
        _ => []
      }
    },
    loc~,
  )
}

///|
/// Assert the whole String matches a regular expression. Unlike `to_match`,
/// the match must start at the start of the string and end at its end.
///
/// The pattern uses the syntax of `@string.Regex`. An invalid pattern fails
/// the assertion.
#callsite(autofill(loc))
pub fn Expectation::to_match_fully(
  self : Expectation[String],
  pattern : String,
  loc~ : SourceLoc,
) -> Unit raise Error {
  let regex = @string.Regex("^(\{pattern})$") catch {
    error =>
      self.report(
        "to_match_fully",
        "pattern",
        [
          ("Error", "invalid regex pattern \{show(pattern)}"),
          ("Cause", error.to_string()),
        ],
        loc,
      )
  }
  self.assert_that(
    regex.execute(self.actual) is Some(_),
    "to_match_fully",
    args="pattern",
    expected=() => "matching \{show(pattern)} in full",
    received=() => show(self.actual),
    loc~,
  )
}