///|
pub const ATTR_READ_ONLY : Int = 0x01

///|
pub const ATTR_HIDDEN : Int = 0x02

///|
pub const ATTR_SYSTEM : Int = 0x04

///|
pub const ATTR_VOLUME_ID : Int = 0x08

///|
pub const ATTR_DIRECTORY : Int = 0x10

///|
pub const ATTR_ARCHIVE : Int = 0x20

///|
pub const ATTR_LFN : Int = 0x0F

///|
pub const DIR_ENTRY_SIZE : Int = 32

///|
pub(all) struct RawEntry {
  offset : Int
  name : Bytes
  attr : Int
  nt_res : Int
  create_tenth : Int
  create_time : Int
  create_date : Int
  access_date : Int
  cluster : Int
  write_time : Int
  write_date : Int
  size : Int
} derive(Debug)

///|
pub fn RawEntry::is_end(self : RawEntry) -> Bool {
  self.name.length() > 0 && self.name[0] == b'\x00'
}

///|
pub fn RawEntry::is_deleted(self : RawEntry) -> Bool {
  self.name.length() > 0 && self.name[0] == b'\xE5'
}

///|
pub fn RawEntry::is_lfn(self : RawEntry) -> Bool {
  (self.attr & ATTR_LFN) == ATTR_LFN
}

///|
pub fn RawEntry::is_volume_id(self : RawEntry) -> Bool {
  (self.attr & ATTR_VOLUME_ID) != 0 && !self.is_lfn()
}

///|
pub fn RawEntry::is_dir(self : RawEntry) -> Bool {
  (self.attr & ATTR_DIRECTORY) != 0 && !self.is_lfn()
}

///|
pub fn RawEntry::is_dot(self : RawEntry) -> Bool {
  display_short_name(self.name) == "." || display_short_name(self.name) == ".."
}

///|
fn parse_raw_entry(
  data : Array[Byte],
  offset : Int,
  kind : FatKind,
) -> Result[RawEntry, FatError] {
  let name = match slice_bytes(data, offset, 11) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let attr = match u8_at(data, offset + 11) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let nt_res = match u8_at(data, offset + 12) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let create_tenth = match u8_at(data, offset + 13) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let create_time = match u16le_at(data, offset + 14) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let create_date = match u16le_at(data, offset + 16) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let access_date = match u16le_at(data, offset + 18) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let cluster_hi = match u16le_at(data, offset + 20) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let write_time = match u16le_at(data, offset + 22) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let write_date = match u16le_at(data, offset + 24) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let cluster_lo = match u16le_at(data, offset + 26) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let size = match u32le_at(data, offset + 28) {
    Err(err) => return Err(err)
    Ok(ok_val) => ok_val
  }
  let cluster = match kind {
    Fat32 => ((cluster_hi & 0xFFFF) << 16) | (cluster_lo & 0xFFFF)
    _ => cluster_lo
  }
  Ok({
    offset,
    name,
    attr,
    nt_res,
    create_tenth,
    create_time,
    create_date,
    access_date,
    cluster,
    write_time,
    write_date,
    size,
  })
}

///|
fn write_raw_entry(
  data : Array[Byte],
  entry : RawEntry,
  kind : FatKind,
) -> Result[Unit, FatError] {
  match blit_bytes(data, entry.offset, entry.name) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, entry.offset + 11, entry.attr) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, entry.offset + 12, entry.nt_res) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, entry.offset + 13, entry.create_tenth) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 14, entry.create_time) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 16, entry.create_date) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 18, entry.access_date) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let cluster_hi = match kind {
    Fat32 => (entry.cluster >> 16) & 0xFFFF
    _ => 0
  }
  match put_u16le(data, entry.offset + 20, cluster_hi) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 22, entry.write_time) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 24, entry.write_date) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, entry.offset + 26, entry.cluster & 0xFFFF) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  put_u32le(data, entry.offset + 28, entry.size)
}

///|
fn lfn_unit_at(
  data : Array[Byte],
  offset : Int,
  index : Int,
) -> Result[Int, FatError] {
  // 0..4 at +1, 5..10 at +14, 11..12 at +28
  let byte_off = if index < 5 {
    offset + 1 + index * 2
  } else if index < 11 {
    offset + 14 + (index - 5) * 2
  } else {
    offset + 28 + (index - 11) * 2
  }
  u16le_at(data, byte_off)
}

///|
fn put_lfn_unit(
  data : Array[Byte],
  offset : Int,
  index : Int,
  unit : Int,
) -> Result[Unit, FatError] {
  let byte_off = if index < 5 {
    offset + 1 + index * 2
  } else if index < 11 {
    offset + 14 + (index - 5) * 2
  } else {
    offset + 28 + (index - 11) * 2
  }
  put_u16le(data, byte_off, unit)
}

///|
fn decode_lfn_chunk(
  data : Array[Byte],
  offset : Int,
) -> Result[Array[Int], FatError] {
  let units : Array[Int] = []
  for i = 0; i < 13; i = i + 1 {
    units.push(
      match lfn_unit_at(data, offset, i) {
        Err(err) => return Err(err)
        Ok(ok_val) => ok_val
      },
    )
  }
  Ok(units)
}

///|
fn encode_lfn_chunk(
  data : Array[Byte],
  offset : Int,
  seq : Int,
  last : Bool,
  checksum : Int,
  units : Array[Int],
  start : Int,
) -> Result[Unit, FatError] {
  match fill_bytes(data, offset, 32, b'\x00') {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let ord = seq | (if last { 0x40 } else { 0 })
  match put_u8(data, offset, ord) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, offset + 11, ATTR_LFN) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, offset + 12, 0) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u8(data, offset + 13, checksum) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  match put_u16le(data, offset + 26, 0) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  for i = 0; i < 13; i = i + 1 {
    let idx = start + i
    let unit = if idx < units.length() {
      units[idx]
    } else if idx == units.length() {
      0
    } else {
      0xFFFF
    }
    match put_lfn_unit(data, offset, i, unit) {
      Err(err) => return Err(err)
      Ok(_) => ()
    }
  }
  Ok(())
}

///|
pub(all) struct DirRecord {
  name : String
  short_name : Bytes
  attr : Int
  cluster : Int
  size : Int
  short_offset : Int
  lfn_offsets : Array[Int]
  write_time : Int
  write_date : Int
} derive(Debug)

///|
pub fn DirRecord::is_dir(self : DirRecord) -> Bool {
  (self.attr & ATTR_DIRECTORY) != 0
}

///|
pub fn DirRecord::is_volume_id(self : DirRecord) -> Bool {
  (self.attr & ATTR_VOLUME_ID) != 0
}

///|
pub fn DirRecord::is_dot(self : DirRecord) -> Bool {
  self.name == "." || self.name == ".."
}

///|
/// Public directory listing item.
pub(all) struct Entry {
  name : String
  short_name : String
  attr : Int
  cluster : Int
  size : Int
  is_dir : Bool
  write : DosTime
} derive(Debug)

///|
fn record_to_entry(record : DirRecord) -> Entry {
  {
    name: record.name,
    short_name: display_short_name(record.short_name),
    attr: record.attr,
    cluster: record.cluster,
    size: record.size,
    is_dir: record.is_dir(),
    write: unpack_dos_stamp(record.write_date, record.write_time),
  }
}