///|
pub(all) struct TrackTable {
  observations : Array[TrackObservation]
}

///|
pub fn TrackTable::new() -> TrackTable {
  { observations: [] }
}

///|
pub fn TrackTable::add(
  table : TrackTable,
  observation : TrackObservation,
) -> Unit {
  table.observations.push(observation)
}

///|
pub fn TrackTable::observation_count(table : TrackTable) -> Int {
  table.observations.length()
}

///|
pub fn TrackTable::track_count(table : TrackTable) -> Int {
  let ids : Array[Int] = []
  for observation in table.observations {
    if !ids.contains(observation.point_id) {
      ids.push(observation.point_id)
    }
  }
  ids.length()
}

///|
pub fn TrackTable::observations_for(
  table : TrackTable,
  track_id : Int,
) -> Array[TrackObservation] {
  let result : Array[TrackObservation] = []
  for observation in table.observations {
    if observation.point_id == track_id {
      result.push(observation)
    }
  }
  result
}

///|
pub fn TrackTable::is_triangulatable(
  table : TrackTable,
  track_id : Int,
  minimum_views : Int,
) -> Bool {
  let observations = table.observations_for(track_id)
  if observations.length() < minimum_views {
    return false
  }
  let views : Array[Int] = []
  for observation in observations {
    if !views.contains(observation.view_id) {
      views.push(observation.view_id)
    }
  }
  views.length() >= minimum_views
}

///|
pub(all) struct TrackQuality {
  unique_views : Int
  pixel_span : Double
  has_unique_views : Bool
  centroid : @core.Point2
} derive(Debug, Eq)

///|
pub fn track_quality(
  observations : ArrayView[TrackObservation],
) -> TrackQuality {
  if observations.length() == 0 {
    return {
      unique_views: 0,
      pixel_span: 0.0,
      has_unique_views: true,
      centroid: @core.Point2::new(x=0.0, y=0.0),
    }
  }
  let views : Array[Int] = []
  let mut min_x = observations[0].pixel.x
  let mut max_x = observations[0].pixel.x
  let mut min_y = observations[0].pixel.y
  let mut max_y = observations[0].pixel.y
  let mut sum_x = 0.0
  let mut sum_y = 0.0
  let mut unique = true
  for observation in observations {
    if views.contains(observation.view_id) {
      unique = false
    } else {
      views.push(observation.view_id)
    }
    if observation.pixel.x < min_x {
      min_x = observation.pixel.x
    }
    if observation.pixel.x > max_x {
      max_x = observation.pixel.x
    }
    if observation.pixel.y < min_y {
      min_y = observation.pixel.y
    }
    if observation.pixel.y > max_y {
      max_y = observation.pixel.y
    }
    sum_x += observation.pixel.x
    sum_y += observation.pixel.y
  }
  let span = @core.Vec2::new(x=max_x - min_x, y=max_y - min_y).norm()
  {
    unique_views: views.length(),
    pixel_span: span,
    has_unique_views: unique,
    centroid: @core.Point2::new(
      x=sum_x / observations.length().to_double(),
      y=sum_y / observations.length().to_double(),
    ),
  }
}

///|
pub fn deduplicate_track(
  observations : ArrayView[TrackObservation],
) -> Array[TrackObservation] {
  let result : Array[TrackObservation] = []
  let views : Array[Int] = []
  for observation in observations {
    if !views.contains(observation.view_id) {
      views.push(observation.view_id)
      result.push(observation)
    }
  }
  result
}