/// Inventory and accounting functions for storage dashboards.

pub(all) struct StorageReport {
  logical_bytes : Int
  physical_bytes : Int
  chunk_count : Int
  unique_chunks : Int
  deduplicated_bytes : Int
  manifest_bytes : Int
} derive(Show)

pub fn StorageReport::logical(self : StorageReport) -> Int { self.logical_bytes }
pub fn StorageReport::physical(self : StorageReport) -> Int { self.physical_bytes }
pub fn StorageReport::chunks(self : StorageReport) -> Int { self.chunk_count }
pub fn StorageReport::unique(self : StorageReport) -> Int { self.unique_chunks }
pub fn StorageReport::saved(self : StorageReport) -> Int { self.deduplicated_bytes }
pub fn StorageReport::manifest(self : StorageReport) -> Int { self.manifest_bytes }

pub fn inspect_storage(store : ChunkStore, manifest : Manifest) -> StorageReport {
  let refs = manifest.references()
  let unique = unique_digests(refs.map(r => r.id()))
  let mut physical = 0
  for digest in unique {
    match store.get(digest) {
      Some(data) => physical = physical + data.length()
      None => ()
    }
  }
  let logical = manifest.size()
  { logical_bytes: logical, physical_bytes: physical, chunk_count: refs.length(), unique_chunks: unique_digests(refs.map(r => r.id())).length(), deduplicated_bytes: logical - physical, manifest_bytes: encode_manifest(manifest).to_bytes().length() }
}

pub fn storage_report_line(report : StorageReport) -> String {
  "logical={report.logical()} physical={report.physical()} chunks={report.chunks()} unique={report.unique()} saved={report.saved()}"
}

pub fn storage_is_consistent(report : StorageReport) -> Bool {
  report.logical() >= report.physical() && report.unique() <= report.chunks()
}

pub fn store_logical_size(store : ChunkStore, manifest : Manifest) -> Int {
  let report = inspect_storage(store, manifest)
  report.logical()
}

pub fn store_physical_size(store : ChunkStore, manifest : Manifest) -> Int {
  let report = inspect_storage(store, manifest)
  report.physical()
}

pub fn storage_saving_ratio(report : StorageReport) -> Double {
  if report.logical() == 0 { 0.0 } else { report.saved().to_double() / report.logical().to_double() }
}

pub fn manifest_has_all_chunks(store : ChunkStore, manifest : Manifest) -> Bool {
  collect_missing(store, manifest).length() == 0
}

pub fn manifest_has_no_extra_chunks(store : ChunkStore, manifest : Manifest) -> Bool {
  collect_extra(store, manifest).length() == 0
}

pub fn inventory_digest(report : StorageReport) -> String {
  digest_string(
    report.logical().to_string() + ":" + report.physical().to_string() + ":" + report.chunks().to_string() + ":" + report.unique().to_string(),
  )
}

pub fn inventory_equal(left : StorageReport, right : StorageReport) -> Bool {
  left.logical() == right.logical() && left.physical() == right.physical() && left.unique() == right.unique()
}

pub fn empty_storage_report() -> StorageReport {
  { logical_bytes: 0, physical_bytes: 0, chunk_count: 0, unique_chunks: 0, deduplicated_bytes: 0, manifest_bytes: 0 }
}

pub fn add_storage_reports(left : StorageReport, right : StorageReport) -> StorageReport {
  { logical_bytes: left.logical() + right.logical(), physical_bytes: left.physical() + right.physical(), chunk_count: left.chunks() + right.chunks(), unique_chunks: left.unique() + right.unique(), deduplicated_bytes: left.saved() + right.saved(), manifest_bytes: left.manifest() + right.manifest() }
}