// SUSP / Rock Ridge(IEEE P1282)解析。
//
// Rock Ridge 把扩展信息塞在目录记录的 system use area 里,每条扩展是:
//   signature(2) + length(1) + version(1) + data
// 关键条目:
//   SP  存在性标记(只出现在根目录的 "." 记录里)
//   ER  扩展引用(RRIP_1991A 之类)
//   NM  替代名字(可跨多条拼接)
//   PX  POSIX 属性(mode / nlink / uid / gid / serial)
//   SL  符号链接目标
//   CE  续接区:其余条目放在映像的另一个位置

///|
pub struct RockRidge {
  /// 替代名字(NM),没有则为 None
  mut name : String?
  /// POSIX mode(PX)
  mut mode : Int32?
  /// 符号链接目标(SL),没有则为 None
  mut symlink : String?
  /// 是否真的存在 SUSP/Rock Ridge 条目
  mut present : Bool
}

///|
fn RockRidge::empty() -> RockRidge {
  { name: None, mode: None, symlink: None, present: false, }
}

///|
/// 拷贝一段字节(越界截断到合法范围)。
fn sub_bytes(data : Bytes, start : Int32, end : Int32) -> Bytes {
  let from = if start < 0 { 0 } else { start }
  let to = if end > data.length() { data.length() } else { end }
  if to <= from {
    return Bytes::new(0)
  }
  let n = to - from
  let out = FixedArray::make(n, b'\x00')
  for i = 0; i < n; i = i + 1 {
    out[i] = data[from + i]
  }
  Bytes::from_array(out[:])
}

///|
/// 从 SUSP 条目流中取一个 both-endian 32 位值(取小端那份,并校验两份一致)。
fn susp_u32_both(area : Bytes, offset : Int32) -> Int32 raise IsoError {
  if offset + 8 > area.length() {
    raise Truncated
  }
  let le = area[offset].to_uint().reinterpret_as_int() |
    (area[offset + 1].to_uint().reinterpret_as_int() << 8) |
    (area[offset + 2].to_uint().reinterpret_as_int() << 16) |
    (area[offset + 3].to_uint().reinterpret_as_int() << 24)
  let be = (area[offset + 4].to_uint().reinterpret_as_int() << 24) |
    (area[offset + 5].to_uint().reinterpret_as_int() << 16) |
    (area[offset + 6].to_uint().reinterpret_as_int() << 8) |
    area[offset + 7].to_uint().reinterpret_as_int()
  if le != be {
    raise BadBothEndian
  }
  le
}

///|
/// 解析一段 SUSP 条目流,把结果累加到 info 上。
///
/// `data` / `block_size` 用于跟随 CE 续接区;depth 限制续接层数,防止畸形映像造成死循环。
pub fn parse_susp(
  data : Bytes,
  block_size : Int32,
  area : Bytes,
  info : RockRidge,
  depth : Int32,
) -> Unit raise IsoError {
  if depth > 4 {
    return
  }
  let mut i = 0
  while i + 4 <= area.length() {
    let len = area[i + 2].to_uint().reinterpret_as_int()
    if len < 4 || i + len > area.length() {
      break
    }
    let sig0 = area[i].to_uint().reinterpret_as_int()
    let sig1 = area[i + 1].to_uint().reinterpret_as_int()
    let body = sub_bytes(area, i + 4, i + len)
    if sig0 == 0x53 && sig1 == 0x50 {
      info.present = true // SP
    } else if sig0 == 0x45 && sig1 == 0x52 {
      info.present = true // ER
    } else if sig0 == 0x52 && sig1 == 0x52 {
      info.present = true // RR
    } else if sig0 == 0x4E && sig1 == 0x4D {
      // NM
      if body.length() >= 1 {
        let flags = body[0].to_uint().reinterpret_as_int()
        if (flags & 2) != 0 {
          info.name = Some(".")
        } else if (flags & 4) != 0 {
          info.name = Some("..")
        } else {
          let text = @utf8.decode_lossy(sub_bytes(body, 1, body.length()))
          info.name = Some(
            match info.name {
              Some(existing) => existing + text
              None => text
            },
          )
        }
      }
    } else if sig0 == 0x50 && sig1 == 0x58 {
      // PX
      info.mode = Some(susp_u32_both(area, i + 4))
    } else if sig0 == 0x53 && sig1 == 0x4C {
      // SL
      if body.length() >= 1 {
        let mut target = ""
        let mut p = 1
        while p + 2 <= body.length() {
          let cflags = body[p].to_uint().reinterpret_as_int()
          let clen = body[p + 1].to_uint().reinterpret_as_int()
          if p + 2 + clen > body.length() {
            break
          }
          let component = if (cflags & 8) != 0 {
            "/"
          } else if (cflags & 2) != 0 {
            "."
          } else if (cflags & 4) != 0 {
            ".."
          } else {
            @utf8.decode_lossy(sub_bytes(body, p + 2, p + 2 + clen))
          }
          if target == "" {
            target = component
          } else if target == "/" {
            target = target + component
          } else {
            target = target + "/" + component
          }
          p = p + 2 + clen
        }
        info.symlink = Some(target)
      }
    } else if sig0 == 0x43 && sig1 == 0x45 {
      // CE
      let block = susp_u32_both(area, i + 4)
      let offset = susp_u32_both(area, i + 12)
      let clen = susp_u32_both(area, i + 20)
      let start = block * block_size + offset
      if start >= 0 && clen >= 0 && start + clen <= data.length() {
        parse_susp(
          data,
          block_size,
          sub_bytes(data, start, start + clen),
          info,
          depth + 1,
        )
      }
    }
    i = i + len
  }
}

///|
/// 解析一条记录的 system use area,得到 Rock Ridge 信息。
pub fn parse_rock_ridge(
  data : Bytes,
  block_size : Int32,
  system_use : Bytes,
) -> RockRidge raise IsoError {
  let info = RockRidge::empty()
  parse_susp(data, block_size, system_use, info, 0)
  info
}