///|
/// Physical unit associated with an engineering value.
pub(all) enum EngineeringUnit {
  Unitless
  Percent
  Celsius
  Fahrenheit
  Pascal
  Kilopascal
  Volt
  Ampere
  Hertz
  Rpm
  Meter
  Millimeter
  LiterPerMinute
} derive(Debug, Eq)

///|
/// Quality assigned to a value after conversion and filtering.
pub(all) enum ReadingQuality {
  Good
  OutOfRange
  Stale
  Disconnected
  BadFormat
  Substituted
} derive(Debug, Eq)

///|
pub fn engineering_unit_name(unit : EngineeringUnit) -> String {
  match unit {
    Unitless => "unitless"
    Percent => "%"
    Celsius => "degC"
    Fahrenheit => "degF"
    Pascal => "Pa"
    Kilopascal => "kPa"
    Volt => "V"
    Ampere => "A"
    Hertz => "Hz"
    Rpm => "rpm"
    Meter => "m"
    Millimeter => "mm"
    LiterPerMinute => "L/min"
  }
}

///|
pub fn reading_quality_name(value : ReadingQuality) -> String {
  match value {
    Good => "good"
    OutOfRange => "out-of-range"
    Stale => "stale"
    Disconnected => "disconnected"
    BadFormat => "bad-format"
    Substituted => "substituted"
  }
}

///|
/// A deterministic integer scaling transform for register data.
pub(all) struct ScaleTransform {
  raw_min : Int
  raw_max : Int
  engineering_min : Int
  engineering_max : Int
  unit : EngineeringUnit
  deadband : Int
  clamp : Bool
  precision : Int
}

///|
pub fn ScaleTransform::new(
  raw_min : Int,
  raw_max : Int,
  engineering_min : Int,
  engineering_max : Int,
  unit? : EngineeringUnit = Unitless,
  deadband? : Int = 0,
  clamp? : Bool = true,
  precision? : Int = 1,
) -> Result[ScaleTransform, ModbusError] {
  if raw_min >= raw_max ||
    engineering_min > engineering_max ||
    deadband < 0 ||
    precision < 1 {
    Err(InvalidData)
  } else {
    Ok({
      raw_min,
      raw_max,
      engineering_min,
      engineering_max,
      unit,
      deadband,
      clamp,
      precision,
    })
  }
}

///|
pub fn ScaleTransform::raw_min(self : ScaleTransform) -> Int {
  self.raw_min
}

///|
pub fn ScaleTransform::raw_max(self : ScaleTransform) -> Int {
  self.raw_max
}

///|
pub fn ScaleTransform::engineering_min(self : ScaleTransform) -> Int {
  self.engineering_min
}

///|
pub fn ScaleTransform::engineering_max(self : ScaleTransform) -> Int {
  self.engineering_max
}

///|
pub fn ScaleTransform::unit(self : ScaleTransform) -> EngineeringUnit {
  self.unit
}

///|
pub fn ScaleTransform::deadband(self : ScaleTransform) -> Int {
  self.deadband
}

///|
pub fn ScaleTransform::precision(self : ScaleTransform) -> Int {
  self.precision
}

///|
pub fn ScaleTransform::raw_in_range(self : ScaleTransform, raw : Int) -> Bool {
  raw >= self.raw_min && raw <= self.raw_max
}

///|
pub fn ScaleTransform::engineering_in_range(
  self : ScaleTransform,
  value : Int,
) -> Bool {
  value >= self.engineering_min && value <= self.engineering_max
}

///|
pub fn ScaleTransform::clamp_raw(self : ScaleTransform, raw : Int) -> Int {
  if raw < self.raw_min {
    self.raw_min
  } else if raw > self.raw_max {
    self.raw_max
  } else {
    raw
  }
}

///|
pub fn ScaleTransform::clamp_engineering(
  self : ScaleTransform,
  value : Int,
) -> Int {
  if value < self.engineering_min {
    self.engineering_min
  } else if value > self.engineering_max {
    self.engineering_max
  } else {
    value
  }
}

///|
pub fn ScaleTransform::raw_to_engineering(
  self : ScaleTransform,
  raw : Int,
) -> Result[Int, ModbusError] {
  if !self.raw_in_range(raw) && !self.clamp {
    return Err(InvalidData)
  }
  let input = if self.clamp { self.clamp_raw(raw) } else { raw }
  let numerator = (input - self.raw_min) *
    (self.engineering_max - self.engineering_min)
  Ok(self.engineering_min + numerator / (self.raw_max - self.raw_min))
}

