///|
/// Configuration value types accepted by the runtime registry.
pub enum ConfigValue {
  ConfigInt(Int)
  ConfigBool(Bool)
  ConfigText(String)
  ConfigDuration(Int)
  ConfigAddress(CommonAddress)
  ConfigAddressList(Array[CommonAddress])
} derive(Eq, Debug)

///|
pub fn ConfigValue::type_name(self : ConfigValue) -> String {
  match self {
    ConfigInt(_) => "int"
    ConfigBool(_) => "bool"
    ConfigText(_) => "text"
    ConfigDuration(_) => "duration"
    ConfigAddress(_) => "address"
    ConfigAddressList(_) => "address-list"
  }
}

///|
pub struct ConfigDefinition {
  key : String
  description : String
  default_value : ConfigValue
  required : Bool
  minimum : Int?
  maximum : Int?
  secret : Bool
  restart_required : Bool
} derive(Eq, Debug)

///|
pub fn ConfigDefinition::new(
  key : String,
  description : String,
  default_value : ConfigValue,
  required? : Bool = true,
  minimum? : Int,
  maximum? : Int,
  secret? : Bool = false,
  restart_required? : Bool = false,
) -> Result[ConfigDefinition, String] {
  if key.length() == 0 || key.length() > 128 {
    Err("configuration key must contain between 1 and 128 characters")
  } else if description.length() == 0 {
    Err("configuration description is empty")
  } else {
    match (minimum, maximum) {
      (Some(low), Some(high)) =>
        if low > high {
          Err("configuration bounds are inverted")
        } else {
          Ok({
            key,
            description,
            default_value,
            required,
            minimum,
            maximum,
            secret,
            restart_required,
          })
        }
      _ =>
        Ok({
          key,
          description,
          default_value,
          required,
          minimum,
          maximum,
          secret,
          restart_required,
        })
    }
  }
}

///|
pub fn ConfigDefinition::key(self : ConfigDefinition) -> String {
  self.key
}

///|
pub fn ConfigDefinition::description(self : ConfigDefinition) -> String {
  self.description
}

///|
pub fn ConfigDefinition::default_value(self : ConfigDefinition) -> ConfigValue {
  self.default_value
}

///|
pub fn ConfigDefinition::required(self : ConfigDefinition) -> Bool {
  self.required
}

///|
pub fn ConfigDefinition::minimum(self : ConfigDefinition) -> Int? {
  self.minimum
}

///|
pub fn ConfigDefinition::maximum(self : ConfigDefinition) -> Int? {
  self.maximum
}

///|
pub fn ConfigDefinition::secret(self : ConfigDefinition) -> Bool {
  self.secret
}

///|
pub fn ConfigDefinition::restart_required(self : ConfigDefinition) -> Bool {
  self.restart_required
}

///|
/// Value and source revision stored for one key.
pub struct ConfigEntry {
  definition : ConfigDefinition
  value : ConfigValue
  revision : Int
  updated_at : Int
  updated_by : String
} derive(Eq, Debug)

///|
pub fn ConfigEntry::definition(self : ConfigEntry) -> ConfigDefinition {
  self.definition
}

///|
pub fn ConfigEntry::value(self : ConfigEntry) -> ConfigValue {
  self.value
}

///|
pub fn ConfigEntry::revision(self : ConfigEntry) -> Int {
  self.revision
}

///|
pub fn ConfigEntry::updated_at(self : ConfigEntry) -> Int {
  self.updated_at
}

///|
pub fn ConfigEntry::updated_by(self : ConfigEntry) -> String {
  self.updated_by
}

///|
pub enum ConfigChangeKind {
  ConfigAdded
  ConfigUpdated
  ConfigReset
  ConfigRejected
} derive(Eq, Debug)

///|
pub struct ConfigChange {
  key : String
  kind : ConfigChangeKind
  from : ConfigValue?
  to : ConfigValue?
  revision : Int
  timestamp : Int
  operator : String
  message : String
} derive(Eq, Debug)

///|
pub fn ConfigChange::key(self : ConfigChange) -> String {
  self.key
}

///|
pub fn ConfigChange::kind(self : ConfigChange) -> ConfigChangeKind {
  self.kind
}

///|
pub fn ConfigChange::from(self : ConfigChange) -> ConfigValue? {
  self.from
}

///|
pub fn ConfigChange::to(self : ConfigChange) -> ConfigValue? {
  self.to
}

///|
pub fn ConfigChange::revision(self : ConfigChange) -> Int {
  self.revision
}

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

