///|
/// CLUT 型 tag(`mft1` / `mft2`)的解析与求值。
///
/// ## 两种类型的布局
///
/// `mft2`(lut16Type,ICC v2 常用):
///
/// | 偏移   | 大小 | 内容 |
/// |--------|------|------|
/// | 0..3   | 4    | `mft2` |
/// | 4..7   | 4    | 保留 |
/// | 8      | 1    | 输入通道数 |
/// | 9      | 1    | 输出通道数 |
/// | 10     | 1    | CLUT 网格点数 |
/// | 11     | 1    | 保留 |
/// | 12..47 | 36   | 矩阵 e1..e9(9 × s15Fixed16) |
/// | 48..49 | 2    | 输入表项数 |
/// | 50..51 | 2    | 输出表项数 |
/// | 52..   |      | 输入表(ic × in_entries × u16) |
/// |        |      | CLUT(gc^ic × oc × u16) |
/// |        |      | 输出表(oc × out_entries × u16) |
///
/// `mft1`(lut8Type)结构相同,但表项数固定为 256、表值类型为 u8,
/// 因此没有 48..51 的表项数字段,表直接从 48 开始。
///
/// ## 这条布局是实测确定的,不是照记忆写的
///
/// 起初按"表项数在 12..15、矩阵在 16..51"实现,结果与声明长度对不上
/// (差 2052 字节)。改用本文件记录的布局后:
/// `52 + 4×256×2 + 9^4×3×2 + 3×2×2 = 41478`,与本机 CMYK 打印机 profile
/// 的 A2B0 声明长度**精确相等**;且 12..47 处读到的是标准单位矩阵
/// `[1,0,0,0,1,0,0,0,1]`,48..51 读到的是 256 / 2。
///
/// ## 处理顺序
///
/// 输入表 → 矩阵(仅当输入通道数为 3)→ CLUT(多线性插值)→ 输出表。
///
/// 注意:本机可用的 LUT 型 profile 其矩阵均为单位矩阵,因此"矩阵在 CLUT
/// 之前还是之后"这一顺序**未被测试证据覆盖**。这里按 ICC 规格的字段顺序
/// 实现(矩阵先于 CLUT),若有反例应补充测试。

///|
/// 一个已解析的 CLUT 型 tag。
pub struct Clut {
  /// 输入通道数。
  input_channels : Int32
  /// 输出通道数。
  output_channels : Int32
  /// 网格点数(每个输入维度)。
  grid_points : Int32
  /// 表值位深:8(`mft1`)或 16(`mft2`)。
  ///|
  ///| 必须保留这个字段:PCS 输出的解码方式取决于它。
  ///| 例如 v2 的 Lab 编码在 16 位下上限是 0xFF00,在 8 位下上限是 0xFF。
  bit_depth : Int32
  /// 矩阵(行主序 9 项);仅当 `input_channels == 3` 时使用。
  matrix : Array[Double]
  /// 输入表,按通道连续存放,值已归一化到 [0,1]。空数组表示恒等。
  input_tables : Array[Double]
  /// 每个通道的输入表项数(0 或 1 表示无表)。
  input_entries : Int32
  /// CLUT 节点值,按 (输入格点组合, 输出通道) 连续存放,归一化到 [0,1]。
  clut : Array[Double]
  /// 输出表,按通道连续存放,归一化到 [0,1]。空数组表示恒等。
  output_tables : Array[Double]
  /// 每个通道的输出表项数(0 或 1 表示无表)。
  output_entries : Int32
}

///|
/// 解析 `mft1` 或 `mft2` tag。其他类型报错。
pub fn parse_clut_tag(data : Bytes, entry : IccTagEntry) -> Clut raise IccError {
  let (type_sig, body_at) = tag_type_and_data(data, entry)
  match type_sig {
    "mft1" => parse_lut(data, entry, body_at, 8)
    "mft2" => parse_lut(data, entry, body_at, 16)
    _ =>
      raise BadTagData(
        tag=entry.signature,
        reason="期望 LUT 类型 \"mft1\" 或 \"mft2\",实际 \"\{type_sig}\"",
      )
  }
}

