///|
pub enum AlarmState {
  NormalAlarm
  Acknowledged
  Shelved
  ActiveAlarm
} derive(Debug, Eq)

///|
pub struct Alarm {
  code : String
  tag : String
  value : Float
  low : Float
  high : Float
  state : AlarmState
  message : String
} derive(Debug, Eq)

///|
pub fn alarm(
  code : String,
  tag : String,
  value : Float,
  low : Float,
  high : Float,
  message : String,
) -> Alarm {
  {
    code,
    tag,
    value,
    low,
    high,
    state: if value < low || value > high {
      ActiveAlarm
    } else {
      NormalAlarm
    },
    message,
  }
}

///|
pub fn Alarm::is_active(self : Alarm) -> Bool {
  self.state is ActiveAlarm
}

///|
pub fn Alarm::is_normal(self : Alarm) -> Bool {
  self.state is NormalAlarm
}

///|
pub fn Alarm::severity(self : Alarm) -> String {
  if self.value < self.low || self.value > self.high {
    "high"
  } else {
    "normal"
  }
}

///|
pub fn Alarm::acknowledge(self : Alarm) -> Alarm {
  if self.is_active() {
    { ..self, state: Acknowledged }
  } else {
    self
  }
}

///|
pub fn Alarm::shelve(self : Alarm) -> Alarm {
  { ..self, state: Shelved }
}

///|
pub struct AlarmBoard {
  alarms : Array[Alarm]
  updated_at : String
} derive(Debug, Eq)

///|
pub fn alarm_board(alarms : Array[Alarm], updated_at : String) -> AlarmBoard {
  { alarms, updated_at }
}

///|
pub fn AlarmBoard::count(self : AlarmBoard) -> Int {
  self.alarms.length()
}

///|
pub fn AlarmBoard::active_count(self : AlarmBoard) -> Int {
  let mut count = 0
  for alarm in self.alarms {
    if alarm.is_active() {
      count = count + 1
    }
  }
  count
}

///|
pub fn AlarmBoard::normal_count(self : AlarmBoard) -> Int {
  self.count() - self.active_count()
}

///|
pub fn AlarmBoard::acknowledge_all(self : AlarmBoard) -> AlarmBoard {
  { ..self, alarms: self.alarms.map(fn(alarm) { alarm.acknowledge() }) }
}

///|
pub fn AlarmBoard::shelve_code(self : AlarmBoard, code : String) -> AlarmBoard {
  {
    ..self,
    alarms: self.alarms.map(fn(alarm) {
      if alarm.code == code {
        alarm.shelve()
      } else {
        alarm
      }
    }),
  }
}

///|
pub fn AlarmBoard::table(self : AlarmBoard) -> ReportTable {
  let rows : Array[Array[String]] = []
  for alarm in self.alarms {
    let state = match alarm.state {
      NormalAlarm => "normal"
      Acknowledged => "acknowledged"
      Shelved => "shelved"
      ActiveAlarm => "active"
    }
    rows.push([alarm.code, alarm.tag, "\{alarm.value}", state, alarm.message])
  }
  table(["code", "tag", "value", "state", "message"], rows)
}

///|
pub enum ControllerMode {
  Manual
  Automatic
  Cascade
  Failed
} derive(Debug, Eq)

///|
pub fn manual_mode() -> ControllerMode {
  Manual
}

///|
pub fn automatic_mode() -> ControllerMode {
  Automatic
}

///|
pub fn cascade_mode() -> ControllerMode {
  Cascade
}

///|
pub fn failed_mode() -> ControllerMode {
  Failed
}

///|
pub struct Controller {
  tag : String
  setpoint : Float
  process_value : Float
  output : Float
  gain : Float
  integral : Float
  mode : ControllerMode
} derive(Debug, Eq)

///|
pub fn controller(
  tag : String,
  setpoint : Float,
  process_value : Float,
  output : Float,
  gain : Float,
  integral : Float,
  mode : ControllerMode,
) -> Controller {
  { tag, setpoint, process_value, output, gain, integral, mode }
}

///|
pub fn Controller::error(self : Controller) -> Float {
  self.setpoint - self.process_value
}

