///|
/// Result returned while validating a topic pattern.
pub(all) enum TopicPatternStatus {
  PatternValid
  PatternEmptySegment(Int)
  PatternHashNotLast(Int)
  PatternInvalidWildcard(Int, String)
} derive(Eq, Debug)

///|
/// Compiled dot-separated topic pattern.
///
/// `*` consumes exactly one segment and `#` consumes remaining segments.
/// An empty pattern matches every topic for backwards compatibility.
pub(all) struct TopicPattern {
  raw : String
  segments : Array[String]
  status : TopicPatternStatus
} derive(Eq, Debug)

///|
fn owned_segments(value : String) -> Array[String] {
  value.split(".").map(part => part.to_owned()).collect()
}

///|
fn validate_pattern_segments(segments : Array[String]) -> TopicPatternStatus {
  let mut index = 0
  for segment in segments {
    if segment == "" {
      return PatternEmptySegment(index)
    }
    if segment == "#" && index != segments.length() - 1 {
      return PatternHashNotLast(index)
    }
    if segment != "*" &&
      segment != "#" &&
      (segment.contains("*") || segment.contains("#")) {
      return PatternInvalidWildcard(index, segment)
    }
    index = index + 1
  }
  PatternValid
}

///|
pub fn TopicPattern::compile(raw : String) -> TopicPattern {
  if raw == "" {
    { raw, segments: [], status: PatternValid }
  } else {
    let segments = owned_segments(raw)
    { raw, segments, status: validate_pattern_segments(segments) }
  }
}

///|
pub fn TopicPattern::is_valid(self : TopicPattern) -> Bool {
  self.status == PatternValid
}

///|
pub fn TopicPattern::is_catch_all(self : TopicPattern) -> Bool {
  self.raw == "" || self.raw == "#"
}

///|
pub fn TopicPattern::matches(self : TopicPattern, topic : String) -> Bool {
  if !self.is_valid() {
    return false
  }
  if self.raw == "" || self.raw == "#" {
    return true
  }
  let topic_segments = owned_segments(topic)
  let mut index = 0
  while index < self.segments.length() {
    let expected = self.segments[index]
    if expected == "#" {
      return true
    }
    if index >= topic_segments.length() {
      return false
    }
    if expected != "*" && expected != topic_segments[index] {
      return false
    }
    index = index + 1
  }
  index == topic_segments.length()
}

///|
/// Higher specificity means fewer wildcard segments.
pub fn TopicPattern::specificity(self : TopicPattern) -> Int {
  if !self.is_valid() {
    return -1
  }
  let mut score = 0
  for segment in self.segments {
    if segment == "*" {
      score = score + 1
    } else if segment != "#" {
      score = score + 4
    }
  }
  score
}

///|
pub fn topic_matches(pattern : String, topic : String) -> Bool {
  TopicPattern::compile(pattern).matches(topic)
}

///|
pub fn validate_topic_pattern(pattern : String) -> TopicPatternStatus {
  TopicPattern::compile(pattern).status
}