///|
pub(all) struct Observable {
  name : String
  unit : String
  values : Array[Double]
} derive(Debug, ToJson)

///|
pub fn Observable::new(
  name : String,
  unit : String,
  values : Array[Double],
) -> Observable {
  { name, unit, values }
}

///|
pub fn Observable::count(observable : Observable) -> Int {
  observable.values.length()
}

///|
pub fn Observable::mean(observable : Observable) -> Double {
  mean(observable.values)
}

///|
pub fn Observable::minimum(observable : Observable) -> Double {
  min_value(observable.values, 0.0)
}

///|
pub fn Observable::maximum(observable : Observable) -> Double {
  max_value(observable.values, 0.0)
}

///|
pub fn Observable::range(observable : Observable) -> Double {
  observable.maximum() - observable.minimum()
}

///|
pub fn Observable::normalize(observable : Observable) -> Observable {
  { ..observable, values: normalize_series(observable.values) }
}

///|
pub fn Observable::scale(
  observable : Observable,
  factor : Double,
) -> Observable {
  { ..observable, values: observable.values.map(fn(value) { value * factor }) }
}

///|
pub fn Observable::shift(
  observable : Observable,
  amount : Double,
) -> Observable {
  { ..observable, values: observable.values.map(fn(value) { value + amount }) }
}

///|
pub fn Observable::summary(observable : Observable) -> SeriesSummary {
  summarize_series(observable.name, observable.values)
}

///|
pub fn Observable::to_csv(observable : Observable) -> String {
  serialize_scalar_series(observable.name, observable.values)
}

///|
pub fn observables_from_diagnostics(
  values : ArrayView[PicDiagnostics],
) -> Array[Observable] {
  [
    Observable::new(
      "total_charge",
      "C",
      values.map(fn(value) { value.total_charge }),
    ),
    Observable::new(
      "kinetic_energy",
      "J",
      values.map(fn(value) { value.kinetic_energy }),
    ),
    Observable::new(
      "field_energy",
      "J",
      values.map(fn(value) { value.field_energy }),
    ),
    Observable::new(
      "total_energy",
      "J",
      values.map(fn(value) { value.total_energy() }),
    ),
    Observable::new(
      "mean_velocity",
      "m/s",
      values.map(fn(value) { value.mean_velocity }),
    ),
  ]
}

///|
pub fn observable_summary_csv(values : ArrayView[Observable]) -> String {
  let output = StringBuilder()
  output.write_string("name,unit,count,minimum,maximum,mean\n")
  for value in values {
    let summary = value.summary()
    output.write_string(
      "\{value.name},\{value.unit},\{summary.count},\{summary.minimum},\{summary.maximum},\{summary.mean}\n",
    )
  }
  output.to_string()
}

///|
pub fn merge_observables(first : Observable, second : Observable) -> Observable {
  let values = first.values.copy()
  for value in second.values {
    values.push(value)
  }
  Observable::new(first.name, first.unit, values)
}

///|
pub fn observable_difference(
  first : Observable,
  second : Observable,
) -> Observable {
  Observable::new(
    first.name,
    first.unit,
    field_difference(first.values, second.values),
  )
}

///|
pub fn observable_correlation(
  first : Observable,
  second : Observable,
) -> Double {
  correlation(first.values, second.values)
}