///|
/// 解析 LUT 公共部分。
///
/// `bit_depth` 为 8(mft1)或 16(mft2)。
fn parse_lut(
  data : Bytes,
  entry : IccTagEntry,
  body_at : Int32,
  bit_depth : Int32,
) -> Clut raise IccError {
  let r = BigEndianReader::new(data)
  r.seek(body_at)
  let input_channels = r.read_u8()
  let output_channels = r.read_u8()
  let grid_points = r.read_u8()
  // 保留位
  r.skip(1)
  if input_channels < 1 || output_channels < 1 {
    raise BadTagData(
      tag=entry.signature,
      reason="通道数非法: 输入 \{input_channels} 输出 \{output_channels}",
    )
  }
  if grid_points < 2 {
    raise BadTagData(
      tag=entry.signature,
      reason="CLUT 网格点数 \{grid_points} 小于 2,无法做插值",
    )
  }
  // 矩阵:9 个 s15Fixed16
  let matrix : Array[Double] = []
  for i = 0; i < 9; i = i + 1 {
    matrix.push(r.read_s15f16())
  }
  // 表项数:mft1 固定 256,mft2 显式给出
  let (input_entries, output_entries) = if bit_depth == 8 {
    (256, 256)
  } else {
    let ie = r.read_u16()
    let oe = r.read_u16()
    (ie, oe)
  }
  // 输入表
  let input_tables = read_table(r, input_channels, input_entries, bit_depth)
  // CLUT
  // CLUT 节点数 = gc^ic × oc。用循环计算并设上限:
  // profile 是外部不可信输入,畸形的通道数/网格点数会试图分配巨量内存。
  let mut nodes_per_output = 1
  for i = 0; i < input_channels; i = i + 1 {
    nodes_per_output = nodes_per_output * grid_points
    if nodes_per_output > 1_000_000 {
      raise BadTagData(
        tag=entry.signature,
        reason="CLUT 节点数超过上限(网格 \{grid_points}^\{input_channels}),疑似畸形 profile",
      )
    }
  }
  let node_count = nodes_per_output * output_channels
  let clut : Array[Double] = []
  for i = 0; i < node_count; i = i + 1 {
    clut.push(read_table_value(r, bit_depth))
  }
  // 输出表
  let output_tables = read_table(r, output_channels, output_entries, bit_depth)
  {
    input_channels,
    output_channels,
    grid_points,
    bit_depth,
    matrix,
    input_tables,
    input_entries,
    clut,
    output_tables,
    output_entries,
  }
}

///|
/// 读一张表(`channels` 个通道,每通道 `entries` 项),归一化到 [0,1]。
///
/// `entries` 为 0 或 1 表示该表不参与运算(恒等),返回空数组。
fn read_table(
  r : BigEndianReader,
  channels : Int32,
  entries : Int32,
  bit_depth : Int32,
) -> Array[Double] raise IccError {
  if entries <= 1 {
    return []
  }
  let out : Array[Double] = []
  for c = 0; c < channels; c = c + 1 {
    for i = 0; i < entries; i = i + 1 {
      out.push(read_table_value(r, bit_depth))
    }
  }
  out
}

///|
/// 读一个表项并归一化。
fn read_table_value(
  r : BigEndianReader,
  bit_depth : Int32,
) -> Double raise IccError {
  if bit_depth == 8 {
    r.read_u8().to_double() / 255.0
  } else {
    r.read_u16().to_double() / 65535.0
  }
}

///|
/// 在等间距表上做线性插值取值。
fn interpolate_table(
  table : Array[Double],
  channel : Int32,
  entries : Int32,
  x : Double,
) -> Double {
  let base = channel * entries
  let v = clamp01(x)
  if entries <= 1 {
    return v
  }
  let pos = v * (entries - 1).to_double()
  let i = pos.to_int()
  if i >= entries - 1 {
    return table[base + entries - 1]
  } else {
    let frac = pos - i.to_double()
    table[base + i] * (1.0 - frac) + table[base + i + 1] * frac
  }
}

///|
/// **求值**:把输入值(0..1)变换为输出值(0..1)。
///
/// 顺序:输入表 → 矩阵(仅 3 输入通道时)→ CLUT 多线性插值 → 输出表。
pub fn Clut::evaluate(self : Clut, inputs : Array[Double]) -> Array[Double] {
  let ic = self.input_channels
  let oc = self.output_channels
  // 1. 输入表
  let stage : Array[Double] = []
  for i = 0; i < ic; i = i + 1 {
    let v = if i < inputs.length() { inputs[i] } else { 0.0 }
    stage.push(interpolate_table(self.input_tables, i, self.input_entries, v))
  }
  // 2. 矩阵(仅当输入通道数为 3;ICC 规定矩阵只作用于 3 通道)
  let after_matrix = if ic == 3 && self.matrix.length() == 9 {
    let x = clamp01(stage[0])
    let y = clamp01(stage[1])
    let z = clamp01(stage[2])
    [
      clamp01(self.matrix[0] * x + self.matrix[1] * y + self.matrix[2] * z),
      clamp01(self.matrix[3] * x + self.matrix[4] * y + self.matrix[5] * z),
      clamp01(self.matrix[6] * x + self.matrix[7] * y + self.matrix[8] * z),
    ]
  } else {
    stage
  }
  // 3. CLUT 多线性插值
  let lut_out = self.interpolate_clut(after_matrix)
  // 4. 输出表
  let out : Array[Double] = []
  for i = 0; i < oc; i = i + 1 {
    out.push(
      interpolate_table(self.output_tables, i, self.output_entries, lut_out[i]),
    )
  }
  out
}

