///|
/// One replica's monotonic positive and negative components.
pub(all) struct CounterComponent {
  replica : String
  positive : Int
  negative : Int
} derive(Eq, Debug)

///| A state-based PN counter. Each component only grows, making merge a

///|
/// component-wise maximum and therefore associative, commutative, idempotent.
pub struct PnCounter {
  components : Array[CounterComponent]
} derive(Eq, Debug)

///|
/// Counter validation errors.
pub(all) enum CounterError {
  EmptyCounterReplica
  NegativeCounterComponent(String, Int, Int)
  NegativeCounterAmount(Int)
} derive(Eq, Debug)

///|
/// Create a zero counter.
pub fn PnCounter::new() -> PnCounter {
  { components: [] }
}

///|
/// Restore validated components, merging duplicates by component-wise max.
pub fn PnCounter::from_components(
  components : Array[CounterComponent],
) -> Result[PnCounter, CounterError] {
  let mut output = PnCounter::new()
  for component in components {
    if component.replica.length() == 0 {
      return Err(EmptyCounterReplica)
    }
    if component.positive < 0 || component.negative < 0 {
      return Err(
        NegativeCounterComponent(
          component.replica,
          component.positive,
          component.negative,
        ),
      )
    }
    output = output.merge({ components: [component] })
  }
  Ok(output)
}

///|
/// Inspect normalized components.
pub fn PnCounter::components(self : PnCounter) -> Array[CounterComponent] {
  let output : Array[CounterComponent] = []
  for component in self.components {
    output.push(component)
  }
  output
}

///|
/// Current signed value.
pub fn PnCounter::value(self : PnCounter) -> Int {
  let mut result = 0
  for component in self.components {
    result = result + component.positive - component.negative
  }
  result
}

///|
/// Positive contribution made by one replica.
pub fn PnCounter::positive_of(self : PnCounter, replica : String) -> Int {
  for component in self.components {
    if component.replica == replica {
      return component.positive
    }
  }
  0
}

///|
/// Negative contribution made by one replica.
pub fn PnCounter::negative_of(self : PnCounter, replica : String) -> Int {
  for component in self.components {
    if component.replica == replica {
      return component.negative
    }
  }
  0
}

///|
/// Add a non-negative amount at one replica.
pub fn PnCounter::increment_by(
  self : PnCounter,
  replica : String,
  amount : Int,
) -> Result[PnCounter, CounterError] {
  if replica.length() == 0 {
    return Err(EmptyCounterReplica)
  }
  if amount < 0 {
    return Err(NegativeCounterAmount(amount))
  }
  Ok(
    self.set_component(
      replica,
      self.positive_of(replica) + amount,
      self.negative_of(replica),
    ),
  )
}

///|
/// Subtract a non-negative amount at one replica.
pub fn PnCounter::decrement_by(
  self : PnCounter,
  replica : String,
  amount : Int,
) -> Result[PnCounter, CounterError] {
  if replica.length() == 0 {
    return Err(EmptyCounterReplica)
  }
  if amount < 0 {
    return Err(NegativeCounterAmount(amount))
  }
  Ok(
    self.set_component(
      replica,
      self.positive_of(replica),
      self.negative_of(replica) + amount,
    ),
  )
}

///|
/// Increment by one.
pub fn PnCounter::increment(
  self : PnCounter,
  replica : String,
) -> Result[PnCounter, CounterError] {
  self.increment_by(replica, 1)
}

///|
/// Decrement by one.
pub fn PnCounter::decrement(
  self : PnCounter,
  replica : String,
) -> Result[PnCounter, CounterError] {
  self.decrement_by(replica, 1)
}

///|
/// Merge state using a component-wise maximum.
pub fn PnCounter::merge(self : PnCounter, other : PnCounter) -> PnCounter {
  let mut output = self
  for component in other.components {
    let positive = if component.positive > output.positive_of(component.replica) {
      component.positive
    } else {
      output.positive_of(component.replica)
    }
    let negative = if component.negative > output.negative_of(component.replica) {
      component.negative
    } else {
      output.negative_of(component.replica)
    }
    output = output.set_component(component.replica, positive, negative)
  }
  output
}

///|
/// True when every local component is less than or equal to the other state.
pub fn PnCounter::is_included_in(self : PnCounter, other : PnCounter) -> Bool {
  for component in self.components {
    if component.positive > other.positive_of(component.replica) ||
      component.negative > other.negative_of(component.replica) {
      return false
    }
  }
  true
}

///|
/// Components that contain information newer than a peer's state.
pub fn PnCounter::delta_since(self : PnCounter, peer : PnCounter) -> PnCounter {
  let output : Array[CounterComponent] = []
  for component in self.components {
    if component.positive > peer.positive_of(component.replica) ||
      component.negative > peer.negative_of(component.replica) {
      output.push(component)
    }
  }
  { components: output }
}

///|
fn PnCounter::set_component(
  self : PnCounter,
  replica : String,
  positive : Int,
  negative : Int,
) -> PnCounter {
  let output : Array[CounterComponent] = []
  let mut found = false
  for component in self.components {
    if component.replica == replica {
      output.push({ replica, positive, negative })
      found = true
    } else {
      output.push(component)
    }
  }
  if !found {
    output.push({ replica, positive, negative })
  }
  { components: output }
}