// KMZ 读写:ZIP 容器 + doc.kml 封装。
//
// KMZ 是「含 doc.kml 的 ZIP」,可以带图片/模型等资源文件。这里实现所需的最小
// ZIP 子集:读取支持 Stored 与 DEFLATE(解压复用 moonbit-community/flate),
// 写入统一用 DEFLATE + CRC-32。

///|
/// ZIP 本地文件头固定长度(不含文件名与额外字段)。
const LOCAL_HEADER_SIZE : Int32 = 30

///|
/// 一个 ZIP 条目(名字、内容与 CRC)。
pub(all) struct ZipEntry {
  name : String
  data : Bytes
}

///|
/// 读取小端 u16。
fn read_u16(data : Bytes, at : Int32) -> Int32 raise KmlError {
  if at + 2 > data.length() {
    raise XmlSyntax("truncated zip u16", at)
  }
  data[at].to_uint().reinterpret_as_int() |
  (data[at + 1].to_uint().reinterpret_as_int() << 8)
}

///|
/// 读取小端 u32。
fn read_u32(data : Bytes, at : Int32) -> Int32 raise KmlError {
  if at + 4 > data.length() {
    raise XmlSyntax("truncated zip u32", at)
  }
  let b0 = data[at].to_uint().reinterpret_as_int()
  let b1 = data[at + 1].to_uint().reinterpret_as_int()
  let b2 = data[at + 2].to_uint().reinterpret_as_int()
  let b3 = data[at + 3].to_uint().reinterpret_as_int()
  b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}

///|
/// 写入小端 u16。
fn put_u16(out : Array[Byte], value : Int32) -> Unit {
  out.push((value & 0xFF).to_byte())
  out.push(((value >> 8) & 0xFF).to_byte())
}

///|
/// 写入小端 u32。
fn put_u32(out : Array[Byte], value : UInt32) -> Unit {
  out.push((value & 0xFF).to_byte())
  out.push(((value >> 8) & 0xFF).to_byte())
  out.push(((value >> 16) & 0xFF).to_byte())
  out.push(((value >> 24) & 0xFF).to_byte())
}

///|
/// CRC-32(IEEE 802.3,初始 0xFFFFFFFF,结果取反)。
fn crc32(data : Bytes) -> UInt32 {
  let mut crc : UInt32 = 0xFFFFFFFF
  let mut i = 0
  while i < data.length() {
    let byte : UInt32 = data[i].to_uint()
    crc = crc ^ byte
    let mut k = 0
    while k < 8 {
      let lsb = (crc & 1) == 1
      crc = crc >> 1
      if lsb {
        crc = crc ^ 0xEDB88320
      }
      k = k + 1
    }
    i = i + 1
  }
  crc ^ 0xFFFFFFFF
}

///|
/// 追加 ASCII 字符串的字节。
fn push_ascii(out : Array[Byte], text : String) -> Unit {
  let data = @utf8.encode(text)
  let mut i = 0
  while i < data.length() {
    out.push(data[i])
    i = i + 1
  }
}

///|
/// 写一个本地文件头(Stored,version 20,DOS 时间固定)。
fn put_local_header(
  out : Array[Byte],
  name : String,
  size : Int32,
  comp_size : Int32,
  compression : Int32,
  crc : UInt32,
) -> Unit {
  put_u32(out, 0x04034b50)
  put_u16(out, 20)
  put_u16(out, 0)
  put_u16(out, compression)
  put_u16(out, 0)
  put_u16(out, 0)
  put_u32(out, crc)
  put_u32(out, comp_size.reinterpret_as_uint())
  put_u32(out, size.reinterpret_as_uint())
  put_u16(out, @utf8.encode(name).length())
  put_u16(out, 0)
  push_ascii(out, name)
}

///|
/// 打包若干条目成 ZIP 字节流。
pub fn zip_pack(entries : Array[ZipEntry]) -> Bytes {
  let out : Array[Byte] = []
  let mut i = 0
  while i < entries.length() {
    let name = entries[i].name
    let data = entries[i].data
    let crc = crc32(data)
    let size = data.length()
    let compressed = @flate.deflate_all(data)
    let comp_size = compressed.length()
    put_local_header(out, name, size, comp_size, 8, crc)
    let mut k = 0
    while k < compressed.length() {
      out.push(compressed[k])
      k = k + 1
    }
    i = i + 1
  }
  let mut i = 0
  while i < entries.length() {
    let name = entries[i].name
    let data = entries[i].data
    let crc = crc32(data)
    let size = data.length()
    let compressed = @flate.deflate_all(data)
    let comp_size = compressed.length()
    let name_len = @utf8.encode(name).length()
    put_u32(out, 0x02014b50)
    put_u16(out, 20)
    put_u16(out, 20)
    put_u16(out, 0)
    put_u16(out, 8)
    put_u16(out, 0)
    put_u16(out, 0)
    put_u32(out, crc)
    put_u32(out, comp_size.reinterpret_as_uint())
    put_u32(out, size.reinterpret_as_uint())
    put_u16(out, name_len)
    put_u16(out, 0)
    put_u16(out, 0)
    put_u16(out, 0)
    put_u16(out, 0)
    put_u32(out, 0)
    put_u32(out, 0)
    push_ascii(out, name)
    i = i + 1
  }
  put_u32(out, 0x06054b50)
  put_u16(out, 0)
  put_u16(out, 0)
  put_u16(out, entries.length())
  put_u16(out, entries.length())
  put_u32(out, 0)
  put_u32(out, 0)
  put_u16(out, 0)
  Bytes::from_array(out)
}