///|
pub fn Controller::next_output(
  self : Controller,
  step : Float,
  minimum : Float,
  maximum : Float,
) -> Float {
  clamp(
    self.output +
    self.gain * self.error() * step +
    self.integral * self.error() * step,
    minimum,
    maximum,
  )
}

///|
pub fn Controller::update(
  self : Controller,
  process_value : Float,
  step : Float,
  minimum : Float,
  maximum : Float,
) -> Controller {
  let next = { ..self, process_value, }
  { ..next, output: next.next_output(step, minimum, maximum) }
}

///|
pub fn Controller::is_automatic(self : Controller) -> Bool {
  self.mode is Automatic || self.mode is Cascade
}

///|
pub fn Controller::is_healthy(self : Controller) -> Bool {
  self.mode != Failed
}

///|
pub fn controller_error(controller : Controller) -> Float {
  controller.error()
}

///|
pub struct ControlLoop {
  controllers : Array[Controller]
  alarms : AlarmBoard
} derive(Debug, Eq)

///|
pub fn control_loop(
  controllers : Array[Controller],
  alarms : AlarmBoard,
) -> ControlLoop {
  { controllers, alarms }
}

///|
pub fn ControlLoop::controller_count(self : ControlLoop) -> Int {
  self.controllers.length()
}

///|
pub fn ControlLoop::healthy_count(self : ControlLoop) -> Int {
  let mut count = 0
  for controller in self.controllers {
    if controller.is_healthy() {
      count = count + 1
    }
  }
  count
}

///|
pub fn ControlLoop::update(
  self : ControlLoop,
  values : Array[Float],
  step : Float,
  minimum : Float,
  maximum : Float,
) -> ControlLoop {
  let controllers = self.controllers.copy()
  for index, controller in controllers {
    if index < values.length() {
      controllers[index] = controller.update(
        values[index],
        step,
        minimum,
        maximum,
      )
    }
  }
  { ..self, controllers, }
}

///|
pub fn ControlLoop::table(self : ControlLoop) -> ReportTable {
  let rows : Array[Array[String]] = []
  for controller in self.controllers {
    let mode = match controller.mode {
      Manual => "manual"
      Automatic => "automatic"
      Cascade => "cascade"
      Failed => "failed"
    }
    rows.push([
      controller.tag,
      "\{controller.setpoint}",
      "\{controller.process_value}",
      "\{controller.output}",
      mode,
    ])
  }
  table(["tag", "setpoint", "process", "output", "mode"], rows)
}

///|
pub fn control_quality_gate(
  control : ControlLoop,
  maximum_active_alarms : Int,
) -> Bool {
  control.healthy_count() == control.controller_count() &&
  control.alarms.active_count() <= maximum_active_alarms
}

///|
pub struct MaintenanceTask {
  asset : String
  task : String
  interval_hours : Float
  operating_hours : Float
  critical : Bool
} derive(Debug, Eq)

///|
pub fn maintenance_task(
  asset : String,
  task : String,
  interval_hours : Float,
  operating_hours : Float,
  critical : Bool,
) -> MaintenanceTask {
  { asset, task, interval_hours, operating_hours, critical }
}

///|
pub fn MaintenanceTask::due(self : MaintenanceTask) -> Bool {
  self.interval_hours <= 0.0 || self.operating_hours >= self.interval_hours
}

///|
pub fn MaintenanceTask::remaining(self : MaintenanceTask) -> Float {
  self.interval_hours - self.operating_hours
}

///|
pub fn MaintenanceTask::risk(self : MaintenanceTask) -> String {
  if self.due() && self.critical {
    "critical"
  } else if self.due() {
    "due"
  } else {
    "planned"
  }
}

///|
pub fn maintenance_table(tasks : Array[MaintenanceTask]) -> ReportTable {
  let rows : Array[Array[String]] = []
  for task in tasks {
    rows.push([
      task.asset,
      task.task,
      "{task.interval_hours}",
      "{task.operating_hours}",
      task.risk(),
    ])
  }
  table(["asset", "task", "interval", "operating hours", "risk"], rows)
}

///|
pub fn maintenance_gate(tasks : Array[MaintenanceTask]) -> Bool {
  for task in tasks {
    if task.due() && task.critical {
      return false
    }
  }
  true
}