///|
pub(all) enum PrinterState {
  Idle
  Processing
  Stopped
  Unrecognized(Int)
} derive(Eq, Debug)

///|
pub extend PrinterState with Eq::{equal, not_equal}

///|
pub extend PrinterState with @debug.Debug::{to_repr}

///|
pub fn printer_state_from_code(code : Int) -> PrinterState {
  match code {
    3 => Idle
    4 => Processing
    5 => Stopped
    _ => Unrecognized(code)
  }
}

///|
pub fn PrinterState::text(self : PrinterState) -> String {
  match self {
    Idle => "idle"
    Processing => "processing"
    Stopped => "stopped"
    Unrecognized(code) => "unknown-\{code}"
  }
}

///|
pub(all) struct PrinterHealth {
  name : String
  state : PrinterState?
  accepting_jobs : Bool?
  queued_jobs : Int?
  reasons : Array[String]
  diagnostics : Array[Diagnostic]
} derive(Debug)

///|
pub extend PrinterHealth with @debug.Debug::{to_repr}

///|
pub fn Capabilities::health(self : Capabilities) -> PrinterHealth {
  let state = match self.printer.get_int("printer-state") {
    Some(code) => Some(printer_state_from_code(code))
    None => None
  }
  let accepting_jobs = self.accepting_jobs()
  let reasons = self.strings("printer-state-reasons")
  let diagnostics : Array[Diagnostic] = []
  match state {
    Some(Stopped) =>
      diagnostics.push({
        severity: Error,
        attribute: "printer-state",
        message: "printer is stopped; inspect state reasons",
      })
    Some(Unrecognized(_)) | None =>
      diagnostics.push({
        severity: Warning,
        attribute: "printer-state",
        message: "printer state is unavailable or unrecognized",
      })
    _ => ()
  }
  if accepting_jobs == Some(false) {
    diagnostics.push({
      severity: Error,
      attribute: "printer-is-accepting-jobs",
      message: "printer is not accepting new jobs",
    })
  }
  for reason in reasons {
    if reason == "none" {
      continue
    }
    let severity = if reason.has_suffix("-error") { Error } else { Warning }
    diagnostics.push({
      severity,
      attribute: "printer-state-reasons",
      message: reason,
    })
  }
  {
    name: self.name(),
    state,
    accepting_jobs,
    reasons,
    diagnostics,
    queued_jobs: self.printer.get_int("queued-job-count"),
  }
}

///|
pub fn PrinterHealth::ready(self : PrinterHealth) -> Bool {
  self.accepting_jobs == Some(true) &&
  (self.state == Some(Idle) || self.state == Some(Processing)) &&
  !self.diagnostics.any(fn(item) { item.severity == Error })
}

///|
pub fn PrinterHealth::to_json(self : PrinterHealth) -> Json {
  {
    "name": Json::string(self.name),
    "state": match self.state {
      Some(state) => Json::string(state.text())
      None => Json::null()
    },
    "accepting_jobs": match self.accepting_jobs {
      Some(value) => Json::boolean(value)
      None => Json::null()
    },
    "queued_jobs": match self.queued_jobs {
      Some(value) => json_int(value)
      None => Json::null()
    },
    "ready": Json::boolean(self.ready()),
    "reasons": Json::array(self.reasons.map(value => Json::string(value))),
    "diagnostics": Json::array(self.diagnostics.map(value => value.to_json())),
  }
}

///|
pub(all) struct QueueSummary {
  total : Int
  pending : Int
  held : Int
  processing : Int
  stopped : Int
  canceled : Int
  aborted : Int
  completed : Int
  unknown : Int
} derive(Eq, Debug)

///|
pub extend QueueSummary with Eq::{equal, not_equal}

///|
pub extend QueueSummary with @debug.Debug::{to_repr}

///|
/// Summaries reject duplicate IDs instead of double-counting a malformed queue.
pub fn summarize_queue(jobs : Array[Job]) -> QueueSummary raise IppError {
  let ids : Map[Int, Bool] = Map([])
  let counts = FixedArray::make(8, 0)
  for job in jobs {
    if ids.contains(job.id) {
      raise Protocol("duplicate job-id in queue response")
    }
    ids[job.id] = true
    let index = match job.state {
      Some(Pending) => 0
      Some(PendingHeld) => 1
      Some(Processing) => 2
      Some(ProcessingStopped) => 3
      Some(Canceled) => 4
      Some(Aborted) => 5
      Some(Completed) => 6
      _ => 7
    }
    counts[index] += 1
  }
  {
    total: jobs.length(),
    pending: counts[0],
    held: counts[1],
    processing: counts[2],
    stopped: counts[3],
    canceled: counts[4],
    aborted: counts[5],
    completed: counts[6],
    unknown: counts[7],
  }
}

///|
pub fn QueueSummary::to_json(self : QueueSummary) -> Json {
  {
    "total": json_int(self.total),
    "pending": json_int(self.pending),
    "held": json_int(self.held),
    "processing": json_int(self.processing),
    "stopped": json_int(self.stopped),
    "canceled": json_int(self.canceled),
    "aborted": json_int(self.aborted),
    "completed": json_int(self.completed),
    "unknown": json_int(self.unknown),
  }
}