///|
/// 一条 ZIP 记录:名字与内容区间。
priv struct ZipRecord {
  name : String
  content_start : Int32
  comp_size : Int32
  compression : Int32
}

///|
/// 枚举 ZIP 里的所有条目(按本地文件头顺序)。
fn zip_records(data : Bytes) -> Array[ZipRecord] raise KmlError {
  let n = data.length()
  let out : Array[ZipRecord] = []
  let mut at = 0
  while at + LOCAL_HEADER_SIZE <= n {
    let sig = read_u32(data, at)
    if sig != 0x04034b50 {
      break
    }
    let compression = read_u16(data, at + 8)
    let comp_size = read_u32(data, at + 18)
    let name_len = read_u16(data, at + 26)
    let extra_len = read_u16(data, at + 28)
    let name_start = at + LOCAL_HEADER_SIZE
    if name_start + name_len + extra_len > n {
      raise XmlSyntax("truncated zip local header", at)
    }
    let content_start = name_start + name_len + extra_len
    if content_start + comp_size > n {
      raise XmlSyntax("truncated zip content", at)
    }
    out.push({
      name: @utf8.decode_lossy(
        data.clamped_view(start=name_start, end=name_start + name_len),
      ),
      content_start,
      comp_size,
      compression,
    })
    at = content_start + comp_size
  }
  out
}

///|
/// 解出某条记录的内容(按需 inflate)。
fn zip_record_data(data : Bytes, record : ZipRecord) -> Bytes raise KmlError {
  let raw = data.clamped_view(
    start=record.content_start,
    end=record.content_start + record.comp_size,
  )
  if record.compression == 0 {
    return Bytes::from_array(raw.to_array())
  }
  if record.compression == 8 {
    return @flate.inflate_all(raw) catch {
      _ => raise XmlSyntax("inflate failed", record.content_start)
    }
  }
  raise XmlSyntax("unsupported zip compression", record.content_start)
}

///|
/// 列出 KMZ/ZIP 里的全部条目名。
pub fn kmz_entry_names(data : Bytes) -> Array[String] raise KmlError {
  let records = zip_records(data)
  let out : Array[String] = []
  let mut i = 0
  while i < records.length() {
    out.push(records[i].name)
    i = i + 1
  }
  out
}

///|
/// 取出 KMZ/ZIP 里指定条目的内容。
pub fn kmz_extract(data : Bytes, name : String) -> Bytes? raise KmlError {
  let records = zip_records(data)
  let mut i = 0
  while i < records.length() {
    if records[i].name == name {
      return Some(zip_record_data(data, records[i]))
    }
    i = i + 1
  }
  None
}

///|
/// 从 ZIP 里找第一个指定名字的条目内容。
pub fn zip_find(data : Bytes, name : String) -> Bytes? raise KmlError {
  kmz_extract(data, name)
}

///|
/// 把 KML 文档与附加资源打包成 KMZ。
pub fn kmz_pack_with(doc : KmlDocument, extra : Array[ZipEntry]) -> Bytes {
  let entries : Array[ZipEntry] = [
    { name: "doc.kml", data: @utf8.encode(serialize(doc)), },
  ]
  let mut i = 0
  while i < extra.length() {
    entries.push(extra[i])
    i = i + 1
  }
  zip_pack(entries)
}

///|
/// 把 KML 文档打包成 KMZ(仅含 doc.kml)。
pub fn kmz_pack(doc : KmlDocument) -> Bytes {
  kmz_pack_with(doc, [])
}

///|
/// 从 KMZ 字节解出 KML 文档:优先 doc.kml,缺失时取第一个 .kml 条目。
pub fn kmz_parse(data : Bytes) -> KmlDocument raise KmlError {
  match kmz_extract(data, "doc.kml") {
    Some(kml) => parse(@utf8.decode_lossy(kml))
    None => {
      let names = kmz_entry_names(data)
      let mut i = 0
      while i < names.length() {
        if names[i].has_suffix(".kml") {
          match kmz_extract(data, names[i]) {
            Some(kml) => return parse(@utf8.decode_lossy(kml))
            None => ()
          }
        }
        i = i + 1
      }
      raise BadCoordinates("missing doc.kml")
    }
  }
}