///|
/// 解析一个字节流时能出错的每一种方式。
///
/// 每条错误都带定位信息。报"坏了"不够,要能指出坏在哪里,
/// 否则用户拿到的是一个无法追问的失败:
/// 读字节流时错的都带字节偏移,回写时错的都带所在条目的 tag。
pub suberror MetaError {
  /// 字节流比声明的长度短:(偏移, 需要, 实有)
  /// 偏移是这次读取想落在的位置;它已经落在块外时实有记 0。
  TooShort(Int, Int, Int)
  /// TIFF 头不在给定偏移处:(偏移)
  BadTiffHeader(Int)
  /// 字节序标记既不是 "II" 也不是 "MM":(偏移)
  BadByteOrder(Int)
  /// IFD 偏移指向块外:(偏移)
  IfdOffsetOutOfRange(Int64)
  /// 条目声明的字节数超过剩余空间:(偏移, tag, 声明, 可用)
  /// 偏移与可用配对:偏移 + 可用 = 块长。整张目录表放不下时没有具体条目,
  /// tag 记 0,偏移就是表体的第一字节。
  EntryTooLarge(Int, Int, Int, Int)
  /// 条目的值偏移落在块外:(tag, 偏移)
  ValueOffsetOutOfRange(Int, Int64)
  /// 规范里有、但本版本还不打算解的类型:(tag, 类型码)
  UnsupportedType(Int, Int)
  /// 子 IFD 指针非法:(父 tag, 偏移)
  SubIfdOutOfRange(Int, Int64)
  /// 回写时文本值含有无法映射成单字节的码点:(tag, 码点)
  UnencodableText(Int, Int)
  /// 回写时声明的类型与值的形状不配对:(tag, 类型码)
  TypeMismatch(Int, Int)
  /// 回写时数值超出该类型码的宽度:(tag, 值)
  ValueOutOfRange(Int, Int64)
  /// 文件开头不是 JPEG 的 SOI:(偏移)
  NotJpeg(Int)
  /// 段声明的长度超出文件剩余:(段偏移, 声明, 可用)
  SegmentTooLarge(Int, Int, Int)
  /// 走到文件尾也没读到段码:(偏移)
  TruncatedAt(Int)
  /// 回写时段长字段放不下这份载荷:(段偏移, 上限, 需要)
  SegmentOverflow(Int, Int, Int)
  /// 还有第二份元数据是本版本够不着的(重复 APP1,或藏在压缩流之后):(偏移)
  ExtraMetadata(Int)
  /// 文件开头不是 PNG 的 8 字节签名:(偏移)
  NotPng(Int)
  /// PNG 块的 CRC 与字节对不上:(块偏移, 声明, 实算)
  BadChecksum(Int, Int64, Int64)
  /// 裸 TIFF 的字节不止元数据:重建只能覆盖一部分,写回去就是丢掉其余
  /// (通常是像素数据)。拒绝写回:(IFD0 偏移, 文件长度, 重建长度)
  TiffNotByteExact(Int64, Int, Int)
  /// 原位模式要清/要写的一段字节与"仍在被人使用"的区间重叠,拒绝:
  /// (条目 tag,冲突区间起点,区间长度,被撞那段所属目录表的偏移,
  ///  那段区间是什么的一句话)。
  /// tag 记 0 表示冲突的是表体本身(搬槽位或清表尾),不是某条值。
  TiffInplaceShared(Int, Int, Int, Int, String)
  /// 原位模式走目录图时又遇到了已经走过的表:(该表偏移)。
  /// 两份表叠在一起,改哪一份都对,但没人说得清哪一份是真的。
  TiffInplaceCycle(Int)
  /// 原位模式读不懂像素声明,拒绝:(条目 tag,读不懂哪一种)。
  /// 第二个参数是哨兵码,不是 TIFF 类型码:0 = 配对的条目不在,
  /// -1 = 配对的两条各数出不同个数的值,-2 = 声明的区间越出文件末尾;
  /// 其余才是这条值的 TIFF 类型码(值压根不是偏移表)。
  TiffInplaceOpaque(Int, Int)
  /// 开头既不是 JPEG 也不是 PNG 也不是 TIFF:(偏移)
  UnknownFormat(Int)
} derive(Debug)