///|
pub fn ConfigChange::operator(self : ConfigChange) -> String {
  self.operator
}

///|
pub fn ConfigChange::message(self : ConfigChange) -> String {
  self.message
}

///|
fn config_int(value : ConfigValue) -> Int? {
  match value {
    ConfigInt(number) => Some(number)
    ConfigDuration(number) => Some(number)
    _ => None
  }
}

///|
fn config_value_valid(
  definition : ConfigDefinition,
  value : ConfigValue,
) -> Bool {
  if definition.default_value().type_name() != value.type_name() {
    false
  } else {
    match config_int(value) {
      None => true
      Some(number) => {
        let low_ok = match definition.minimum() {
          Some(low) => number >= low
          None => true
        }
        let high_ok = match definition.maximum() {
          Some(high) => number <= high
          None => true
        }
        low_ok && high_ok
      }
    }
  }
}

///|
pub struct ConfigTransaction {
  base_revision : Int
  operator : String
  timestamp : Int
  updates : Array[(String, ConfigValue)]
  resets : Array[String]
  mut closed : Bool
} derive(Debug)

///|
pub fn ConfigTransaction::new(
  base_revision : Int,
  operator : String,
  timestamp : Int,
) -> ConfigTransaction {
  { base_revision, operator, timestamp, updates: [], resets: [], closed: false }
}

///|
pub fn ConfigTransaction::base_revision(self : ConfigTransaction) -> Int {
  self.base_revision
}

///|
pub fn ConfigTransaction::operator(self : ConfigTransaction) -> String {
  self.operator
}

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

///|
pub fn ConfigTransaction::count(self : ConfigTransaction) -> Int {
  self.updates.length() + self.resets.length()
}

///|
pub fn ConfigTransaction::set(
  self : ConfigTransaction,
  key : String,
  value : ConfigValue,
) -> Result[Unit, String] {
  if self.closed {
    Err("configuration transaction is closed")
  } else {
    self.updates.push((key, value))
    Ok(())
  }
}

///|
pub fn ConfigTransaction::reset(
  self : ConfigTransaction,
  key : String,
) -> Result[Unit, String] {
  if self.closed {
    Err("configuration transaction is closed")
  } else {
    self.resets.push(key)
    Ok(())
  }
}

///|
pub fn ConfigTransaction::rollback(self : ConfigTransaction) -> Unit {
  self.updates.clear()
  self.resets.clear()
  self.closed = true
}

///|
/// Versioned configuration registry with atomic transaction commits.
pub struct ConfigurationRegistry {
  definitions : Map[String, ConfigDefinition]
  values : Map[String, ConfigEntry]
  history : Array[ConfigChange]
  history_limit : Int
  mut revision : Int
} derive(Debug)

///|
pub fn ConfigurationRegistry::new(
  history_limit? : Int = 4096,
) -> Result[ConfigurationRegistry, String] {
  if history_limit < 1 {
    Err("configuration history limit must be positive")
  } else {
    Ok({ definitions: {}, values: {}, history: [], history_limit, revision: 0 })
  }
}

///|
pub fn ConfigurationRegistry::revision(self : ConfigurationRegistry) -> Int {
  self.revision
}

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

///|
pub fn ConfigurationRegistry::history(
  self : ConfigurationRegistry,
) -> Array[ConfigChange] {
  self.history.copy()
}

///|
pub fn ConfigurationRegistry::register_definition(
  self : ConfigurationRegistry,
  definition : ConfigDefinition,
  timestamp : Int,
  operator : String,
) -> Result[Unit, String] {
  if self.definitions.contains(definition.key()) {
    Err("configuration definition already exists")
  } else if !config_value_valid(definition, definition.default_value()) {
    Err("configuration default value violates definition")
  } else {
    self.definitions[definition.key()] = definition
    self.values[definition.key()] = {
      definition,
      value: definition.default_value(),
      revision: 1,
      updated_at: timestamp,
      updated_by: operator,
    }
    self.revision += 1
    self.record(
      definition.key(),
      ConfigAdded,
      None,
      Some(definition.default_value()),
      timestamp,
      operator,
      "configuration defined",
    )
    Ok(())
  }
}

///|
pub fn ConfigurationRegistry::definition(
  self : ConfigurationRegistry,
  key : String,
) -> ConfigDefinition? {
  self.definitions.get(key)
}

