///|
/// MoonEvent is a small event bus and message abstraction layer.
pub(all) enum DispatchControl {
  Continue
  Stop
} derive(Eq, Debug)

///|
pub(all) enum HandlerStatus {
  Handled
  Ignored
  Failed(String)
} derive(Eq, Debug)

///|
pub(all) struct Event {
  name : String
  topic : String
  payload : String
  tags : Array[String]
  priority : Int
  correlation_id : String
} derive(Eq, Debug)

///|
pub(all) struct HandlerReport {
  handler : String
  status : HandlerStatus
  note : String
} derive(Eq, Debug)

///|
pub(all) struct DispatchReport {
  event : Event
  delivered : Int
  ignored : Int
  failed : Int
  stopped : Bool
  reports : Array[HandlerReport]
} derive(Eq, Debug)

///|
pub(all) struct Subscription {
  id : Int
  name : String
  topic : String
  tag : String
  min_priority : Int
  once : Bool
  active : Bool
} derive(Eq, Debug)

///|
pub fn Event::new(
  name : String,
  topic? : String = "",
  payload? : String = "",
  tags? : Array[String] = [],
  priority? : Int = 0,
  correlation_id? : String = "",
) -> Event {
  { name, topic, payload, tags, priority, correlation_id }
}

///|
pub fn Subscription::new(
  id : Int,
  name : String,
  topic? : String = "",
  tag? : String = "",
  min_priority? : Int = 0,
  once? : Bool = false,
) -> Subscription {
  { id, name, topic, tag, min_priority, once, active: true }
}

///|
pub fn Event::has_tag(self : Event, tag : String) -> Bool {
  self.tags.contains(tag)
}

///|
pub fn Event::matches_topic(self : Event, topic : String) -> Bool {
  topic_matches(topic, self.topic)
}

///|
pub fn Event::summary(self : Event) -> String {
  self.name + "@" + self.topic + "#" + self.correlation_id
}

///|
pub fn Subscription::matches(self : Subscription, event : Event) -> Bool {
  if !self.active {
    false
  } else if !topic_matches(self.topic, event.topic) {
    false
  } else if self.tag != "" && !event.has_tag(self.tag) {
    false
  } else {
    event.priority >= self.min_priority
  }
}

///|
pub fn HandlerReport::ok(
  handler : String,
  note? : String = "",
) -> HandlerReport {
  { handler, status: Handled, note }
}

///|
pub fn HandlerReport::ignored(
  handler : String,
  note? : String = "",
) -> HandlerReport {
  { handler, status: Ignored, note }
}

///|
pub fn HandlerReport::failed(
  handler : String,
  reason : String,
) -> HandlerReport {
  { handler, status: Failed(reason), note: reason }
}

///|
fn count_failed(reports : Array[HandlerReport]) -> Int {
  let mut total = 0
  for report in reports {
    match report.status {
      Failed(_) => total = total + 1
      _ => ()
    }
  }
  total
}

///|
fn count_ignored(reports : Array[HandlerReport]) -> Int {
  let mut total = 0
  for report in reports {
    match report.status {
      Ignored => total = total + 1
      _ => ()
    }
  }
  total
}

///|
pub fn DispatchReport::from_reports(
  event : Event,
  reports : Array[HandlerReport],
  stopped? : Bool = false,
) -> DispatchReport {
  let failed = count_failed(reports)
  let ignored = count_ignored(reports)
  {
    event,
    delivered: reports.length() - failed - ignored,
    ignored,
    failed,
    stopped,
    reports,
  }
}

///|
pub fn DispatchReport::is_ok(self : DispatchReport) -> Bool {
  self.failed == 0
}

///|
pub fn dispatch(
  event : Event,
  subscriptions : Array[Subscription],
) -> DispatchReport {
  let reports : Array[HandlerReport] = []
  let mut stopped = false
  for sub in subscriptions {
    if stopped {
      ()
    } else if sub.matches(event) {
      reports.push(HandlerReport::ok(sub.name, note="matched"))
      if sub.once {
        stopped = true
      }
    } else {
      reports.push(HandlerReport::ignored(sub.name, note="filtered"))
    }
  }
  DispatchReport::from_reports(event, reports, stopped~)
}

///|
pub fn publish_name(
  name : String,
  subscriptions : Array[Subscription],
) -> DispatchReport {
  dispatch(Event::new(name), subscriptions)
}

///|
pub fn filter_by_topic(
  subscriptions : Array[Subscription],
  topic : String,
) -> Array[Subscription] {
  let out : Array[Subscription] = []
  for sub in subscriptions {
    if sub.topic == topic {
      out.push(sub)
    }
  }
  out
}

///|
pub fn active_count(subscriptions : Array[Subscription]) -> Int {
  let mut total = 0
  for sub in subscriptions {
    if sub.active {
      total = total + 1
    }
  }
  total
}