///|
/// A bounded uniform mesh; coordinates describe cell edges.
#derive(Debug, Eq)
pub struct Mesh {
  left : Double
  right : Double
  cells : Int32
  dx : Double
}

///|
pub fn Mesh::new(
  left : Double,
  right : Double,
  cells : Int32,
) -> Mesh raise GasError {
  if !finite(left) ||
    !finite(right) ||
    right <= left ||
    cells < 2 ||
    cells > 1000000 {
    raise Invalid("mesh needs finite increasing edges and 2..1000000 cells")
  }
  let dx = (right - left) / cells.to_double()
  if !finite(dx) ||
    dx <= 0.0 ||
    left + dx <= left ||
    right - dx >= right ||
    dx < 3.6e-15 * left.abs().max(right.abs()) {
    raise Invalid("mesh spacing is not representable")
  }
  { left, right, cells, dx, }
}

///|
pub fn Mesh::cells(self : Mesh) -> Int32 {
  self.cells
}

///|
pub fn Mesh::left(self : Mesh) -> Double {
  self.left
}

///|
pub fn Mesh::right(self : Mesh) -> Double {
  self.right
}

///|
pub fn Mesh::dx(self : Mesh) -> Double {
  self.dx
}

///|
pub fn Mesh::center(self : Mesh, index : Int32) -> Double raise GasError {
  if index < 0 || index >= self.cells {
    raise Invalid("cell index outside mesh")
  }
  self.left + (index.to_double() + 0.5) * self.dx
}

///|
pub fn Mesh::edge(self : Mesh, index : Int32) -> Double raise GasError {
  if index < 0 || index > self.cells {
    raise Invalid("edge index outside mesh")
  }
  if index == self.cells {
    self.right
  } else {
    self.left + index.to_double() * self.dx
  }
}

///|
pub fn Mesh::locate(self : Mesh, x : Double) -> Int32 raise GasError {
  if !finite(x) || x < self.left || x > self.right {
    raise Invalid("coordinate outside mesh")
  }
  if x == self.right {
    self.cells - 1
  } else {
    ((x - self.left) / self.dx).to_int().min(self.cells - 1)
  }
}

///|
/// Both ends are periodic together, or each end independently transmits/reflects.
#derive(Debug, Eq)
pub(all) enum EndBoundary {
  Outflow
  Wall
}

///|
#derive(Debug, Eq)
pub(all) enum Boundaries {
  Periodic
  Ends(EndBoundary, EndBoundary)
}

///|
fn reflected(q : Primitive) -> Primitive {
  { rho: q.rho, velocity: -q.velocity, pressure: q.pressure, }
}

///|
pub fn boundary_state(
  q : Array[Primitive],
  index : Int32,
  bc : Boundaries,
) -> Primitive raise GasError {
  let n = q.length()
  if n < 2 || index < -1 || index > n {
    raise Invalid("boundary stencil out of range")
  }
  for state in q {
    state.validate()
  }
  if index >= 0 && index < n {
    return q[index]
  }
  match bc {
    Periodic => q[if index < 0 { n - 1 } else { 0 }]
    Ends(left, right) => {
      let value = q[if index < 0 { 0 } else { n - 1 }]
      match (if index < 0 { left } else { right }) {
        Outflow => value
        Wall => reflected(value)
      }
    }
  }
}

///|
pub fn Gas::validate_cells(
  self : Gas,
  mesh : Mesh,
  states : Array[Conserved],
) -> Unit raise GasError {
  if states.length() != mesh.cells {
    raise Invalid("cell array length differs from mesh")
  }
  for u in states {
    ignore(self.primitive(u))
  }
}

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

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

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

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

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

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