///|
/// Predicate over extracted events. Absent fields are ignored.
pub(all) struct FilterSpec {
  event_id : UInt?
  provider : String?
  channel : String?
  computer : String?
  level : Int?
  contains : String?
  since : UInt64?
  until : UInt64?
} derive(Debug)

///|
/// Empty filter that keeps every event.
pub fn empty_filter() -> FilterSpec {
  FilterSpec::{
    event_id: None,
    provider: None,
    channel: None,
    computer: None,
    level: None,
    contains: None,
    since: None,
    until: None,
  }
}

///|
pub fn text_contains(hay : String, needle : String) -> Bool {
  if needle.length() == 0 {
    return true
  }
  if needle.length() > hay.length() {
    return false
  }
  let last = hay.length() - needle.length()
  for i = 0; i <= last; i = i + 1 {
    let mut ok = true
    for j = 0; j < needle.length(); j = j + 1 {
      if hay[i + j] != needle[j] {
        ok = false
      }
    }
    if ok {
      return true
    }
  }
  false
}

///|
fn opt_eq_uint(got : UInt, want : UInt?) -> Bool {
  match want {
    None => true
    Some(v) => got == v
  }
}

///|
fn opt_eq_int(got : Int, want : Int?) -> Bool {
  match want {
    None => true
    Some(v) => got == v
  }
}

///|
fn opt_eq_str(got : String, want : String?) -> Bool {
  match want {
    None => true
    Some(v) => got == v
  }
}

///|
fn opt_since(ts : UInt64, want : UInt64?) -> Bool {
  match want {
    None => true
    Some(v) => ts >= v
  }
}

///|
fn opt_until(ts : UInt64, want : UInt64?) -> Bool {
  match want {
    None => true
    Some(v) => ts <= v
  }
}

///|
pub fn Event::matches(self : Event, spec : FilterSpec) -> Bool {
  let text_hit = match spec.contains {
    None => true
    Some(part) =>
      text_contains(self.message, part) ||
      text_contains(self.provider, part) ||
      text_contains(self.channel, part) ||
      text_contains(self.computer, part)
  }
  opt_eq_uint(self.event_id, spec.event_id) &&
  opt_eq_str(self.provider, spec.provider) &&
  opt_eq_str(self.channel, spec.channel) &&
  opt_eq_str(self.computer, spec.computer) &&
  opt_eq_int(self.level, spec.level) &&
  opt_since(self.timestamp, spec.since) &&
  opt_until(self.timestamp, spec.until) &&
  text_hit
}

///|
pub fn filter_events(events : Array[Event], spec : FilterSpec) -> Array[Event] {
  let out : Array[Event] = []
  for i = 0; i < events.length(); i = i + 1 {
    if events[i].matches(spec) {
      out.push(events[i])
    }
  }
  out
}

///|
pub fn EvtxFile::filter(
  self : EvtxFile,
  spec : FilterSpec,
  style : TimeStyle,
) -> Result[Array[Event], EvtxError] {
  Ok(
    filter_events(
      match self.events(style) {
        Ok(v) => v
        Err(e) => return Err(e)
      },
      spec,
    ),
  )
}

///|
pub extend FilterSpec with @moonbitlang/core/debug.Debug::{to_repr}