///|
/// Lifecycle of an application service attached to a device.
pub(all) enum DeviceServiceState {
  ServiceCreated
  ServiceStarting
  ServiceRunning
  ServiceDraining
  ServiceStopped
  ServiceFailed
} derive(Debug, Eq)

///|
/// Kind of service hosted by the integration layer.
pub(all) enum DeviceServiceKind {
  PollingService
  CommandService
  AlarmService
  HistorianService
  GatewayService
  DiagnosticsService
} derive(Debug, Eq)

///|
/// Registration record for a long-lived device-facing service.
pub(all) struct DeviceService {
  id : String
  device_id : String
  kind : DeviceServiceKind
  endpoint : String
  mut state : DeviceServiceState
  mut started_at : Int?
  mut stopped_at : Int?
  mut restart_count : Int
  tags : Array[String]
}

///|
pub fn DeviceService::new(
  id : String,
  device_id : String,
  kind : DeviceServiceKind,
  endpoint : String,
) -> Result[DeviceService, ModbusError] {
  if id == "" || device_id == "" || endpoint == "" {
    return Err(InvalidData)
  }
  Ok({
    id,
    device_id,
    kind,
    endpoint,
    state: ServiceCreated,
    started_at: None,
    stopped_at: None,
    restart_count: 0,
    tags: [],
  })
}

///|
pub fn DeviceService::id(self : DeviceService) -> String {
  self.id
}

///|
pub fn DeviceService::device_id(self : DeviceService) -> String {
  self.device_id
}

///|
pub fn DeviceService::kind(self : DeviceService) -> DeviceServiceKind {
  self.kind
}

///|
pub fn DeviceService::endpoint(self : DeviceService) -> String {
  self.endpoint
}

///|
pub fn DeviceService::state(self : DeviceService) -> DeviceServiceState {
  self.state
}

///|
pub fn DeviceService::started_at(self : DeviceService) -> Int? {
  self.started_at
}

///|
pub fn DeviceService::stopped_at(self : DeviceService) -> Int? {
  self.stopped_at
}

///|
pub fn DeviceService::restart_count(self : DeviceService) -> Int {
  self.restart_count
}

///|
pub fn DeviceService::tag_count(self : DeviceService) -> Int {
  self.tags.length()
}

///|
pub fn DeviceService::add_tag(
  self : DeviceService,
  tag : String,
) -> Result[Unit, ModbusError] {
  if tag == "" {
    return Err(InvalidData)
  }
  for existing in self.tags {
    if existing == tag {
      return Ok(())
    }
  }
  self.tags.push(tag)
  Ok(())
}

///|
pub fn DeviceService::start(
  self : DeviceService,
  timestamp : Int,
) -> Result[Unit, ModbusError] {
  if timestamp < 0 ||
    self.state == ServiceRunning ||
    self.state == ServiceStarting {
    return Err(Busy)
  }
  self.state = ServiceStarting
  self.started_at = Some(timestamp)
  self.stopped_at = None
  self.state = ServiceRunning
  Ok(())
}

///|
pub fn DeviceService::drain(self : DeviceService) -> Result[Unit, ModbusError] {
  if self.state != ServiceRunning {
    return Err(Busy)
  }
  self.state = ServiceDraining
  Ok(())
}

///|
pub fn DeviceService::stop(
  self : DeviceService,
  timestamp : Int,
) -> Result[Unit, ModbusError] {
  if timestamp < 0 ||
    self.state == ServiceStopped ||
    self.state == ServiceCreated {
    return Err(Busy)
  }
  self.state = ServiceStopped
  self.stopped_at = Some(timestamp)
  Ok(())
}

///|
pub fn DeviceService::fail(
  self : DeviceService,
  timestamp : Int,
) -> Result[Unit, ModbusError] {
  if timestamp < 0 {
    return Err(InvalidData)
  }
  self.state = ServiceFailed
  self.stopped_at = Some(timestamp)
  Ok(())
}

///|
pub fn DeviceService::restart(
  self : DeviceService,
  timestamp : Int,
) -> Result[Unit, ModbusError] {
  if timestamp < 0 || self.state == ServiceRunning {
    return Err(Busy)
  }
  self.restart_count += 1
  self.start(timestamp)
}