///|
pub impl Show for MetaError with fn output(self : MetaError, logger) {
  match self {
    TooShort(off, need, have) =>
      logger.write_string(
        "need \{need} byte(s) at offset \{off} but the block holds only \{have}",
      )
    BadTiffHeader(off) =>
      logger.write_string("no TIFF header ('II' or 'MM') at offset \{off}")
    BadByteOrder(off) =>
      logger.write_string(
        "byte-order mark at offset \{off} is neither 'II' nor 'MM'",
      )
    IfdOffsetOutOfRange(off) =>
      logger.write_string("IFD offset \{off} points outside the block")
    EntryTooLarge(off, tag, declared, available) =>
      logger.write_string(
        "tag \{tag_hex(tag)} at offset \{off} declares \{declared} byte(s), only \{available} left",
      )
    ValueOffsetOutOfRange(tag, off) =>
      logger.write_string(
        "tag \{tag_hex(tag)} points to offset \{off}, outside the block",
      )
    UnsupportedType(tag, code) =>
      logger.write_string(
        "tag \{tag_hex(tag)} uses TIFF type \{code}, which this version does not decode",
      )
    SubIfdOutOfRange(parent, off) =>
      logger.write_string(
        "sub-IFD pointer of tag \{tag_hex(parent)} points to offset \{off}, outside the block",
      )
    UnencodableText(tag, cp) =>
      logger.write_string(
        "tag \{tag_hex(tag)} holds U+\{cp}, which has no single-byte ISO-8859-1 form",
      )
    TypeMismatch(tag, code) =>
      logger.write_string(
        "tag \{tag_hex(tag)} declares TIFF type \{code} but its value has a different shape",
      )
    ValueOutOfRange(tag, v) =>
      logger.write_string(
        "tag \{tag_hex(tag)} holds \{v}, which does not fit its declared TIFF type",
      )
    NotJpeg(off) =>
      logger.write_string(
        "no JPEG start-of-image marker (0xffd8) at offset \{off}",
      )
    SegmentTooLarge(off, declared, available) =>
      logger.write_string(
        "segment at offset \{off} declares \{declared} byte(s), only \{available} left in the file",
      )
    TruncatedAt(off) =>
      logger.write_string(
        "the file runs out at offset \{off}, in the middle of a segment table",
      )
    SegmentOverflow(off, limit, needed) =>
      logger.write_string(
        "the segment at offset \{off} can hold \{limit} byte(s); this payload needs \{needed}",
      )
    ExtraMetadata(off) =>
      logger.write_string(
        "a second metadata block sits at offset \{off}; removing only the first one would be a fake redaction",
      )
    NotPng(off) =>
      logger.write_string(
        "not a PNG: the 8-byte signature differs at offset \{off}",
      )
    BadChecksum(off, declared, computed) =>
      logger.write_string(
        "chunk at offset \{off} declares CRC \{hex32(declared)} but its bytes hash to \{hex32(computed)}",
      )
    TiffNotByteExact(ifd0, len, rebuilt) =>
      logger.write_string(
        "refusing to rewrite this bare TIFF: its IFD0 sits at offset \{ifd0}, and the file's \{len} byte(s) are not what the metadata model rebuilds (\{rebuilt} byte(s)); the difference is bytes the model does not hold, and writing would drop them",
      )
    TiffInplaceShared(tag, from, len, owner, what) =>
      logger.write_string(
        "refusing to write this bare TIFF in place: tag \{tag_hex(tag)} would write offsets \{from}..\{from + len}, but those bytes are already \{what} (of the table at offset \{owner}); in-place mode never moves bytes, and it will not guess which of the two uses is the real one",
      )
    TiffInplaceCycle(off) =>
      logger.write_string(
        "refusing to write this bare TIFF in place: the table at offset \{off} is reached twice, so the file holds two readings of the same bytes and there is no way to say which one a viewer would use",
      )
    TiffInplaceOpaque(tag, code) =>
      logger.write_string(
        "refusing to write this bare TIFF in place: tag \{tag_hex(tag)} declares where the pixel data sits, but \{opaque_form(code)}; guessing wrong would delete the image, so the file is left untouched",
      )
    UnknownFormat(off) =>
      logger.write_string(
        "none of the JPEG, PNG or TIFF magics is at offset \{off}; the container cannot be identified",
      )
  }
}

///|
pub extend MetaError with @debug.Debug::{to_repr}

///|
pub extend MetaError with Show::{to_string, output}

///|
fn tag_hex(tag : Int) -> String {
  "0x" +
  ((tag >> 8) & 0xff).to_byte().to_hex() +
  (tag & 0xff).to_byte().to_hex()
}

///|
/// 原位模式拒绝时,那句"看不懂"要说清是哪一种看不懂。第二个参数是哨兵码,
/// 不是 TIFF 类型码:0/-1/-2 分别对应缺半边、伙伴条数不等、声明越出文件末尾,
/// 只有其余取值才是这条值的类型码。把哨兵当类型码打印成"类型 0"会误导,
/// 把三种读不懂压成一句更糟——那是在替一个没发生的状况编理由。
///
/// 越出文件末尾这一种,最想说出口的是"越到了哪个数"。一个哨兵位装不下它,
/// 装下它要给 `TiffInplaceOpaque` 加字段(公开 API 与 `.mbti` 跟着动)。
/// 这里先只把"是哪一种"说准,宁可少说一个数,不拿"值不是偏移表"这种
/// 听着具体、其实没发生的句子顶岗。
fn opaque_form(code : Int) -> String {
  if code == 0 {
    "its partner entry is missing"
  } else if code == -1 {
    "its partner entry lists a different number of values"
  } else if code == -2 {
    "the pixel range it declares runs past the end of the file"
  } else {
    "its value is TIFF type \{code}, not a list of byte offsets"
  }
}

///|
/// 32 位无符号数写成 `0x........`。CRC 会超过 31 位,所以一路用 Int64。
fn hex32(v : Int64) -> String {
  let b0 = ((v >> 24) & 255L).to_byte().to_hex()
  let b1 = ((v >> 16) & 255L).to_byte().to_hex()
  let b2 = ((v >> 8) & 255L).to_byte().to_hex()
  let b3 = (v & 255L).to_byte().to_hex()
  "0x" + b0 + b1 + b2 + b3
}