///|
/// An in-memory `FileSystem`, so a whole server session runs without a disk.
///
/// The point of this backend is not convenience: it is that a host out of
/// space, or one that refuses a file, is exactly the branch a real filesystem
/// will not produce on demand. Here they are just a field.

///|
/// A filesystem whose files live in memory.
pub struct MemoryFileSystem {
  /// Stored files, by path.
  files : Map[String, Bytes]
  /// Makes every write fail with ERROR 3, to exercise the disk-full path.
  mut disk_full : Bool
  /// Makes every read fail with ERROR 1, to exercise the missing-file path.
  mut read_fails : Bool
}

///|
/// An empty in-memory filesystem.
pub fn MemoryFileSystem::new() -> MemoryFileSystem {
  { files: {}, disk_full: false, read_fails: false, }
}

///|
/// Puts `payload` at `path` as if it had been written there before.
pub fn MemoryFileSystem::put(
  self : MemoryFileSystem,
  path : String,
  payload : Bytes,
) -> Unit {
  self.files[path] = payload
}

///|
/// The bytes stored at `path`, for a test that wants to look at them.
pub fn MemoryFileSystem::get(self : MemoryFileSystem, path : String) -> Bytes? {
  self.files.get(path)
}

///|
/// Whether `path` is present.
pub fn MemoryFileSystem::has(self : MemoryFileSystem, path : String) -> Bool {
  self.files.contains(path)
}

///|
/// Every path currently stored.
pub fn MemoryFileSystem::paths(self : MemoryFileSystem) -> Array[String] {
  self.files.keys().collect()
}

///|
/// Makes every subsequent write report a full disk.
pub fn MemoryFileSystem::make_full(self : MemoryFileSystem) -> Unit {
  self.disk_full = true
}

///|
/// Makes every subsequent read report a missing file.
pub fn MemoryFileSystem::break_reads(self : MemoryFileSystem) -> Unit {
  self.read_fails = true
}

///|
pub extend MemoryFileSystem with FileSystem::{read, write, exists}

///|
pub impl FileSystem for MemoryFileSystem with fn read(self, path) {
  if self.read_fails {
    return Err(FileNotFound("read \{path}"))
  }
  match self.files.get(path) {
    Some(payload) => Ok(payload)
    None => Err(FileNotFound("read \{path}"))
  }
}

///|
pub impl FileSystem for MemoryFileSystem with fn write(self, path, payload) {
  ignore(path)
  if self.disk_full {
    return Err(DiskFull("write \{path}"))
  }
  self.files[path] = payload
  Ok(())
}

///|
pub impl FileSystem for MemoryFileSystem with fn exists(self, path) {
  ignore(self)
  self.files.contains(path)
}