///|
/// An in-memory FAT12/16/32 volume.
pub(all) struct Volume {
  mut data : Array[Byte]
  layout : Layout
}

///|
pub fn mount(data : Array[Byte]) -> Result[Volume, FatError] {
  let layout = match parse_layout(data) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  Ok({ data, layout })
}

///|
pub fn mount_bytes(bytes : Bytes) -> Result[Volume, FatError] {
  let data = Array::make(bytes.length(), b'\x00')
  for i = 0; i < bytes.length(); i = i + 1 {
    data[i] = bytes[i]
  }
  mount(data)
}

///|
pub fn mount_range(
  data : Array[Byte],
  offset : Int,
  length : Int,
) -> Result[Volume, FatError] {
  if offset < 0 || length <= 0 || offset + length > data.length() {
    return Err(Truncated("partition", offset + length, data.length()))
  }
  let slice = Array::make(length, b'\x00')
  for i = 0; i < length; i = i + 1 {
    slice[i] = data[offset + i]
  }
  mount(slice)
}

///|
pub fn Volume::kind(self : Volume) -> FatKind {
  self.layout.kind
}

///|
pub fn Volume::label(self : Volume) -> String {
  self.layout.label
}

///|
pub fn Volume::cluster_count(self : Volume) -> Int {
  self.layout.cluster_count
}

///|
pub fn Volume::total_bytes(self : Volume) -> Int {
  self.layout.total_sectors * self.layout.bytes_per_sector
}

///|
pub fn Volume::bytes_per_cluster(self : Volume) -> Int {
  self.layout.bytes_per_cluster()
}

///|
pub fn Volume::to_bytes(self : Volume) -> Bytes {
  Bytes::from_array(self.data)
}

///|
pub fn Volume::boot_preview(self : Volume) -> Result[Bytes, FatError] {
  slice_bytes(self.data, 0, self.layout.bytes_per_sector)
}

///|
fn Volume::walk_parts(
  self : Volume,
  parts : Array[String],
) -> Result[(Int, DirRecord?), FatError] {
  let mut dir_cluster = root_cluster_of(self.layout)
  if parts.length() == 0 {
    return Ok((dir_cluster, None))
  }
  let mut i = 0
  while i < parts.length() {
    let name = parts[i]
    if name == "." {
      i = i + 1
      continue
    }
    if name == ".." {
      if dir_cluster == root_cluster_of(self.layout) {
        i = i + 1
        continue
      }
      let records = match read_directory(self.data, self.layout, dir_cluster) {
        Err(err) => return Err(err)
        Ok(ok_val) => ok_val
      }
      match find_record(records, "..") {
        None => return Err(Corrupt("directory is missing .."))
        Some(parent) =>
          dir_cluster = if parent.cluster == 0 {
            root_cluster_of(self.layout)
          } else {
            parent.cluster
          }
      }
      i = i + 1
      continue
    }
    let records = match read_directory(self.data, self.layout, dir_cluster) {
      Err(err) => return Err(err)
      Ok(ok_val) => ok_val
    }
    match find_record(records, name) {
      None => {
        if i == parts.length() - 1 {
          return Ok((dir_cluster, None))
        }
        return Err(NotFound(name))
      }
      Some(record) => {
        if i == parts.length() - 1 {
          return Ok((dir_cluster, Some(record)))
        }
        if !record.is_dir() {
          return Err(NotDirectory(name))
        }
        dir_cluster = record.cluster
      }
    }
    i = i + 1
  }
  Ok((dir_cluster, None))
}

///|
pub fn Volume::list(
  self : Volume,
  path : String,
) -> Result[Array[Entry], FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let (dir_cluster, last) = match self.walk_parts(parts) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let cluster = match last {
    None => dir_cluster
    Some(record) => {
      if !record.is_dir() {
        return Err(NotDirectory(path))
      }
      record.cluster
    }
  }
  let records = match read_directory(self.data, self.layout, cluster) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let entries : Array[Entry] = []
  for i = 0; i < records.length(); i = i + 1 {
    if records[i].is_dot() || records[i].is_volume_id() {
      continue
    }
    entries.push(record_to_entry(records[i]))
  }
  Ok(entries)
}

///|
pub fn Volume::stat(self : Volume, path : String) -> Result[Entry, FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parts.length() == 0 {
    return Ok({
      name: "/",
      short_name: "",
      attr: ATTR_DIRECTORY,
      cluster: root_cluster_of(self.layout),
      size: 0,
      is_dir: true,
      write: default_dos_time(),
    })
  }
  let (_, last) = match self.walk_parts(parts) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  match last {
    None => Err(NotFound(path))
    Some(record) => Ok(record_to_entry(record))
  }
}

