// flac_picture.mbt
//
// FLAC PICTURE 块(块类型 6)与 Vorbis Comment 的 `METADATA_BLOCK_PICTURE`
// 共用同一套载荷布局(xiph.org 的 picture 规范):
//
//   u32be 图片类型(与 ID3v2 APIC 的类型码同源,3 = 封面正面)
//   u32be MIME 长度 + MIME(ASCII;引用外部 URL 时为 "-->")
//   u32be 描述长度 + 描述(UTF-8)
//   u32be 宽 / 高 / 颜色深度 / 调色板颜色数
//   u32be 图片数据长度 + 图片数据
//
// 与 ID3v2 APIC 的差别是最容易踩的点:这里**没有**文本编码字节,也**没有**
// $00 终止符,全部字段都是长度前缀;因此不能复用 APIC 的解析器。

///|
/// Vorbis Comment 中存封面的标准键名——Ogg Vorbis / Opus 唯一的封面方式。
pub const METADATA_BLOCK_PICTURE : String = "METADATA_BLOCK_PICTURE"

///|
/// 从图片数据里认出宽高(PNG 的 IHDR、JPEG 的 SOFn 段)。
///
/// 拿不到就返回 `None`,调用方写 0 表示"未知"——规范允许 0,
/// 但能让播放器/管理器直接显示尺寸显然更好。
fn image_dimensions(
  mime : String,
  data : BytesView,
) -> (Int32, Int32)? raise TagError {
  let lower = mime.to_lower()
  let is_png = lower == "image/png" || lower == "png"
  let is_jpeg = lower == "image/jpeg" ||
    lower == "image/jpg" ||
    lower == "jpeg" ||
    lower == "jpg"
  if is_png && data.length() >= 24 {
    // 8 字节签名 + 4 字节块长 + "IHDR" + 宽(u32be) + 高(u32be)
    if data.at(12) == b'I' &&
      data.at(13) == b'H' &&
      data.at(14) == b'D' &&
      data.at(15) == b'R' {
      let w = read_u32be(data, 16)
      let h = read_u32be(data, 20)
      return Some((w, h))
    }
    return None
  }
  if is_jpeg && data.length() > 4 {
    if data.at(0).to_int() != 0xFF || data.at(1).to_int() != 0xD8 {
      return None
    }
    let mut pos = 2
    while pos + 9 < data.length() {
      if data.at(pos).to_int() != 0xFF {
        pos = pos + 1
        continue
      }
      let marker = data.at(pos + 1).to_int()
      // SOF0..SOF3、SOF5..SOF7、SOF9..SOF11、SOF13..SOF15 都带尺寸
      let is_sof = (marker >= 0xC0 && marker <= 0xC3) ||
        (marker >= 0xC5 && marker <= 0xC7) ||
        (marker >= 0xC9 && marker <= 0xCB) ||
        (marker >= 0xCD && marker <= 0xCF)
      if is_sof {
        // 段内布局:长度(2) 精度(1) 高(2) 宽(2)
        let h = read_be(data, pos + 5, 2)
        let w = read_be(data, pos + 7, 2)
        return Some((w, h))
      }
      if marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7) {
        pos = pos + 2
        continue
      }
      let seg = read_be(data, pos + 2, 2)
      if seg <= 0 {
        return None
      }
      pos = pos + 2 + seg
    }
    return None
  }
  None
}

///|
/// 解析 FLAC PICTURE 块载荷(同样用于 Base64 解码后的 METADATA_BLOCK_PICTURE)。
pub fn parse_flac_picture(payload : BytesView) -> Picture raise TagError {
  if payload.length() < 8 {
    raise BadFrameData(
      frame_id="PICTURE",
      reason="载荷长度 \{payload.length()} 不足 8 字节",
    )
  }
  let raw_type = read_u32be(payload, 0)
  let mime_len = read_u32be(payload, 4)
  require_range(payload, 8, mime_len)
  let mime = read_ascii(payload, 8, 8 + mime_len)
  let desc_len_off = 8 + mime_len
  require_range(payload, desc_len_off, 4)
  let desc_len = read_u32be(payload, desc_len_off)
  let desc_off = desc_len_off + 4
  require_range(payload, desc_off, desc_len)
  let description = @utf8.decode_lossy(
    payload.exact_view(start=desc_off, end=desc_off + desc_len),
  )
  // 宽/高/深度/调色板四个 u32 必须存在,即使全是 0
  let geom_off = desc_off + desc_len
  require_range(payload, geom_off, 16)
  let data_len_off = geom_off + 16
  require_range(payload, data_len_off, 4)
  let data_len = read_u32be(payload, data_len_off)
  let data_off = data_len_off + 4
  require_range(payload, data_off, data_len)
  let bytes = slice_bytes(payload, data_off, data_off + data_len)
  {
    mime,
    picture_type: PictureType::from_code(raw_type),
    raw_type,
    description,
    data: bytes,
  }
}

