///|
/// Timestamped events for coordinating motion, audio, and render callbacks.
pub enum MotionEventKind2 {
  MarkerEvent
  TriggerEvent
  LoopEvent
  EnterEvent
  ExitEvent
} derive(Debug)

///|
pub fn motion_event_kinds2() -> Array[MotionEventKind2] {
  [MarkerEvent, TriggerEvent, LoopEvent, EnterEvent, ExitEvent]
}

///|
pub struct MotionEvent2 {
  time : Double
  id : Int
  label : String
  kind : MotionEventKind2
  payload : Double
  consumed : Bool
} derive(Debug)

///|
pub fn motion_event2(
  time : Double,
  id : Int,
  label : String,
  kind : MotionEventKind2,
  payload? : Double = 0.0,
) -> MotionEvent2 {
  { time, id, label, kind, payload, consumed: false }
}

///|
pub fn MotionEvent2::time(self : MotionEvent2) -> Double {
  self.time
}

///|
pub fn MotionEvent2::id(self : MotionEvent2) -> Int {
  self.id
}

///|
pub fn MotionEvent2::label(self : MotionEvent2) -> String {
  self.label
}

///|
pub fn MotionEvent2::kind(self : MotionEvent2) -> MotionEventKind2 {
  self.kind
}

///|
pub fn MotionEvent2::payload(self : MotionEvent2) -> Double {
  self.payload
}

///|
pub fn MotionEvent2::consumed(self : MotionEvent2) -> Bool {
  self.consumed
}

///|
pub fn MotionEvent2::mark_consumed(self : MotionEvent2) -> MotionEvent2 {
  {
    time: self.time,
    id: self.id,
    label: self.label,
    kind: self.kind,
    payload: self.payload,
    consumed: true,
  }
}

///|
pub struct MotionEventQueue2 {
  events : Array[MotionEvent2]
  mut cursor : Int
  mut last_time : Double
} derive(Debug)

///|
pub fn motion_event_queue2() -> MotionEventQueue2 {
  { events: [], cursor: 0, last_time: -1000000000.0 }
}

///|
pub fn MotionEventQueue2::length(self : MotionEventQueue2) -> Int {
  self.events.length()
}

///|
pub fn MotionEventQueue2::events(
  self : MotionEventQueue2,
) -> Array[MotionEvent2] {
  self.events.copy()
}

///|
pub fn MotionEventQueue2::cursor(self : MotionEventQueue2) -> Int {
  self.cursor
}

///|
pub fn MotionEventQueue2::push(
  self : MotionEventQueue2,
  event : MotionEvent2,
) -> Unit {
  self.events.push(event)
  self.events.sort_by(fn(left, right) {
    let time_order = left.time.compare(right.time)
    if time_order == 0 {
      left.id.compare(right.id)
    } else {
      time_order
    }
  })
}

///|
pub fn MotionEventQueue2::push_many(
  self : MotionEventQueue2,
  events : Array[MotionEvent2],
) -> Unit {
  for event in events {
    self.push(event)
  }
}

///|
pub fn MotionEventQueue2::peek(self : MotionEventQueue2) -> MotionEvent2? {
  if self.cursor < self.events.length() {
    Some(self.events[self.cursor])
  } else {
    None
  }
}

///|
pub fn MotionEventQueue2::poll_until(
  self : MotionEventQueue2,
  time : Double,
) -> Array[MotionEvent2] {
  let result : Array[MotionEvent2] = []
  while self.cursor < self.events.length() &&
        self.events[self.cursor].time <= time {
    result.push(self.events[self.cursor])
    self.cursor = self.cursor + 1
  }
  self.last_time = time
  result
}

///|
pub fn MotionEventQueue2::poll_window(
  self : MotionEventQueue2,
  start : Double,
  end : Double,
) -> Array[MotionEvent2] {
  let result : Array[MotionEvent2] = []
  for event in self.events {
    if event.time >= start && event.time <= end {
      result.push(event)
    }
  }
  result
}

///|
pub fn MotionEventQueue2::reset(
  self : MotionEventQueue2,
  time : Double,
) -> Unit {
  self.cursor = 0
  self.last_time = time
  while self.cursor < self.events.length() &&
        self.events[self.cursor].time < time {
    self.cursor = self.cursor + 1
  }
}

///|
pub fn MotionEventQueue2::clear(self : MotionEventQueue2) -> Unit {
  self.events.clear()
  self.cursor = 0
}

///|
pub struct EventWindow2 {
  start : Double
  end : Double
  events : Array[MotionEvent2]
  crossed_forward : Bool
} derive(Debug)

///|
pub fn EventWindow2::start(self : EventWindow2) -> Double {
  self.start
}

///|
pub fn EventWindow2::end(self : EventWindow2) -> Double {
  self.end
}

///|
pub fn EventWindow2::events(self : EventWindow2) -> Array[MotionEvent2] {
  self.events.copy()
}

///|
pub fn EventWindow2::crossed_forward(self : EventWindow2) -> Bool {
  self.crossed_forward
}

///|
pub fn build_event_window(
  queue : MotionEventQueue2,
  previous : Double,
  current : Double,
) -> EventWindow2 {
  let forward = current >= previous
  let start = previous.min(current)
  let end = previous.max(current)
  {
    start,
    end,
    events: queue.poll_window(start, end),
    crossed_forward: forward,
  }
}

///|
pub fn EventWindow2::contains(self : EventWindow2, time : Double) -> Bool {
  time >= self.start && time <= self.end
}

///|
pub fn EventWindow2::normalized_progress(
  self : EventWindow2,
  time : Double,
) -> Double {
  if self.end == self.start {
    0.0
  } else {
    clamp01((time - self.start) / (self.end - self.start))
  }
}

///|
pub fn filter_events_by_kind(
  events : Array[MotionEvent2],
  kind : MotionEventKind2,
) -> Array[MotionEvent2] {
  let result : Array[MotionEvent2] = []
  for event in events {
    let matches = match (event.kind, kind) {
      (MarkerEvent, MarkerEvent)
      | (TriggerEvent, TriggerEvent)
      | (LoopEvent, LoopEvent)
      | (EnterEvent, EnterEvent)
      | (ExitEvent, ExitEvent) => true
      (_, _) => false
    }
    if matches {
      result.push(event)
    }
  }
  result
}

///|
pub fn event_times(events : Array[MotionEvent2]) -> Array[Double] {
  let result : Array[Double] = []
  for event in events {
    result.push(event.time)
  }
  result
}

///|
pub fn next_event_time(events : Array[MotionEvent2], after : Double) -> Double? {
  let mut best : Double? = None
  for event in events {
    if event.time > after {
      match best {
        None => best = Some(event.time)
        Some(value) => if event.time < value { best = Some(event.time) }
      }
    }
  }
  best
}

///|
pub fn event_queue_duration(queue : MotionEventQueue2) -> Double {
  if queue.events.length() < 2 {
    return 0.0
  }
  queue.events[queue.events.length() - 1].time - queue.events[0].time
}