///|
pub(all) struct Grid1D {
  cells : Int
  length : Double
  dx : Double
} derive(Debug, ToJson)

///|
pub fn Grid1D::new(cells : Int, length : Double) -> Grid1D {
  { cells, length, dx: length / cells.to_double() }
}

///|
pub fn Grid1D::cells_count(grid : Grid1D) -> Int {
  grid.cells
}

///|
pub fn Grid1D::domain_length(grid : Grid1D) -> Double {
  grid.length
}

///|
pub fn Grid1D::cell_width(grid : Grid1D) -> Double {
  grid.dx
}

///|
pub fn Grid1D::is_valid(grid : Grid1D) -> Bool {
  grid.cells > 0 && grid.length > 0.0 && grid.dx > 0.0
}

///|
pub fn Grid1D::cell_index(grid : Grid1D, x : Double) -> Int {
  periodic_index(@math.floor(grid.wrap(x) / grid.dx).to_int(), grid.cells)
}

///|
pub fn Grid1D::position(grid : Grid1D, index : Int) -> Double {
  (index.to_double() + 0.5) * grid.dx
}

///|
pub fn Grid1D::wrap(grid : Grid1D, x : Double) -> Double {
  let periods = @math.floor(x / grid.length)
  let y = x - periods * grid.length
  if y < 0.0 {
    y + grid.length
  } else if y >= grid.length {
    y - grid.length
  } else {
    y
  }
}

///|
pub fn zeros(n : Int) -> Array[Double] {
  Array::make(n, 0.0)
}

///|
pub fn mean(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    values.fold(init=0.0, (acc, v) => acc + v) / values.length().to_double()
  }
}

///|
pub fn l2_norm(values : ArrayView[Double]) -> Double {
  values.fold(init=0.0, (acc, v) => acc + v * v).sqrt()
}

///|
pub fn linspace(start : Double, stop : Double, count : Int) -> Array[Double] {
  if count <= 1 {
    [start]
  } else {
    let step = (stop - start) / (count - 1).to_double()
    Array::makei(count, fn(i) { start + i.to_double() * step })
  }
}