///|
/// 序列化 FLAC PICTURE 块载荷。
///
/// 宽/高/深度从图片数据本身推出来(PNG/JPEG),推不出时写 0 表示未知;
/// 调色板颜色数写 0(真彩色图片不适用)。
pub fn serialize_flac_picture(pic : Picture) -> Bytes raise TagError {
  let mime = @utf8.encode(pic.mime)
  let desc = @utf8.encode(pic.description)
  let (w, h) = match image_dimensions(pic.mime, pic.data.exact_view()) {
    Some((w, h)) => (w, h)
    None => (0, 0)
  }
  let depth : Int32 = if w > 0 { 24 } else { 0 }
  let buf = @buffer.Buffer()
  buf.write_bytes(write_u32be(pic.raw_type))
  buf.write_bytes(write_u32be(mime.length()))
  buf.write_bytes(mime)
  buf.write_bytes(write_u32be(desc.length()))
  buf.write_bytes(desc)
  buf.write_bytes(write_u32be(w))
  buf.write_bytes(write_u32be(h))
  buf.write_bytes(write_u32be(depth))
  buf.write_bytes(write_u32be(0))
  buf.write_bytes(write_u32be(pic.data.length()))
  buf.write_bytes(pic.data)
  buf.to_bytes()
}

///|
/// 尝试把一条 Base64 注释值解析成图片;任何一步失败都返回 `None` 而不是抛出。
fn try_picture_from_base64(value : String) -> Picture? {
  let bytes : Bytes? = Some(base64_decode(value)) catch { _ => None }
  match bytes {
    None => None
    Some(b) => Some(parse_flac_picture(b.exact_view())) catch { _ => None }
  }
}

///|
/// 从 FLAC 文件取封面(原生 PICTURE 元数据块,块类型 6)。
///
/// 与 comment 里的 `METADATA_BLOCK_PICTURE` 是两套并存的机制:FLAC 原生用块,
/// Ogg 只能用 comment。这里取第一个 PICTURE 块;没有块返回 `None`。
/// 注意块载荷是**不带块头**的 PICTURE 结构,所以直接喂给同一套解析器。
pub fn flac_picture(data : BytesView) -> Picture? raise TagError {
  let meta = parse_meta(data)
  let block = match meta.block(Picture) {
    None => return None
    Some(b) => b
  }
  let payload = slice_bytes(data, block.offset, block.offset + block.length)
  Some(parse_flac_picture(payload.exact_view())) catch {
    _ => None
  }
}

///|
/// 从 Vorbis Comment 取封面(Ogg 与 FLAC 的 comment 路径共用)。
///
/// 取第一张能解析出来的 `METADATA_BLOCK_PICTURE`;条目存在但解不出来的,
/// 跳过继续找下一张,全都不行才返回 `None`——封面损坏不该让整个标签读不出。
pub fn vorbis_picture(vc : VorbisComment) -> Picture? {
  for v in vc.get_all(METADATA_BLOCK_PICTURE) {
    match try_picture_from_base64(v) {
      Some(p) => return Some(p)
      None => ()
    }
  }
  None
}

///|
/// 写入 / 删除 Vorbis Comment 里的封面。
///
/// `None` 表示删除全部 `METADATA_BLOCK_PICTURE` 条目。
pub fn set_vorbis_picture(
  vc : VorbisComment,
  pic : Picture?,
) -> VorbisComment raise TagError {
  match pic {
    None => vc.set_key(METADATA_BLOCK_PICTURE, "")
    Some(p) => {
      let payload = serialize_flac_picture(p)
      vc.set_key(METADATA_BLOCK_PICTURE, base64_encode(payload.exact_view()))
    }
  }
}