/// MoonChunk public data model.

/// A half-open byte interval in the source stream.
pub(all) struct Span {
  start : Int
  end : Int
} derive(Show, Eq)

pub fn Span::new(start : Int, end : Int) -> Span {
  { start, end }
}

pub fn Span::length(self : Span) -> Int {
  self.end - self.start
}

pub fn Span::is_empty(self : Span) -> Bool {
  self.start >= self.end
}

/// A content-addressed block. The digest is represented as a stable hex string.
pub(all) struct Chunk {
  ordinal : Int
  span : Span
  digest : String
  data : Array[Byte]
} derive(Show)

pub fn Chunk::new(ordinal : Int, span : Span, data : Array[Byte]) -> Chunk {
  {
    ordinal,
    span,
    digest: digest_bytes(data),
    data,
  }
}

pub fn Chunk::size(self : Chunk) -> Int {
  self.data.length()
}

pub fn Chunk::id(self : Chunk) -> String {
  self.digest
}

pub fn Chunk::bytes(self : Chunk) -> Array[Byte] {
  self.data.copy()
}

pub fn Chunk::index(self : Chunk) -> Int {
  self.ordinal
}

pub fn Chunk::range(self : Chunk) -> Span {
  self.span
}

/// Parameters used by the rolling chunker.
pub(all) struct ChunkerConfig {
  min_size : Int
  average_size : Int
  max_size : Int
  seed : UInt
} derive(Show, Eq)

pub fn ChunkerConfig::new(
  min_size? : Int = 512,
  average_size? : Int = 2048,
  max_size? : Int = 8192,
  seed? : UInt = 0x9e3779b9U,
) -> ChunkerConfig {
  let min_size = if min_size < 16 { 16 } else { min_size }
  let average_size = if average_size < min_size { min_size } else { average_size }
  let max_size = if max_size < average_size { average_size } else { max_size }
  { min_size, average_size, max_size, seed }
}

pub fn ChunkerConfig::validate(self : ChunkerConfig) -> Result[Unit, String] {
  if self.min_size <= 0 {
    Err("min_size must be positive")
  } else if self.min_size > self.average_size {
    Err("min_size must not exceed average_size")
  } else if self.average_size > self.max_size {
    Err("average_size must not exceed max_size")
  } else {
    Ok(())
  }
}

pub fn ChunkerConfig::with_seed(self : ChunkerConfig, seed : UInt) -> ChunkerConfig {
  { ..self, seed }
}

pub fn ChunkerConfig::minimum(self : ChunkerConfig) -> Int {
  self.min_size
}

pub fn ChunkerConfig::average(self : ChunkerConfig) -> Int {
  self.average_size
}

pub fn ChunkerConfig::maximum(self : ChunkerConfig) -> Int {
  self.max_size
}

/// A reference to a stored chunk inside a manifest.
pub(all) struct ChunkRef {
  position : Int
  digest : String
  size : Int
} derive(Show, Eq)

pub fn ChunkRef::new(position : Int, digest : String, size : Int) -> ChunkRef {
  { position, digest, size }
}

pub fn ChunkRef::at(self : ChunkRef) -> Int { self.position }
pub fn ChunkRef::id(self : ChunkRef) -> String { self.digest }
pub fn ChunkRef::length(self : ChunkRef) -> Int { self.size }

/// An immutable description of one version of a file.
pub(all) struct Manifest {
  name : String
  total_size : Int
  chunks : Array[ChunkRef]
  root : String
  format_version : Int
} derive(Show)

pub fn Manifest::new(name : String, chunks : Array[ChunkRef]) -> Manifest {
  let mut total = 0
  for c in chunks { total = total + c.size }
  let root = merkle_root(chunks.map(c => c.digest))
  { name, total_size: total, chunks, root, format_version: 1 }
}

pub fn Manifest::filename(self : Manifest) -> String { self.name }
pub fn Manifest::size(self : Manifest) -> Int { self.total_size }
pub fn Manifest::count(self : Manifest) -> Int { self.chunks.length() }
pub fn Manifest::root_digest(self : Manifest) -> String { self.root }
pub fn Manifest::version(self : Manifest) -> Int { self.format_version }
pub fn Manifest::references(self : Manifest) -> Array[ChunkRef] { self.chunks.copy() }

pub fn Manifest::is_valid(self : Manifest) -> Bool {
  self.total_size >= 0 && self.chunks.length() >= 0 &&
    self.root == merkle_root(self.chunks.map(c => c.digest))
}

/// A single operation in an incremental synchronization plan.
pub(all) enum SyncOp {
  Reuse(digest~ : String, size~ : Int)
  Upload(digest~ : String, size~ : Int, source_index~ : Int)
  Remove(digest~ : String)
} derive(Show, Eq)

pub(all) struct SyncPlan {
  operations : Array[SyncOp]
  reused_bytes : Int
  uploaded_bytes : Int
  removed_chunks : Int
} derive(Show)

pub fn SyncPlan::new(operations : Array[SyncOp]) -> SyncPlan {
  let mut reused = 0
  let mut uploaded = 0
  let mut removed = 0
  for op in operations {
    match op {
      Reuse(size~) => reused = reused + size
      Upload(size~) => uploaded = uploaded + size
      Remove => removed = removed + 1
    }
  }
  { operations, reused_bytes: reused, uploaded_bytes: uploaded, removed_chunks: removed }
}

pub fn SyncPlan::operations(self : SyncPlan) -> Array[SyncOp] { self.operations.copy() }
pub fn SyncPlan::reused(self : SyncPlan) -> Int { self.reused_bytes }
pub fn SyncPlan::uploaded(self : SyncPlan) -> Int { self.uploaded_bytes }
pub fn SyncPlan::removed(self : SyncPlan) -> Int { self.removed_chunks }

/// A detailed verification result suitable for a CLI or a UI.
pub(all) enum VerifyResult {
  Valid
  InvalidManifest(reason~ : String)
  MissingChunk(digest~ : String, position~ : Int)
  CorruptChunk(expected~ : String, actual~ : String, position~ : Int)
  SizeMismatch(expected~ : Int, actual~ : Int)
} derive(Show, Eq)

pub fn VerifyResult::ok(self : VerifyResult) -> Bool {
  match self {
    Valid => true
    _ => false
  }
}