///|
pub fn device_service_kind_name(kind : DeviceServiceKind) -> String {
  match kind {
    PollingService => "polling"
    CommandService => "command"
    AlarmService => "alarm"
    HistorianService => "historian"
    GatewayService => "gateway"
    DiagnosticsService => "diagnostics"
  }
}

///|
pub fn device_service_state_name(state : DeviceServiceState) -> String {
  match state {
    ServiceCreated => "created"
    ServiceStarting => "starting"
    ServiceRunning => "running"
    ServiceDraining => "draining"
    ServiceStopped => "stopped"
    ServiceFailed => "failed"
  }
}

///|
/// A point/value pair captured from a device snapshot.
pub(all) struct SnapshotValue {
  point_id : String
  value : Int
  quality : ReadingQuality
  timestamp : Int
}

///|
pub fn SnapshotValue::new(
  point_id : String,
  value : Int,
  timestamp : Int,
  quality? : ReadingQuality = Good,
) -> Result[SnapshotValue, ModbusError] {
  if point_id == "" || timestamp < 0 {
    Err(InvalidData)
  } else {
    Ok({ point_id, value, quality, timestamp })
  }
}

///|
pub fn SnapshotValue::point_id(self : SnapshotValue) -> String {
  self.point_id
}

///|
pub fn SnapshotValue::value(self : SnapshotValue) -> Int {
  self.value
}

///|
pub fn SnapshotValue::quality(self : SnapshotValue) -> ReadingQuality {
  self.quality
}

///|
pub fn SnapshotValue::timestamp(self : SnapshotValue) -> Int {
  self.timestamp
}

///|
pub fn SnapshotValue::is_good(self : SnapshotValue) -> Bool {
  self.quality == Good
}

///|
/// A consistent set of point values from one acquisition cycle.
pub(all) struct DeviceSnapshot {
  device_id : String
  captured_at : Int
  values : Array[SnapshotValue]
  sequence : Int
  mut complete : Bool
}

///|
pub fn DeviceSnapshot::new(
  device_id : String,
  captured_at : Int,
  sequence : Int,
) -> Result[DeviceSnapshot, ModbusError] {
  if device_id == "" || captured_at < 0 || sequence < 1 {
    Err(InvalidData)
  } else {
    Ok({ device_id, captured_at, values: [], sequence, complete: false })
  }
}

///|
pub fn DeviceSnapshot::device_id(self : DeviceSnapshot) -> String {
  self.device_id
}

///|
pub fn DeviceSnapshot::captured_at(self : DeviceSnapshot) -> Int {
  self.captured_at
}

///|
pub fn DeviceSnapshot::sequence(self : DeviceSnapshot) -> Int {
  self.sequence
}

///|
pub fn DeviceSnapshot::value_count(self : DeviceSnapshot) -> Int {
  self.values.length()
}

///|
pub fn DeviceSnapshot::complete(self : DeviceSnapshot) -> Bool {
  self.complete
}

///|
pub fn DeviceSnapshot::add(
  self : DeviceSnapshot,
  value : SnapshotValue,
) -> Result[Unit, ModbusError] {
  if value.timestamp() < self.captured_at {
    return Err(InvalidData)
  }
  for existing in self.values {
    if existing.point_id() == value.point_id() {
      return Err(InvalidData)
    }
  }
  self.values.push(value)
  Ok(())
}

///|
pub fn DeviceSnapshot::finish(
  self : DeviceSnapshot,
  expected : Int,
) -> Result[Unit, ModbusError] {
  if expected < 1 || self.values.length() != expected {
    return Err(Incomplete)
  }
  self.complete = true
  Ok(())
}

///|
pub fn DeviceSnapshot::get(
  self : DeviceSnapshot,
  point_id : String,
) -> SnapshotValue? {
  for value in self.values {
    if value.point_id() == point_id {
      return Some(value)
    }
  }
  None
}

///|
pub fn DeviceSnapshot::good_count(self : DeviceSnapshot) -> Int {
  let mut count = 0
  for value in self.values {
    if value.is_good() {
      count += 1
    }
  }
  count
}

///|
pub fn DeviceSnapshot::quality(self : DeviceSnapshot) -> ReadingQuality {
  if self.values.length() == 0 {
    Stale
  } else if self.good_count() == self.values.length() {
    Good
  } else if self.good_count() == 0 {
    Disconnected
  } else {
    Substituted
  }
}