///|
pub fn ConfigurationRegistry::get(
  self : ConfigurationRegistry,
  key : String,
) -> ConfigEntry? {
  self.values.get(key)
}

///|
fn ConfigurationRegistry::record(
  self : ConfigurationRegistry,
  key : String,
  kind : ConfigChangeKind,
  from : ConfigValue?,
  to : ConfigValue?,
  timestamp : Int,
  operator : String,
  message : String,
) -> Unit {
  self.history.push({
    key,
    kind,
    from,
    to,
    revision: self.revision,
    timestamp,
    operator,
    message,
  })
  while self.history.length() > self.history_limit {
    ignore(self.history.remove(0))
  }
}

///|
pub fn ConfigurationRegistry::set(
  self : ConfigurationRegistry,
  key : String,
  value : ConfigValue,
  timestamp : Int,
  operator : String,
) -> Result[ConfigEntry, String] {
  match self.values.get(key) {
    None => Err("configuration key is undefined")
    Some(entry) =>
      if !config_value_valid(entry.definition(), value) {
        self.record(
          key,
          ConfigRejected,
          Some(entry.value()),
          None,
          timestamp,
          operator,
          "value violates configuration definition",
        )
        Err("configuration value violates definition")
      } else {
        let updated = {
          definition: entry.definition(),
          value,
          revision: entry.revision() + 1,
          updated_at: timestamp,
          updated_by: operator,
        }
        self.values[key] = updated
        self.revision += 1
        self.record(
          key,
          ConfigUpdated,
          Some(entry.value()),
          Some(value),
          timestamp,
          operator,
          "configuration updated",
        )
        Ok(updated)
      }
  }
}

///|
pub fn ConfigurationRegistry::reset(
  self : ConfigurationRegistry,
  key : String,
  timestamp : Int,
  operator : String,
) -> Result[ConfigEntry, String] {
  match self.values.get(key) {
    None => Err("configuration key is undefined")
    Some(entry) =>
      self.set(key, entry.definition().default_value(), timestamp, operator)
  }
}

///|
pub fn ConfigurationRegistry::commit(
  self : ConfigurationRegistry,
  transaction : ConfigTransaction,
) -> Result[Array[ConfigChange], String] {
  if transaction.closed {
    Err("configuration transaction is closed")
  } else if transaction.base_revision() != self.revision {
    transaction.closed = true
    Err("configuration revision conflict")
  } else {
    let first = self.history.length()
    for update in transaction.updates {
      let (key, value) = update
      match
        self.set(key, value, transaction.timestamp(), transaction.operator()) {
        Ok(_) => ()
        Err(error) => {
          transaction.closed = true
          return Err(error)
        }
      }
    }
    for key in transaction.resets {
      match self.reset(key, transaction.timestamp(), transaction.operator()) {
        Ok(_) => ()
        Err(error) => {
          transaction.closed = true
          return Err(error)
        }
      }
    }
    transaction.closed = true
    let result : Array[ConfigChange] = []
    for index in first.. Array[String] {
  let result : Array[String] = []
  for key, _ in self.values {
    result.push(key)
  }
  result.sort()
  result
}

///|
pub fn ConfigurationRegistry::export_lines(
  self : ConfigurationRegistry,
) -> Array[String] {
  let result : Array[String] = []
  for key in self.keys() {
    match self.values.get(key) {
      None => ()
      Some(entry) => result.push("\{key}=\{config_value_text(entry.value())}")
    }
  }
  result
}

///|
fn config_value_text(value : ConfigValue) -> String {
  match value {
    ConfigInt(number) => "int:\{number}"
    ConfigBool(flag) => if flag { "bool:true" } else { "bool:false" }
    ConfigText(text) => "text:\{text}"
    ConfigDuration(number) => "duration:\{number}"
    ConfigAddress(address) => "address:\{address.number()}"
    ConfigAddressList(addresses) => {
      let mut text = ""
      for address in addresses {
        text = "\{text}\{address.number()},"
      }
      "addresses:\{text}"
    }
  }
}

///|
pub fn config_value_examples() -> Array[ConfigValue] {
  [
    ConfigInt(1),
    ConfigBool(true),
    ConfigText("iec104"),
    ConfigDuration(10),
    ConfigAddress(CommonAddress::new(1).unwrap()),
    ConfigAddressList([CommonAddress::new(1).unwrap()]),
  ]
}

///|
pub fn config_change_kind_examples() -> Array[ConfigChangeKind] {
  [ConfigAdded, ConfigUpdated, ConfigReset, ConfigRejected]
}