///|
/// 在 CLUT 网点上做多线性插值。
///
/// 对每个输入维度计算 `位置 = v * (gc-1)`、取整索引与小数部分,
/// 然后遍历 `2^ic` 个角点,按小数部分组合权重。
/// 步长:最后一维变化最快(步长 1 个节点),依次向前乘 `gc`。
fn Clut::interpolate_clut(self : Clut, values : Array[Double]) -> Array[Double] {
  let ic = self.input_channels
  let oc = self.output_channels
  let gc = self.grid_points
  // 各维的基点与小数部分
  let base : Array[Int32] = []
  let frac : Array[Double] = []
  for i = 0; i < ic; i = i + 1 {
    let v = clamp01(values[i])
    let pos = v * (gc - 1).to_double()
    let mut b = pos.to_int()
    if b > gc - 2 {
      b = gc - 2
    }
    if b < 0 {
      b = 0
    }
    base.push(b)
    frac.push(pos - b.to_double())
  }
  // 各维步长(以节点为单位)
  let stride : Array[Int32] = []
  for i = 0; i < ic; i = i + 1 {
    let mut s = 1
    for j = i + 1; j < ic; j = j + 1 {
      s = s * gc
    }
    stride.push(s)
  }
  let out : Array[Double] = []
  for c = 0; c < oc; c = c + 1 {
    out.push(0.0)
  }
  // 遍历 2^ic 个角点
  let corners = 1 << ic
  for mask = 0; mask < corners; mask = mask + 1 {
    let mut weight = 1.0
    let mut node = 0
    for i = 0; i < ic; i = i + 1 {
      let bit = (mask >> i) & 1
      if bit == 1 {
        weight = weight * frac[i]
        node = node + (base[i] + 1) * stride[i]
      } else {
        weight = weight * (1.0 - frac[i])
        node = node + base[i] * stride[i]
      }
    }
    if weight != 0.0 {
      for c = 0; c < oc; c = c + 1 {
        out[c] = out[c] + weight * self.clut[node * oc + c]
      }
    }
  }
  out
}

///|
/// 把 CLUT 的输出(按位深满量程归一化到 [0,1])解码为 PCS Lab。
///
/// **ICC v2 与 v4 的 Lab 编码范围不同**,这是实现 LUT 时最容易出错的地方:
///
/// | 版本 | 编码 | L 满量程 | a/b 满量程 |
/// |------|------|----------|-----------|
/// | v2 | 16 位 | `0xFF00` = 65280 | `0xFF00` |
/// | v2 | 8 位 | `0xFF` = 255 | `0xFF` |
/// | v4 | 16 位 | `0xFFFF` = 65535 | `0xFFFF` |
///
/// 因此需要同时知道**位深**与**版本**:8 位表值本身就是满量程,
/// 不需要乘回 65535;16 位表值要先乘回 65535 再按版本的上限折算。
///
/// 实测依据:本机 CMYK 打印机 profile(v2.0)的 A2B1 首个 CLUT 节点为
/// `(65533, 33024, 33022)`。按 v2 解码得 Lab ≈ (100.39, 1.00, 0.99),
/// 与 lcms2 在 K=0 处的 (100.00, 0.00, 0.00) 吻合;按 v4 解码得
/// (100.00, 0.50, 0.49),a/b 偏差明显更大。
pub fn clut_output_to_lab(
  values : Array[Double],
  bit_depth : Int32,
  is_v2 : Bool,
) -> Lab {
  // 只对**原始比例**做 [0,1] 夹紧(即限制在 0..满位深)。
  //
  // 注意不能在折算之后再夹到 1.0:v2 的 Lab 编码上限是 0xFF00,
  // 但 profile 里合法地存到 0xFFFF(表示略高于 100 的 L)。
  // 若在折算后夹紧,L=65533 这类值会被截成 100.0。
  // 本项目最初正是这样写的,网格节点判据立刻抓到了它
  // (期望 100.387561,实得 100)。
  let fraction = fn(v : Double) -> Double {
    let x = clamp01(v)
    if bit_depth == 8 {
      // 8 位表值直接就是该版本的上限
      x
    } else if is_v2 {
      // 16 位表值 0..65535,v2 的名义上限是 65280
      x * 65535.0 / 65280.0
    } else {
      x
    }
  }
  {
    l: fraction(values[0]) * 100.0,
    // a/b 编码:0..1 线性映射到 -128..127
    a: fraction(values[1]) * 255.0 - 128.0,
    b: fraction(values[2]) * 255.0 - 128.0,
  }
}

