///|
pub(all) enum Pattern {
  Ubiquitous
  StateDriven
  EventDriven
  OptionalFeature
  UnwantedBehaviour
  Complex
} derive(Eq, Debug)

///|
pub(all) enum Trigger {
  Event(String)
  Unwanted(String)
} derive(Eq, Debug)

///|
/// A validated EARS sentence. Only the parser can construct a requirement.
/// Free-text clauses retain their case and punctuation, with whitespace folded.
struct Requirement {
  feature : String?
  preconditions : ReadOnlyArray[String]
  trigger : Trigger?
  system : String
  response : String
} derive(Eq, Debug)

///|
pub fn Requirement::feature(self : Requirement) -> String? {
  self.feature
}

///|
pub fn Requirement::preconditions(self : Requirement) -> ReadOnlyArray[String] {
  self.preconditions
}

///|
pub fn Requirement::trigger(self : Requirement) -> Trigger? {
  self.trigger
}

///|
pub fn Requirement::system(self : Requirement) -> String {
  self.system
}

///|
pub fn Requirement::response(self : Requirement) -> String {
  self.response
}

///|
pub fn Requirement::pattern(self : Requirement) -> Pattern {
  let count = (if self.feature is Some(_) { 1 } else { 0 }) +
    self.preconditions.length() +
    (if self.trigger is Some(_) { 1 } else { 0 })
  if count > 1 {
    return Complex
  }
  if self.feature is Some(_) {
    return OptionalFeature
  }
  if !self.preconditions.is_empty() {
    return StateDriven
  }
  match self.trigger {
    Some(Event(_)) => EventDriven
    Some(Unwanted(_)) => UnwantedBehaviour
    None => Ubiquitous
  }
}

///|
/// Render canonical keyword case and spacing. Response punctuation is retained.
pub fn Requirement::to_string(self : Requirement) -> String {
  let clauses = []
  if self.feature is Some(feature) {
    clauses.push("Where " + feature)
  }
  for state in self.preconditions {
    clauses.push("While " + state)
  }
  match self.trigger {
    Some(Event(event)) => clauses.push("When " + event)
    Some(Unwanted(event)) => clauses.push("If " + event)
    None => ()
  }
  let system = if self.trigger is Some(Unwanted(_)) {
    "then the "
  } else if clauses.is_empty() {
    "The "
  } else {
    "the "
  }
  clauses.push(system + self.system + " shall " + self.response)
  clauses.join(", ")
}

///|
pub(all) suberror ParseError {
  EmptyRequirement
  ExpectedSystem
  MissingShall
  MissingResponse
  EmptyClause(keyword~ : String)
  MissingSeparator
  MissingThen
  InvalidClauseOrder
} derive(Eq, Debug)

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

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

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

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

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

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

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

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