///|
pub fn ScaleTransform::engineering_to_raw(
  self : ScaleTransform,
  value : Int,
) -> Result[Int, ModbusError] {
  if !self.engineering_in_range(value) && !self.clamp {
    return Err(InvalidData)
  }
  let input = if self.clamp { self.clamp_engineering(value) } else { value }
  let numerator = (input - self.engineering_min) * (self.raw_max - self.raw_min)
  Ok(self.raw_min + numerator / (self.engineering_max - self.engineering_min))
}

///|
pub fn ScaleTransform::normalize(
  self : ScaleTransform,
  raw : Int,
) -> Result[Int, ModbusError] {
  if !self.raw_in_range(raw) && !self.clamp {
    return Err(InvalidData)
  }
  let input = if self.clamp { self.clamp_raw(raw) } else { raw }
  Ok((input - self.raw_min) * 10000 / (self.raw_max - self.raw_min))
}

///|
pub fn ScaleTransform::apply_deadband(
  self : ScaleTransform,
  previous : Int,
  current : Int,
) -> Int {
  if (current - previous).abs() <= self.deadband {
    previous
  } else {
    current
  }
}

///|
pub fn ScaleTransform::format(self : ScaleTransform, value : Int) -> String {
  value.to_string() + " " + engineering_unit_name(self.unit)
}

///|
pub fn ScaleTransform::summary(self : ScaleTransform) -> String {
  "raw=" +
  self.raw_min.to_string() +
  ".." +
  self.raw_max.to_string() +
  " engineering=" +
  self.engineering_min.to_string() +
  ".." +
  self.engineering_max.to_string() +
  " unit=" +
  engineering_unit_name(self.unit)
}

///|
/// A calibration sample used to build a piecewise-linear curve.
pub(all) struct CalibrationSample {
  raw : Int
  engineering : Int
}

///|
pub fn CalibrationSample::new(
  raw : Int,
  engineering : Int,
) -> CalibrationSample {
  { raw, engineering }
}

///|
pub fn CalibrationSample::raw(self : CalibrationSample) -> Int {
  self.raw
}

///|
pub fn CalibrationSample::engineering(self : CalibrationSample) -> Int {
  self.engineering
}

///|
/// A monotonic calibration curve for sensors with non-linear error.
pub(all) struct CalibrationCurve {
  samples : Array[CalibrationSample]
  max_samples : Int
  clamp : Bool
}

///|
pub fn CalibrationCurve::new(
  max_samples? : Int = 32,
  clamp? : Bool = true,
) -> Result[CalibrationCurve, ModbusError] {
  if max_samples < 2 {
    Err(CapacityExceeded)
  } else {
    Ok({ samples: [], max_samples, clamp })
  }
}

///|
pub fn CalibrationCurve::length(self : CalibrationCurve) -> Int {
  self.samples.length()
}

///|
pub fn CalibrationCurve::add(
  self : CalibrationCurve,
  sample : CalibrationSample,
) -> Result[Unit, ModbusError] {
  if self.samples.length() >= self.max_samples {
    return Err(CapacityExceeded)
  }
  for current in self.samples {
    if current.raw() == sample.raw() {
      return Err(InvalidData)
    }
    if current.raw() > sample.raw() {
      return Err(InvalidData)
    }
  }
  if self.samples.length() > 0 &&
    sample.raw() <= self.samples[self.samples.length() - 1].raw() {
    return Err(InvalidData)
  }
  self.samples.push(sample)
  Ok(())
}

///|
pub fn CalibrationCurve::sample(
  self : CalibrationCurve,
  index : Int,
) -> Result[CalibrationSample, ModbusError] {
  if index < 0 || index >= self.samples.length() {
    Err(InvalidAddress)
  } else {
    Ok(self.samples[index])
  }
}

///|
pub fn CalibrationCurve::raw_min(
  self : CalibrationCurve,
) -> Result[Int, ModbusError] {
  if self.samples.length() == 0 {
    Err(Incomplete)
  } else {
    Ok(self.samples[0].raw())
  }
}

///|
pub fn CalibrationCurve::raw_max(
  self : CalibrationCurve,
) -> Result[Int, ModbusError] {
  if self.samples.length() == 0 {
    Err(Incomplete)
  } else {
    Ok(self.samples[self.samples.length() - 1].raw())
  }
}

