// 目录记录(ECMA-119 §9.1):ISO 9660 里唯一描述「文件/目录」的结构,
// 目录本身也是一串目录记录,只有 0 长度记录表示本扇区剩余部分是填充。

///|
/// 目录记录的 7 字节时间戳(§9.1.5):
/// 年 = 字段值 + 1900,最后 1 字节是有符号的 15 分钟时区偏移。
pub struct RecordDate {
  year : Int32
  month : Int32
  day : Int32
  hour : Int32
  minute : Int32
  second : Int32
  gmt_offset_quarters : Int32
}

///|
pub fn RecordDate::to_iso_string(self : RecordDate) -> String {
  let pad = fn(n : Int32) -> String {
    if n < 10 {
      "0" + n.to_string()
    } else {
      n.to_string()
    }
  }
  self.year.to_string() +
  "-" +
  pad(self.month) +
  "-" +
  pad(self.day) +
  "T" +
  pad(self.hour) +
  ":" +
  pad(self.minute) +
  ":" +
  pad(self.second)
}

///|
/// 一条目录记录。
///
/// - extent:文件内容起始的逻辑块号(LBA),目录则是目录数据起始 LBA;
/// - size:数据长度(字节),目录则是目录数据总长度;
/// - name:去掉 `;版本号` 后的名字,目录为原始目录名;
/// - is_dir:flags 的 bit 1。
pub struct DirectoryRecord {
  extent : Int32
  size : Int32
  flags : Int32
  mut name : String
  date : RecordDate
  /// system use area 原始字节(Rock Ridge / SUSP 扩展放这里);没有则为空。
  system_use : Bytes
  /// POSIX mode(Rock Ridge PX);没有则为 None
  mut posix_mode : Int32?
  /// 符号链接目标(Rock Ridge SL);没有则为 None
  mut symlink_target : String?
}

///|
pub fn DirectoryRecord::is_dir(self : DirectoryRecord) -> Bool {
  (self.flags & 2) != 0
}

///|
/// 是否为 Rock Ridge 符号链接。
pub fn DirectoryRecord::is_symlink(self : DirectoryRecord) -> Bool {
  self.symlink_target is Some(_)
}

///|
/// flags 的 bit 7:多段文件,后面还有同名的连续记录。
pub fn DirectoryRecord::is_multi_extent(self : DirectoryRecord) -> Bool {
  (self.flags & 0x80) != 0
}

///|
/// 特殊记录:0x00 是「本目录」,0x01 是「父目录」。
pub fn DirectoryRecord::is_special(self : DirectoryRecord) -> Bool {
  self.name == "\u{0}" || self.name == "\u{1}"
}

///|
/// 去掉 ISO 9660 的 `;版本号`,并去掉「无扩展名」时残留的点号。
pub fn normalize_iso_name(raw : String) -> String {
  let mut name = raw
  match name.find(";") {
    Some(i) => name = name[:i].to_owned()
    None => ()
  }
  if name.length() > 1 && name.has_suffix(".") {
    name = name[:name.length() - 1].to_owned()
  }
  name
}

///|
/// 解码 Joliet 的 UCS-2 大端名字(含非 BMP 的代理对),遇到 NUL 结束。
pub fn decode_ucs2_be(raw : Bytes) -> String {
  let mut out = ""
  let mut i = 0
  while i + 1 < raw.length() {
    let hi = raw[i].to_uint().reinterpret_as_int()
    let lo = raw[i + 1].to_uint().reinterpret_as_int()
    let unit = (hi << 8) | lo
    i += 2
    if unit == 0 {
      break
    }
    if unit >= 0xD800 && unit <= 0xDBFF && i + 1 < raw.length() {
      let hi2 = raw[i].to_uint().reinterpret_as_int()
      let lo2 = raw[i + 1].to_uint().reinterpret_as_int()
      let unit2 = (hi2 << 8) | lo2
      if unit2 >= 0xDC00 && unit2 <= 0xDFFF {
        let combined = 0x10000 + ((unit - 0xD800) << 10) + (unit2 - 0xDC00)
        out = out + Int32::unsafe_to_char(combined).to_string()
        i += 2
        continue
      }
    }
    out = out + Int32::unsafe_to_char(unit).to_string()
  }
  out
}

///|
/// 解析一条 ISO 9660(ASCII 名字)目录记录。
pub fn parse_directory_record(
  reader : ByteReader,
) -> (DirectoryRecord, Int32) raise IsoError {
  parse_directory_record_in(reader, false)
}

///|
/// 从 reader 当前位置解析一条目录记录,返回记录本身与消耗的字节数。
///
/// `joliet` 为 true 时名字按 UCS-2 大端解码(补充卷描述符里的记录)。
/// 返回长度为 0 表示目录数据在本扇区后面是填充。
pub fn parse_directory_record_in(
  reader : ByteReader,
  joliet : Bool,
) -> (DirectoryRecord, Int32) raise IsoError {
  let start = reader.position()
  let len = reader.read_u8().reinterpret_as_int()
  if len == 0 {
    // 填充:调用方负责跳到下一个扇区
    return (
      DirectoryRecord::{
        extent: 0,
        size: 0,
        flags: 0,
        name: "",
        date: RecordDate::{
          year: 0,
          month: 0,
          day: 0,
          hour: 0,
          minute: 0,
          second: 0,
          gmt_offset_quarters: 0,
        },
        system_use: Bytes::new(0),
        posix_mode: None,
        symlink_target: None,
      },
      0,
    )
  }
  if len < 34 {
    raise BadDirectoryRecord(len)
  }
  let _ext_attr_len = reader.read_u8().reinterpret_as_int()
  let extent = reader.read_u32_both()
  let size = reader.read_u32_both()
  let year = reader.read_u8().reinterpret_as_int() + 1900
  let month = reader.read_u8().reinterpret_as_int()
  let day = reader.read_u8().reinterpret_as_int()
  let hour = reader.read_u8().reinterpret_as_int()
  let minute = reader.read_u8().reinterpret_as_int()
  let second = reader.read_u8().reinterpret_as_int()
  let gmt_raw = reader.read_u8().reinterpret_as_int()
  // 时区偏移是 8 位补码,单位 15 分钟
  let gmt = if gmt_raw >= 128 { gmt_raw - 256 } else { gmt_raw }
  let flags = reader.read_u8().reinterpret_as_int()
  let _file_unit_size = reader.read_u8().reinterpret_as_int()
  let _interleave_gap = reader.read_u8().reinterpret_as_int()
  let _volume_seq = reader.read_u16_both()
  let name_len = reader.read_u8().reinterpret_as_int()
  let name = if name_len == 1 {
    let c = reader.read_u8().reinterpret_as_int()
    if c == 0 {
      "\u{0}"
    } else {
      "\u{1}"
    }
  } else if joliet {
    normalize_iso_name(decode_ucs2_be(reader.read_bytes(name_len)))
  } else {
    normalize_iso_name(reader.read_ascii(name_len))
  }
  // 名字长度是偶数时,后面补 1 字节
  if name_len % 2 == 0 {
    reader.skip(1)
  }
  let consumed = reader.position() - start
  // system use area:Rock Ridge / SUSP 扩展就放在这里
  let system_use = if consumed < len {
    reader.read_bytes(len - consumed)
  } else {
    Bytes::new(0)
  }
  (
    DirectoryRecord::{
      extent,
      size,
      flags,
      name,
      date: RecordDate::{
        year,
        month,
        day,
        hour,
        minute,
        second,
        gmt_offset_quarters: gmt,
      },
      system_use,
      posix_mode: None,
      symlink_target: None,
    },
    len,
  )
}