///|
/// Discrete transition event emitted when an entity crosses or resides within a geofence.
pub(all) enum GeofenceEvent {
  Enter(Int, Int)
  Exit(Int, Int)
  Stay(Int, Int)
} derive(Eq, Debug)

///|
/// Formats geofence transition event.
pub fn GeofenceEvent::to_string(self : GeofenceEvent) -> String {
  match self {
    Enter(item_id, fence_id) =>
      "Enter(" + item_id.to_string() + ", " + fence_id.to_string() + ")"
    Exit(item_id, fence_id) =>
      "Exit(" + item_id.to_string() + ", " + fence_id.to_string() + ")"
    Stay(item_id, fence_id) =>
      "Stay(" + item_id.to_string() + ", " + fence_id.to_string() + ")"
  }
}

///|
/// Extracts the moving entity ID.
pub fn GeofenceEvent::item_id(self : GeofenceEvent) -> Int {
  match self {
    Enter(id, _) => id
    Exit(id, _) => id
    Stay(id, _) => id
  }
}

///|
/// Extracts the target geofence ID.
pub fn GeofenceEvent::fence_id(self : GeofenceEvent) -> Int {
  match self {
    Enter(_, fid) => fid
    Exit(_, fid) => fid
    Stay(_, fid) => fid
  }
}

///|
/// State tracker monitoring moving entity positions against a spatial index of geofences.
pub struct GeofenceMonitor {
  priv inner : @geofence.GeofenceMonitor
} derive(Eq, Debug, Default)

///|
/// Instantiates a new GeofenceMonitor indexing the supplied geofence bounding boxes.
pub fn GeofenceMonitor::new(
  fences : Array[(Rect, Int)],
  node_capacity? : Int = 16,
) -> GeofenceMonitor {
  let inner = @geofence.GeofenceMonitor::new(fences, node_capacity~)
  GeofenceMonitor::{ inner, }
}

///|
fn map_event(ev : @geofence.GeofenceEvent) -> GeofenceEvent {
  match ev {
    Enter(item_id, fence_id) => GeofenceEvent::Enter(item_id, fence_id)
    Exit(item_id, fence_id) => GeofenceEvent::Exit(item_id, fence_id)
    Stay(item_id, fence_id) => GeofenceEvent::Stay(item_id, fence_id)
  }
}

///|
/// Updates entity position with a 2D bounding rectangle and returns generated transition events.
pub fn GeofenceMonitor::update_rect(
  self : GeofenceMonitor,
  item_id : Int,
  bounds : Rect,
) -> Array[GeofenceEvent] {
  let evs = self.inner.update_rect(item_id, bounds)
  let res : Array[GeofenceEvent] = []
  for i = 0; i < evs.length(); i = i + 1 {
    res.push(map_event(evs[i]))
  }
  res
}

///|
/// Updates entity position with a 2D point coordinate (x, y) and returns generated events.
pub fn GeofenceMonitor::update_point(
  self : GeofenceMonitor,
  item_id : Int,
  x : Double,
  y : Double,
) -> Array[GeofenceEvent] {
  let evs = self.inner.update_point(item_id, x, y)
  let res : Array[GeofenceEvent] = []
  for i = 0; i < evs.length(); i = i + 1 {
    res.push(map_event(evs[i]))
  }
  res
}

///|
/// Unregisters tracked entity and returns Exit events for all fences it was previously inside.
pub fn GeofenceMonitor::remove_item(
  self : GeofenceMonitor,
  item_id : Int,
) -> Array[GeofenceEvent] {
  let evs = self.inner.remove_item(item_id)
  let res : Array[GeofenceEvent] = []
  for i = 0; i < evs.length(); i = i + 1 {
    res.push(map_event(evs[i]))
  }
  res
}

///|
/// Returns currently active geofence IDs occupied by the entity.
pub fn GeofenceMonitor::active_fences(
  self : GeofenceMonitor,
  item_id : Int,
) -> Array[Int] {
  self.inner.active_fences(item_id)
}

///|
/// Returns true if the entity is currently tracked inside the specified geofence.
pub fn GeofenceMonitor::is_in_fence(
  self : GeofenceMonitor,
  item_id : Int,
  fence_id : Int,
) -> Bool {
  self.inner.is_in_fence(item_id, fence_id)
}

///|
/// Returns the number of currently tracked entities.
pub fn GeofenceMonitor::tracked_count(self : GeofenceMonitor) -> Int {
  self.inner.tracked_count()
}

///|
/// Clears all tracked entities.
pub fn GeofenceMonitor::clear(self : GeofenceMonitor) -> Unit {
  self.inner.clear()
}