///|
/// The private state shared by copies of an in-memory database handle.
priv struct DatabaseState {
  mut committed : GraphState
  mut active : Ref[TransactionStatus]?
  mut closed : Bool
  wal_path : String?
  mut transaction_id : UInt64
  mut recovery_required : Bool
  mut writer_lock : @fs.File?
}

///|
/// The private transaction lifecycle shared by the database and its writer.
priv enum TransactionStatus {
  Active
  Failed
  Committing
  Committed
  RolledBack
}

///|
/// An in-memory database handle over one committed graph state.
pub struct Database {
  priv state : Ref[DatabaseState]
}

///|
/// A write transaction over one detached candidate graph.
pub struct WriteTransaction {
  priv database : Ref[DatabaseState]
  priv candidate : GraphState
  priv operations : Array[TransactionOperation]
  priv status : Ref[TransactionStatus]
}

///|
/// Create an empty in-memory database.
pub fn Database::new_in_memory() -> Database {
  {
    state: Ref::Ref({
      committed: GraphState::new(),
      active: None,
      closed: false,
      wal_path: None,
      transaction_id: 0UL,
      recovery_required: false,
      writer_lock: None,
    }),
  }
}

///|
/// Read a detached node from the last committed state.
pub fn Database::get_node(
  self : Database,
  id : NodeId,
) -> Node raise DatabaseError {
  if self.state.val.closed {
    raise DatabaseError::DatabaseClosed
  }
  if self.state.val.recovery_required {
    raise DatabaseError::DatabaseRecoveryRequired
  }
  self.state.val.committed.get_node(id)
}

///|
/// Read a detached edge from the last committed state.
pub fn Database::get_edge(
  self : Database,
  id : EdgeId,
) -> Edge raise DatabaseError {
  if self.state.val.closed {
    raise DatabaseError::DatabaseClosed
  }
  if self.state.val.recovery_required {
    raise DatabaseError::DatabaseRecoveryRequired
  }
  self.state.val.committed.get_edge(id)
}

///|
/// Begin the one constructor-facing write transaction allowed by this handle.
pub fn Database::begin_write(
  self : Database,
) -> WriteTransaction raise DatabaseError {
  if self.state.val.closed {
    raise DatabaseError::DatabaseClosed
  }
  if self.state.val.recovery_required {
    raise DatabaseError::DatabaseRecoveryRequired
  }
  match self.state.val.active {
    Some(_) => raise DatabaseError::WriterTransactionActive
    None => ()
  }
  let status = Ref::Ref(Active)
  let candidate = self.state.val.committed.copy_detached()
  self.state.val.active = Some(status)
  { database: self.state, candidate, operations: [], status, }
}

///|
/// Close the handle and discard the constructor-facing active transaction.
pub fn Database::close(self : Database) -> Unit raise DatabaseError {
  if self.state.val.closed {
    raise DatabaseError::DatabaseClosed
  }
  match self.state.val.active {
    Some(status) =>
      match status.val {
        Committing => raise DatabaseError::TransactionCommitInProgress
        _ => status.update(fn(_) { RolledBack })
      }
    None => ()
  }
  self.state.val.active = None
  self.state.val.closed = true
  match self.state.val.writer_lock {
    Some(lock) => {
      lock.unlock()
      lock.close()
      self.state.val.writer_lock = None
    }
    None => ()
  }
}