///|
pub(all) struct WalRecord {
  transaction_id : Int
  snapshot_version : Int
  commit_version : Int
  operations : Array[WriteIntent]
  checksum : Int
} derive(Debug, Eq)

///|
fn checksum_text(value : String) -> Int {
  let mut hash = 216613
  for index = 0; index < value.length(); index = index + 1 {
    match value.get_char(index) {
      Some(ch) => hash = (hash * 167 + ch.to_int()) % 1000000007
      None => ()
    }
  }
  hash
}

///|
fn wal_checksum(
  transaction_id : Int,
  snapshot_version : Int,
  commit_version : Int,
  operations : Array[WriteIntent],
) -> Int {
  let mut checksum = (
      transaction_id * 31 + snapshot_version * 131 + commit_version * 521
    ) %
    1000000007
  for operation in operations {
    checksum = (checksum * 257 + checksum_text(operation.key)) % 1000000007
    checksum = match operation.value {
      Some(value) => (checksum * 257 + checksum_text(value) + 1) % 1000000007
      None => (checksum * 257 + 7) % 1000000007
    }
  }
  checksum
}

///|
pub fn WalRecord::new(
  transaction_id : Int,
  snapshot_version : Int,
  commit_version : Int,
  operations : Array[WriteIntent],
) -> WalRecord {
  let copied = operations.copy()
  {
    transaction_id,
    snapshot_version,
    commit_version,
    operations: copied,
    checksum: wal_checksum(
      transaction_id, snapshot_version, commit_version, copied,
    ),
  }
}

///|
pub fn WalRecord::is_valid(self : WalRecord) -> Bool {
  self.commit_version > self.snapshot_version &&
  self.operations.length() > 0 &&
  self.checksum ==
  wal_checksum(
    self.transaction_id,
    self.snapshot_version,
    self.commit_version,
    self.operations,
  )
}

///|
fn json_escape(value : String) -> String {
  let out = StringBuilder()
  for ch in value {
    match ch {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
pub fn WriteIntent::to_json(self : WriteIntent) -> String {
  match self.value {
    Some(value) =>
      "{\"key\":\"\{json_escape(self.key)}\",\"value\":\"\{json_escape(value)}\"}"
    None => "{\"key\":\"\{json_escape(self.key)}\",\"value\":null}"
  }
}

///|
pub fn WalRecord::to_json(self : WalRecord) -> String {
  let out = StringBuilder()
  out.write_string(
    "{\"transaction_id\":\{self.transaction_id},\"snapshot_version\":\{self.snapshot_version},\"commit_version\":\{self.commit_version},\"operations\":[",
  )
  for index, operation in self.operations {
    if index > 0 {
      out.write_char(',')
    }
    out.write_string(operation.to_json())
  }
  out.write_string("],\"checksum\":\{self.checksum}}")
  out.to_string()
}