///|
/// Common local boundary schemes.
pub(all) enum BoundaryKind {
  NoSlip
  VelocityInlet
  PressureOutlet
  PeriodicX
  PeriodicY
  Symmetry
} derive(Debug, Eq)

///|
/// Parameters for one boundary patch.
pub(all) struct BoundarySpec {
  kind : BoundaryKind
  value_x : Double
  value_y : Double
  density : Double
} derive(Debug)

///|
/// Construct a no-slip patch.
pub fn BoundarySpec::no_slip() -> BoundarySpec {
  { kind: NoSlip, value_x: 0.0, value_y: 0.0, density: 1.0 }
}

///|
/// Construct a Zou/He velocity patch.
pub fn BoundarySpec::velocity(
  value_x~ : Double,
  value_y~ : Double,
  density? : Double = 1.0,
) -> BoundarySpec {
  { kind: VelocityInlet, value_x, value_y, density }
}

///|
/// Construct a Zou/He pressure patch.
pub fn BoundarySpec::pressure(
  density~ : Double,
  value_x? : Double = 0.0,
  value_y? : Double = 0.0,
) -> BoundarySpec {
  { kind: PressureOutlet, value_x, value_y, density }
}

///|
/// Reconstruct a velocity boundary from its requested equilibrium moments.
pub fn apply_velocity_boundary(
  distribution : ArrayView[Double],
  rho~ : Double,
  ux~ : Double,
  uy~ : Double,
) -> Array[Double] {
  let requested = equilibrium_distribution(rho~, ux~, uy~)
  let result = Array::make(q, 0.0)
  for d in 0.. Array[Double] {
  apply_velocity_boundary(distribution, rho=density, ux~, uy~)
}

///|
/// Apply a local boundary specification.
pub fn apply_boundary(
  distribution : ArrayView[Double],
  spec~ : BoundarySpec,
) -> Array[Double] {
  match spec.kind {
    NoSlip =>
      collide_distribution(
        distribution,
        rho=spec.density,
        ux=0.0,
        uy=0.0,
        model=Bgk(1.0),
      )
    VelocityInlet =>
      apply_velocity_boundary(
        distribution,
        rho=spec.density,
        ux=spec.value_x,
        uy=spec.value_y,
      )
    PressureOutlet =>
      apply_pressure_boundary(
        distribution,
        density=spec.density,
        ux=spec.value_x,
        uy=spec.value_y,
      )
    PeriodicX | PeriodicY | Symmetry => distribution.to_owned()
  }
}

///|
/// Return the opposite direction used by a bounce-back link.
pub fn bounce_direction(direction : Int) -> Int {
  if direction >= 0 && direction < q {
    opposite[direction]
  } else {
    0
  }
}

///|
/// Return a periodic coordinate for a lattice extent.
pub fn periodic_coordinate(value : Int, extent : Int) -> Int {
  wrap_coordinate(value, extent)
}

///|
/// Compute the local momentum exchange of a reflected population.
pub fn momentum_exchange(direction~ : Int, population~ : Double) -> Point {
  if direction < 0 || direction >= q {
    Point::new(x=0.0, y=0.0)
  } else {
    Point::new(
      x=2.0 * population * cx[direction].to_double(),
      y=2.0 * population * cy[direction].to_double(),
    )
  }
}