///|
pub fn Volume::read_file(
  self : Volume,
  path : String,
) -> Result[Bytes, FatError] {
  let entry = match self.stat(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if entry.is_dir {
    return Err(IsDirectory(path))
  }
  read_file_bytes(self.data, self.layout, entry.cluster, entry.size)
}

///|
pub fn Volume::mkdir(self : Volume, path : String) -> Result[Unit, FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parts.length() == 0 {
    return Err(InvalidPath(path))
  }
  let name = parts[parts.length() - 1]
  let parent_parts : Array[String] = []
  for i = 0; i < parts.length() - 1; i = i + 1 {
    parent_parts.push(parts[i])
  }
  let (parent_cluster, last) = match self.walk_parts(parent_parts) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parent_parts.length() > 0 {
    match last {
      None => return Err(NotFound(path))
      Some(record) => if !record.is_dir() { return Err(NotDirectory(path)) }
    }
  }
  let dir_cluster = match last {
    None => parent_cluster
    Some(record) => record.cluster
  }
  let records = match read_directory(self.data, self.layout, dir_cluster) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  match find_record(records, name) {
    Some(_) => return Err(AlreadyExists(path))
    None => ()
  }
  let cluster = match alloc_cluster(self.data, self.layout, 2) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  match init_subdirectory(self.data, self.layout, cluster, dir_cluster) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match
    create_dir_item(
      self.data,
      self.layout,
      dir_cluster,
      name,
      ATTR_DIRECTORY,
      cluster,
      0,
    ) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  Ok(())
}

///|
pub fn Volume::write_file(
  self : Volume,
  path : String,
  payload : Bytes,
) -> Result[Unit, FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parts.length() == 0 {
    return Err(IsDirectory(path))
  }
  let name = parts[parts.length() - 1]
  let parent_parts : Array[String] = []
  for i = 0; i < parts.length() - 1; i = i + 1 {
    parent_parts.push(parts[i])
  }
  let (parent_cluster, last) = match self.walk_parts(parent_parts) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parent_parts.length() > 0 {
    match last {
      None => return Err(NotFound(path))
      Some(record) => if !record.is_dir() { return Err(NotDirectory(path)) }
    }
  }
  let dir_cluster = match last {
    None => parent_cluster
    Some(record) => record.cluster
  }
  let records = match read_directory(self.data, self.layout, dir_cluster) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  match find_record(records, name) {
    Some(record) => {
      if record.is_dir() {
        return Err(IsDirectory(path))
      }
      let cluster = match
        write_file_bytes(self.data, self.layout, payload, record.cluster) {
        Err(err) => return Err(err)
        Ok(ok_val) => ok_val
      }
      update_record_size_cluster(
        self.data,
        self.layout,
        record,
        cluster,
        payload.length(),
      )
    }
    None => {
      let cluster = match write_file_bytes(self.data, self.layout, payload, 0) {
        Err(err) => return Err(err)
        Ok(ok_val) => ok_val
      }
      match
        create_dir_item(
          self.data,
          self.layout,
          dir_cluster,
          name,
          ATTR_ARCHIVE,
          cluster,
          payload.length(),
        ) {
        Err(err) => return Err(err)
        Ok(_) => ()
      }
      Ok(())
    }
  }
}

///|
pub fn Volume::remove(self : Volume, path : String) -> Result[Unit, FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  if parts.length() == 0 {
    return Err(InvalidPath(path))
  }
  let (dir_cluster, last) = match self.walk_parts(parts) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  match last {
    None => Err(NotFound(path))
    Some(record) => {
      if record.is_dir() {
        let empty = match
          directory_is_empty(self.data, self.layout, record.cluster) {
          Err(err) => return Err(err)
          Ok(ok_val) => ok_val
        }
        if !empty {
          return Err(DirectoryNotEmpty(path))
        }
        match free_chain(self.data, self.layout, record.cluster) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
      } else if record.cluster != 0 {
        match free_chain(self.data, self.layout, record.cluster) {
          Err(err) => return Err(err)
          Ok(_) => ()
        }
      }
      let _ = dir_cluster
      delete_record(self.data, record)
    }
  }
}

///|
pub fn Volume::walk(
  self : Volume,
  path : String,
) -> Result[Array[Entry], FatError] {
  let out : Array[Entry] = []
  match self.walk_into(path, out) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  Ok(out)
}

///|
fn Volume::walk_into(
  self : Volume,
  path : String,
  out : Array[Entry],
) -> Result[Unit, FatError] {
  let entries = match self.list(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  for i = 0; i < entries.length(); i = i + 1 {
    out.push(entries[i])
    if entries[i].is_dir {
      match self.walk_into(join_path(path, entries[i].name), out) {
        Err(err) => return Err(err)
        Ok(_) => ()
      }
    }
  }
  Ok(())
}

///|
pub fn Volume::free_clusters(self : Volume) -> Result[Int, FatError] {
  count_free_clusters(self.data, self.layout)
}

///|
pub fn Volume::mkdir_p(self : Volume, path : String) -> Result[Unit, FatError] {
  let parts = match split_path(path) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let mut current = "/"
  for i = 0; i < parts.length(); i = i + 1 {
    current = join_path(current, parts[i])
    match self.stat(current) {
      Ok(entry) => if !entry.is_dir { return Err(NotDirectory(current)) }
      Err(NotFound(_)) =>
        match self.mkdir(current) {
          Err(err) => return Err(err)
          Ok(ok_val) => ok_val
        }
      Err(err) => return Err(err)
    }
  }
  Ok(())
}