///|
pub fn CalibrationCurve::convert(
  self : CalibrationCurve,
  raw : Int,
) -> Result[Int, ModbusError] {
  if self.samples.length() < 2 {
    return Err(Incomplete)
  }
  if raw < self.samples[0].raw() {
    if !self.clamp {
      return Err(InvalidData)
    }
    return Ok(self.samples[0].engineering())
  }
  if raw > self.samples[self.samples.length() - 1].raw() {
    if !self.clamp {
      return Err(InvalidData)
    }
    return Ok(self.samples[self.samples.length() - 1].engineering())
  }
  for index in 1.. Result[Array[Int], ModbusError] {
  let result : Array[Int] = []
  for value in values {
    match self.convert(value) {
      Ok(converted) => result.push(converted)
      Err(error) => return Err(error)
    }
  }
  Ok(result)
}

///|
pub fn CalibrationCurve::snapshot(
  self : CalibrationCurve,
) -> Array[CalibrationSample] {
  let result : Array[CalibrationSample] = []
  for sample in self.samples {
    result.push(sample)
  }
  result
}

///|
/// A timestamped reading after scaling and quality assessment.
pub(all) struct ScaledReading {
  point_id : String
  raw : Int
  value : Int
  unit : EngineeringUnit
  quality : ReadingQuality
  timestamp : Int
}

///|
pub fn ScaledReading::new(
  point_id : String,
  raw : Int,
  transform : ScaleTransform,
  timestamp : Int,
) -> Result[ScaledReading, ModbusError] {
  if point_id == "" || timestamp < 0 {
    return Err(InvalidData)
  }
  let quality = if transform.raw_in_range(raw) { Good } else { OutOfRange }
  let value = match transform.raw_to_engineering(raw) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ point_id, raw, value, unit: transform.unit(), quality, timestamp })
}

///|
pub fn ScaledReading::point_id(self : ScaledReading) -> String {
  self.point_id
}

///|
pub fn ScaledReading::raw(self : ScaledReading) -> Int {
  self.raw
}

///|
pub fn ScaledReading::value(self : ScaledReading) -> Int {
  self.value
}

///|
pub fn ScaledReading::unit(self : ScaledReading) -> EngineeringUnit {
  self.unit
}

///|
pub fn ScaledReading::quality(self : ScaledReading) -> ReadingQuality {
  self.quality
}

///|
pub fn ScaledReading::timestamp(self : ScaledReading) -> Int {
  self.timestamp
}

///|
pub fn ScaledReading::with_quality(
  self : ScaledReading,
  quality : ReadingQuality,
) -> ScaledReading {
  { ..self, quality, }
}

///|
pub fn ScaledReading::summary(self : ScaledReading) -> String {
  self.point_id +
  "=" +
  self.value.to_string() +
  " " +
  engineering_unit_name(self.unit) +
  " raw=" +
  self.raw.to_string() +
  " quality=" +
  reading_quality_name(self.quality)
}

///|
/// Simple rate limiter for noisy integer signals.
pub(all) struct RateLimiter {
  max_delta : Int
  mut previous : Int?
}

///|
pub fn RateLimiter::new(max_delta : Int) -> Result[RateLimiter, ModbusError] {
  if max_delta < 0 {
    Err(InvalidData)
  } else {
    Ok({ max_delta, previous: None })
  }
}

///|
pub fn RateLimiter::max_delta(self : RateLimiter) -> Int {
  self.max_delta
}

///|
pub fn RateLimiter::apply(self : RateLimiter, value : Int) -> Int {
  match self.previous {
    None => {
      self.previous = Some(value)
      value
    }
    Some(previous) => {
      let delta = value - previous
      let output = if delta > self.max_delta {
        previous + self.max_delta
      } else if delta < -self.max_delta {
        previous - self.max_delta
      } else {
        value
      }
      self.previous = Some(output)
      output
    }
  }
}

///|
pub fn RateLimiter::reset(self : RateLimiter) -> Unit {
  self.previous = None
}

///|
/// Fixed-size moving average with integer arithmetic.
pub(all) struct MovingAverage {
  values : Array[Int]
  max_values : Int
}

///|
pub fn MovingAverage::new(
  max_values : Int,
) -> Result[MovingAverage, ModbusError] {
  if max_values < 1 {
    Err(CapacityExceeded)
  } else {
    Ok({ values: [], max_values })
  }
}

///|
pub fn MovingAverage::push(self : MovingAverage, value : Int) -> Unit {
  if self.values.length() >= self.max_values {
    ignore(self.values.remove(0))
  }
  self.values.push(value)
}

///|
pub fn MovingAverage::length(self : MovingAverage) -> Int {
  self.values.length()
}

///|
pub fn MovingAverage::average(self : MovingAverage) -> Result[Int, ModbusError] {
  if self.values.length() == 0 {
    return Err(Incomplete)
  }
  let mut sum = 0
  for value in self.values {
    sum += value
  }
  Ok(sum / self.values.length())
}

///|
pub fn MovingAverage::snapshot(self : MovingAverage) -> Array[Int] {
  let result : Array[Int] = []
  for value in self.values {
    result.push(value)
  }
  result
}

///|
pub fn MovingAverage::clear(self : MovingAverage) -> Unit {
  self.values.clear()
}