///|
/// Estimated memory footprint of row-major simulation arrays.
pub(all) struct MemoryEstimate {
  cells : Int
  distribution_bytes : Int
  next_bytes : Int
  mask_bytes : Int
  scalar_field_bytes : Int
  vector_field_bytes : Int
  total_bytes : Int
} derive(Debug)

///|
/// Estimate payload memory without hiding allocation overhead.
pub fn memory_estimate(size : Size) -> MemoryEstimate {
  let cells = size.width.max(0) * size.height.max(0)
  let distributions = cells * q * 8
  let next = distributions
  let mask = cells
  let scalar = cells * 8
  let vector = cells * 16
  {
    cells,
    distribution_bytes: distributions,
    next_bytes: next,
    mask_bytes: mask,
    scalar_field_bytes: scalar,
    vector_field_bytes: vector,
    total_bytes: distributions + next + mask + scalar + vector,
  }
}

///|
/// Format a memory estimate in a readable form.
pub fn MemoryEstimate::to_string(self : MemoryEstimate) -> String {
  "cells=\{self.cells}\ndistribution_bytes=\{self.distribution_bytes}\nnext_bytes=\{self.next_bytes}\nmask_bytes=\{self.mask_bytes}\nscalar_field_bytes=\{self.scalar_field_bytes}\nvector_field_bytes=\{self.vector_field_bytes}\ntotal_bytes=\{self.total_bytes}\n"
}

///|
/// Return the number of distribution values in a domain.
pub fn distribution_length(size : Size) -> Int {
  size.width.max(0) * size.height.max(0) * q
}

///|
/// Convert bytes to mebibytes.
pub fn bytes_to_mebibytes(bytes : Int) -> Double {
  bytes.to_double() / 1048576.0
}

///|
/// Return true when a domain has a safe positive allocation size.
pub fn size_is_allocatable(size : Size) -> Bool {
  size.width > 0 && size.height > 0 && distribution_length(size) > 0
}

///|
/// Return an upper bound on bytes for a scalar/vector/checkpoint bundle.
pub fn bundle_memory_bytes(size : Size, checkpoints? : Int = 1) -> Int {
  memory_estimate(size).total_bytes +
  checkpoints.max(0) * distribution_length(size) * 16
}

///|
/// Return an upper bound on a square simulation footprint.
pub fn square_memory_estimate(side : Int) -> MemoryEstimate {
  memory_estimate(Size::new(width=side, height=side))
}