///|
pub fn DeviceSnapshot::diff(
  self : DeviceSnapshot,
  previous : DeviceSnapshot,
) -> Array[String] {
  let result : Array[String] = []
  for value in self.values {
    match previous.get(value.point_id()) {
      Some(old) =>
        if old.value() != value.value() || old.quality() != value.quality() {
          result.push(value.point_id())
        }
      None => result.push(value.point_id())
    }
  }
  result
}

///|
/// Registry for services and latest snapshots.
pub(all) struct DeviceServiceRegistry {
  services : Array[DeviceService]
  snapshots : Array[DeviceSnapshot]
  max_services : Int
  max_snapshots : Int
}

///|
pub fn DeviceServiceRegistry::new(
  max_services? : Int = 1024,
  max_snapshots? : Int = 4096,
) -> Result[DeviceServiceRegistry, ModbusError] {
  if max_services < 1 || max_snapshots < 1 {
    Err(InvalidData)
  } else {
    Ok({ services: [], snapshots: [], max_services, max_snapshots })
  }
}

///|
pub fn DeviceServiceRegistry::service_count(
  self : DeviceServiceRegistry,
) -> Int {
  self.services.length()
}

///|
pub fn DeviceServiceRegistry::snapshot_count(
  self : DeviceServiceRegistry,
) -> Int {
  self.snapshots.length()
}

///|
pub fn DeviceServiceRegistry::register(
  self : DeviceServiceRegistry,
  service : DeviceService,
) -> Result[Unit, ModbusError] {
  if self.services.length() >= self.max_services {
    return Err(CapacityExceeded)
  }
  for existing in self.services {
    if existing.id() == service.id() {
      return Err(InvalidData)
    }
  }
  self.services.push(service)
  Ok(())
}

///|
pub fn DeviceServiceRegistry::service(
  self : DeviceServiceRegistry,
  id : String,
) -> DeviceService? {
  for service in self.services {
    if service.id() == id {
      return Some(service)
    }
  }
  None
}

///|
pub fn DeviceServiceRegistry::for_device(
  self : DeviceServiceRegistry,
  device_id : String,
) -> Array[DeviceService] {
  let result : Array[DeviceService] = []
  for service in self.services {
    if service.device_id() == device_id {
      result.push(service)
    }
  }
  result
}

///|
pub fn DeviceServiceRegistry::put_snapshot(
  self : DeviceServiceRegistry,
  snapshot : DeviceSnapshot,
) -> Result[Unit, ModbusError] {
  if self.snapshots.length() >= self.max_snapshots {
    ignore(self.snapshots.remove(0))
  }
  for i in 0.. DeviceSnapshot? {
  for snapshot in self.snapshots {
    if snapshot.device_id() == device_id {
      return Some(snapshot)
    }
  }
  None
}

///|
pub fn DeviceServiceRegistry::running(
  self : DeviceServiceRegistry,
) -> Array[String] {
  let result : Array[String] = []
  for service in self.services {
    if service.state() == ServiceRunning {
      result.push(service.id())
    }
  }
  result
}

///|
pub fn DeviceServiceRegistry::failed(
  self : DeviceServiceRegistry,
) -> Array[String] {
  let result : Array[String] = []
  for service in self.services {
    if service.state() == ServiceFailed {
      result.push(service.id())
    }
  }
  result
}

///|
/// Return a stable line-oriented snapshot for diagnostics and support bundles.
pub fn DeviceServiceRegistry::export_summary(
  self : DeviceServiceRegistry,
) -> Array[String] {
  let lines : Array[String] = []
  for service in self.services {
    lines.push(
      "service=" +
      service.id() +
      " device=" +
      service.device_id() +
      " kind=" +
      device_service_kind_name(service.kind()) +
      " state=" +
      device_service_state_name(service.state()),
    )
  }
  for snapshot in self.snapshots {
    lines.push(
      "snapshot=" +
      snapshot.device_id() +
      " values=" +
      snapshot.value_count().to_string() +
      " complete=" +
      snapshot.complete().to_string() +
      " quality=" +
      reading_quality_name(snapshot.quality()),
    )
  }
  lines
}