///|
/// 把 Lab 编码到 CLUT 输入域(`clut_output_to_lab` 的逆)。
pub fn lab_to_clut_input(
  lab : Lab,
  bit_depth : Int32,
  is_v2 : Bool,
) -> Array[Double] {
  let unscale = fn(f : Double) -> Double {
    if bit_depth == 8 {
      f
    } else if is_v2 {
      f * 65280.0 / 65535.0
    } else {
      f
    }
  }
  [
    unscale(clamp01(lab.l / 100.0)),
    unscale(clamp01((lab.a + 128.0) / 255.0)),
    unscale(clamp01((lab.b + 128.0) / 255.0)),
  ]
}

///|
/// 把 CLUT 的输出(归一化到 [0,1])解码为 PCS XYZ。
///
/// **XYZ 的 PCS 编码是 `u1Fixed15Number`:1.0 = `0x8000`,上限约 1.99997**
/// (`0xFFFF` 表示 1 + 32767/32768),且 **v2 与 v4 相同**——
/// 这一点与 Lab 不同(Lab 的 v2/v4 上限分别是 `0xFF00` 与 `0xFFFF`)。
///
/// 因此 16 位表值要乘 `65535/32768`,8 位表值要乘 `255/128`。
/// 实测依据:用一个合成的 XYZ-PCS LUT profile(`testdata/xyz_lut.icc`,
/// 节点值 `(i,j,k)×32768`)与 lcms2 对拍,输出恰为 `device/1.99997…`,
/// 逐点偏差 0 级;若误按满量程 `0xFFFF` 解释,结果会整体差 2 倍。
pub fn clut_output_to_xyz(
  values : Array[Double],
  bit_depth : Int32,
) -> XyzNumber {
  let scale = fn(v : Double) -> Double {
    let f = clamp01(v)
    if bit_depth == 8 {
      f * 255.0 / 128.0
    } else {
      f * 65535.0 / 32768.0
    }
  }
  { x: scale(values[0]), y: scale(values[1]), z: scale(values[2]), }
}

///|
/// 把 PCS XYZ 编码到 CLUT 输入域(`clut_output_to_xyz` 的逆)。
pub fn xyz_to_clut_input(xyz : XyzNumber, bit_depth : Int32) -> Array[Double] {
  let unscale = fn(v : Double) -> Double {
    if bit_depth == 8 {
      clamp01(v * 128.0 / 255.0)
    } else {
      clamp01(v * 32768.0 / 65535.0)
    }
  }
  [unscale(xyz.x), unscale(xyz.y), unscale(xyz.z)]
}

///|
/// 该 profile 是否使用 ICC v2 的 PCS 编码。
///
/// ICC 的版本从 2 直接跳到 4,没有 v3。因此主版本号 <= 2 即视为 v2。
/// 这个判断影响 Lab 的解码范围(见 `clut_output_to_lab`)。
pub fn Profile::is_v2(self : Profile) -> Bool {
  self.header.major_version <= 2
}

///|
/// 按渲染意图选取 **设备 → PCS** 的 LUT tag 签名。
///
/// ICC 的编号约定:
///
/// | 意图 | 编号 | tag |
/// |------|------|-----|
/// | 感知 | 0 | `A2B0` |
/// | 媒体相对比色 | 1 | `A2B1` |
/// | 饱和度 | 2 | `A2B2` |
/// | 绝对比色 | 3 | 无独立 tag,用 `A2B1` 再加白点调整 |
///
/// 实测依据:本机 CMYK 打印机 profile 的 A2B0 与 A2B1 **内容不同**
/// (逐字节比较有 26539 处差异,全部落在 CLUT 区)。用 lcms2 以 intent=1
/// 变换时只有 A2B1 的节点值与之一致——若误用 A2B0,暗端会差约 5 个 L 单位。
pub fn intent_to_a2b_tag(intent : Int32) -> String {
  match intent {
    0 => "A2B0"
    2 => "A2B2"
    // 相对比色与绝对比色都用 A2B1(绝对比色需另做白点调整,本库未实现)
    _ => "A2B1"
  }
}

