///|
/// A debouncer that requires several consecutive positive observations.
pub struct ConsecutiveRule {
  required : Int
  mut hits : Int
  mut misses : Int
}

///|
pub fn ConsecutiveRule::new(required? : Int = 3) -> ConsecutiveRule {
  { required: if required < 1 { 1 } else { required }, hits: 0, misses: 0 }
}

///|
pub fn ConsecutiveRule::push(self : ConsecutiveRule, positive : Bool) -> Bool {
  if positive {
    self.hits += 1
    self.misses = 0
  } else {
    self.hits = 0
    self.misses += 1
  }
  if self.hits >= self.required {
    self.hits = 0
    true
  } else {
    false
  }
}

///|
pub fn ConsecutiveRule::hits(self : ConsecutiveRule) -> Int {
  self.hits
}

///|
/// Hysteresis prevents alert flapping around a single threshold.
pub struct HysteresisRule {
  enter_threshold : Double
  exit_threshold : Double
  mut active : Bool
}

///|
pub fn HysteresisRule::new(
  enter_threshold? : Double = 1.0,
  exit_threshold? : Double = 0.5,
) -> HysteresisRule {
  let enter = if enter_threshold < 0.0 { 0.0 } else { enter_threshold }
  {
    enter_threshold: enter,
    exit_threshold: if exit_threshold < 0.0 {
      0.0
    } else if exit_threshold > enter {
      enter
    } else {
      exit_threshold
    },
    active: false,
  }
}

///|
pub fn HysteresisRule::push(self : HysteresisRule, score : Double) -> Bool {
  if self.active {
    if score <= self.exit_threshold {
      self.active = false
    }
  } else if score >= self.enter_threshold {
    self.active = true
  }
  self.active
}

///|
pub fn HysteresisRule::active(self : HysteresisRule) -> Bool {
  self.active
}

///|
pub struct VoteRule {
  required : Int
  window : Array[Bool]
  size : Int
}

///|
pub fn VoteRule::new(size? : Int = 5, required? : Int = 3) -> VoteRule {
  let safe_size = if size < 1 { 1 } else { size }
  {
    required: if required < 1 {
      1
    } else if required > safe_size {
      safe_size
    } else {
      required
    },
    window: [],
    size: safe_size,
  }
}

///|
pub fn VoteRule::push(self : VoteRule, positive : Bool) -> Bool {
  self.window.push(positive)
  if self.window.length() > self.size {
    ignore(self.window.remove(0))
  }
  let mut votes = 0
  for item in self.window {
    if item {
      votes += 1
    }
  }
  votes >= self.required
}

///|
pub fn VoteRule::votes(self : VoteRule) -> Int {
  let mut count = 0
  for item in self.window {
    if item {
      count += 1
    }
  }
  count
}

///|
pub struct AlertBudget {
  capacity : Double
  refill : Double
  mut tokens : Double
  mut suppressed : Int
}

///|
pub fn AlertBudget::new(
  capacity? : Int = 5,
  refill? : Double = 1.0,
) -> AlertBudget {
  let safe_capacity = if capacity < 1 { 1 } else { capacity }
  {
    capacity: safe_capacity.to_double(),
    refill: if refill < 0.0 {
      0.0
    } else {
      refill
    },
    tokens: safe_capacity.to_double(),
    suppressed: 0,
  }
}

///|
pub fn AlertBudget::tick(self : AlertBudget) -> Unit {
  self.tokens = if self.tokens + self.refill > self.capacity {
    self.capacity
  } else {
    self.tokens + self.refill
  }
}

///|
pub fn AlertBudget::allow(self : AlertBudget) -> Bool {
  if self.tokens >= 1.0 {
    self.tokens -= 1.0
    true
  } else {
    self.suppressed += 1
    false
  }
}

///|
pub fn AlertBudget::suppressed(self : AlertBudget) -> Int {
  self.suppressed
}