// Retention policies for bounded in-memory log archives.

///|
pub(all) struct RetentionPolicy {
  minimum_level : Level?
  maximum_age : Int?
  maximum_count : Int?
}

///|
pub(all) struct RetentionResult {
  records : Array[LogRecord]
  dropped_by_level : Int
  dropped_by_age : Int
  dropped_by_capacity : Int
}

///|
pub fn RetentionPolicy::new() -> RetentionPolicy {
  RetentionPolicy::{
    minimum_level: None,
    maximum_age: None,
    maximum_count: None,
  }
}

///|
pub fn RetentionPolicy::min_level(
  self : RetentionPolicy,
  level : Level,
) -> RetentionPolicy {
  RetentionPolicy::{ ..self, minimum_level: Some(level) }
}

///|
pub fn RetentionPolicy::max_age(
  self : RetentionPolicy,
  age : Int,
) -> RetentionPolicy {
  let normalized = if age < 0 { 0 } else { age }
  RetentionPolicy::{ ..self, maximum_age: Some(normalized) }
}

///|
pub fn RetentionPolicy::max_count(
  self : RetentionPolicy,
  count : Int,
) -> RetentionPolicy {
  let normalized = if count < 0 { 0 } else { count }
  RetentionPolicy::{ ..self, maximum_count: Some(normalized) }
}

///|
pub fn RetentionPolicy::apply(
  self : RetentionPolicy,
  records : Array[LogRecord],
  current_timestamp : Int,
) -> RetentionResult {
  let filtered = retain_matching(self, records, current_timestamp, 0, [], 0, 0)
  let kept = trim_retained(filtered.0, self.maximum_count)
  RetentionResult::{
    records: kept.0,
    dropped_by_level: filtered.1,
    dropped_by_age: filtered.2,
    dropped_by_capacity: kept.1,
  }
}

///|
fn retain_matching(
  policy : RetentionPolicy,
  records : Array[LogRecord],
  current_timestamp : Int,
  index : Int,
  kept : Array[LogRecord],
  dropped_level : Int,
  dropped_age : Int,
) -> (Array[LogRecord], Int, Int) {
  if index >= records.length() {
    (kept, dropped_level, dropped_age)
  } else {
    let record = records[index]
    if !retention_level_matches(policy.minimum_level, record.level) {
      retain_matching(
        policy,
        records,
        current_timestamp,
        index + 1,
        kept,
        dropped_level + 1,
        dropped_age,
      )
    } else if !retention_age_matches(
        policy.maximum_age,
        current_timestamp,
        record.timestamp,
      ) {
      retain_matching(
        policy,
        records,
        current_timestamp,
        index + 1,
        kept,
        dropped_level,
        dropped_age + 1,
      )
    } else {
      kept.push(record.copy())
      retain_matching(
        policy,
        records,
        current_timestamp,
        index + 1,
        kept,
        dropped_level,
        dropped_age,
      )
    }
  }
}

///|
fn retention_level_matches(minimum : Level?, actual : Level) -> Bool {
  match minimum {
    None => true
    Some(level) => level_ordinal(actual) >= level_ordinal(level)
  }
}

///|
fn retention_age_matches(
  maximum_age : Int?,
  current_timestamp : Int,
  timestamp : Int,
) -> Bool {
  match maximum_age {
    None => true
    Some(age) => timestamp >= current_timestamp - age
  }
}

///|
fn trim_retained(
  records : Array[LogRecord],
  maximum_count : Int?,
) -> (Array[LogRecord], Int) {
  match maximum_count {
    None => (records, 0)
    Some(count) => {
      let dropped = if records.length() > count {
        records.length() - count
      } else {
        0
      }
      (copy_retained(records, dropped, []), dropped)
    }
  }
}

///|
fn copy_retained(
  records : Array[LogRecord],
  index : Int,
  result : Array[LogRecord],
) -> Array[LogRecord] {
  if index >= records.length() {
    result
  } else {
    result.push(records[index].copy())
    copy_retained(records, index + 1, result)
  }
}

///|
pub fn RetentionResult::get_records(self : RetentionResult) -> Array[LogRecord] {
  copy_retained(self.records, 0, [])
}

///|
pub fn RetentionResult::kept_count(self : RetentionResult) -> Int {
  self.records.length()
}

///|
pub fn RetentionResult::dropped_count(self : RetentionResult) -> Int {
  self.dropped_by_level + self.dropped_by_age + self.dropped_by_capacity
}

///|
pub fn Logger::apply_retention(
  self : Logger,
  policy : RetentionPolicy,
  current_timestamp : Int,
) -> RetentionResult {
  policy.apply(self.records(), current_timestamp)
}