///|
/// 按渲染意图选取 **PCS → 设备** 的 LUT tag 签名。
pub fn intent_to_b2a_tag(intent : Int32) -> String {
  match intent {
    0 => "B2A0"
    2 => "B2A2"
    _ => "B2A1"
  }
}

///|
/// 取出设备 → PCS 的 LUT。
///
/// 若指定意图的 tag 不存在,退回 `A2B0`;都没有则报错。
///
/// 支持 v2 的 `mft1`/`mft2` 与 v4 的 `mAB `(见 `v4lut.mbt`)。
pub fn Profile::a2b(self : Profile, intent : Int32) -> LutTable raise IccError {
  let want = intent_to_a2b_tag(intent)
  match self.find_tag(want) {
    Some(t) => parse_a2b_table(self.data, t)
    None =>
      match self.find_tag("A2B0") {
        Some(t) => parse_a2b_table(self.data, t)
        None =>
          raise BadTagData(
            tag=want,
            reason="profile 中既没有 \{want} 也没有 A2B0,无法做设备到 PCS 的 LUT 变换",
          )
      }
  }
}

///|
/// 取出 PCS → 设备的 LUT。
///
/// 支持 v2 的 `mft1`/`mft2` 与 v4 的 `mBA `(见 `v4lut.mbt`)。
pub fn Profile::b2a(self : Profile, intent : Int32) -> LutTable raise IccError {
  let want = intent_to_b2a_tag(intent)
  match self.find_tag(want) {
    Some(t) => parse_b2a_table(self.data, t)
    None =>
      match self.find_tag("B2A0") {
        Some(t) => parse_b2a_table(self.data, t)
        None =>
          raise BadTagData(
            tag=want,
            reason="profile 中既没有 \{want} 也没有 B2A0,无法做 PCS 到设备的 LUT 变换",
          )
      }
  }
}

///|
/// 设备值(0..1,按 profile 输入色空间的通道顺序)→ PCS XYZ。
///
/// LUT 型 profile 的 PCS 可以是 `Lab ` 或 `XYZ `,两者的编码不同:
/// Lab 按版本折算(v2 上限 `0xFF00`),XYZ 一律是 `u1Fixed15Number`
/// (1.0 = `0x8000`)。见 `clut_output_to_lab` 与 `clut_output_to_xyz`。
pub fn Profile::device_to_xyz(
  self : Profile,
  intent : Int32,
  inputs : Array[Double],
) -> XyzNumber raise IccError {
  let table = self.a2b(intent)
  let raw = table.evaluate(inputs)
  if self.header.pcs == "XYZ " {
    clut_output_to_xyz(raw, table.bit_depth())
  } else if self.header.pcs == "Lab " {
    lab_to_xyz(
      clut_output_to_lab(raw, table.bit_depth(), table.pcs_is_v2(self.is_v2())),
    )
  } else {
    raise BadTagData(
      tag="pcs",
      reason="LUT 变换只支持 PCS 为 \"Lab \" 或 \"XYZ \",实际 \"\{self.header.pcs}\"",
    )
  }
}

///|
/// PCS XYZ → 设备值(`device_to_xyz` 的逆方向)。
pub fn Profile::xyz_to_device(
  self : Profile,
  intent : Int32,
  xyz : XyzNumber,
) -> Array[Double] raise IccError {
  let table = self.b2a(intent)
  let encoded = if self.header.pcs == "XYZ " {
    xyz_to_clut_input(xyz, table.bit_depth())
  } else if self.header.pcs == "Lab " {
    lab_to_clut_input(
      xyz_to_lab(xyz),
      table.bit_depth(),
      table.pcs_is_v2(self.is_v2()),
    )
  } else {
    raise BadTagData(
      tag="pcs",
      reason="LUT 变换只支持 PCS 为 \"Lab \" 或 \"XYZ \",实际 \"\{self.header.pcs}\"",
    )
  }
  table.evaluate(encoded)
}

///|
/// 设备值(0..1,按 profile 输入色空间的通道顺序)→ PCS Lab。
///
/// PCS 为 `XYZ ` 的 LUT 型 profile 会先求出 PCS XYZ 再转 Lab
/// (这一步是精确的坐标换算,不引入近似)。
pub fn Profile::device_to_lab(
  self : Profile,
  intent : Int32,
  inputs : Array[Double],
) -> Lab raise IccError {
  xyz_to_lab(self.device_to_xyz(intent, inputs))
}