///|
pub fn BatchRun::new(id : String) -> BatchRun {
  { id, records: [] }
}

///|
pub fn BatchRun::add(batch : BatchRun, measurement : Measurement) -> Unit {
  batch.records.push(measurement)
}

///|
pub fn BatchRun::measurements(batch : BatchRun) -> Array[Measurement] {
  batch.records
}

///|
pub fn BatchRun::summary(batch : BatchRun) -> MeasurementSummary {
  let mut pass = 0
  let mut warn = 0
  let mut fail = 0
  let values : Array[Double] = []
  for measurement in batch.records {
    values.push(measurement.value)
    match measurement.decision {
      Pass => pass += 1
      Warn => warn += 1
      Fail => fail += 1
    }
  }
  let total = batch.records.length()
  {
    total,
    pass,
    warn,
    fail,
    acceptance_rate: if total == 0 {
      0.0
    } else {
      Double::from_int(pass + warn) / Double::from_int(total)
    },
    values: describe_samples(values),
  }
}

///|
pub fn BatchRun::accepted(batch : BatchRun) -> Array[Measurement] {
  batch.records.filter(fn(measurement) { measurement_passes(measurement) })
}

///|
pub fn BatchRun::failed(batch : BatchRun) -> Array[Measurement] {
  batch.records.filter(fn(measurement) { !measurement_passes(measurement) })
}

///|
pub fn BatchRun::merge(first : BatchRun, second : BatchRun) -> BatchRun {
  let result = BatchRun::new(first.id + "+" + second.id)
  for measurement in first.records {
    result.add(measurement)
  }
  for measurement in second.records {
    result.add(measurement)
  }
  result
}

///|
pub fn render_measurements_csv(measurements : Array[Measurement]) -> String {
  let out = StringBuilder()
  out.write_string("name,value,unit,decision,deviation\n")
  for measurement in measurements {
    out.write_string(measurement.name)
    out.write_string(",")
    out.write_string(measurement.value.to_string())
    out.write_string(",")
    out.write_string(measurement.unit)
    out.write_string(",")
    out.write_string(
      match measurement.decision {
        Pass => "PASS"
        Warn => "WARN"
        Fail => "FAIL"
      },
    )
    out.write_string(",")
    out.write_string(measurement.deviation.to_string())
    out.write_string("\n")
  }
  out.to_string()
}

///|
pub fn batch_statistics(batch : BatchRun) -> BatchStatistics {
  let summary = batch.summary()
  {
    id: batch.id,
    total: summary.total,
    accepted: summary.pass + summary.warn,
    rejected: summary.fail,
    mean: summary.values.mean,
    standard_deviation: summary.values.standard_deviation,
    minimum: summary.values.minimum,
    maximum: summary.values.maximum,
  }
}

///|
pub fn decision_counts(measurements : Array[Measurement]) -> Array[Int] {
  let result = [0, 0, 0]
  for measurement in measurements {
    match measurement.decision {
      Pass => result[0] += 1
      Warn => result[1] += 1
      Fail => result[2] += 1
    }
  }
  result
}