///|
/// 色域边界描述符(Gamut Boundary Descriptor)。
///
/// ## 为什么不能再用「往返 ΔE」
///
/// 上一版把「PCS → 设备 → PCS 的往返误差」当色域判据,并用中性轴基线
/// 自适应阈值。这个判据混进了**颜色映射误差**:同一个 profile 的 A2B 与
/// B2A 是两张独立的表,CMYK 还受 GCR 与总墨量限制,二者并不严格互逆。
///
/// 实测(1296 个 CMYK 设备值网格样本,按定义**全部在色域内**):
/// 往返误差平均 6.78 ΔE、最大 19.73,用当时的容差 5.59 判定会把
/// **696/1296 = 53.7%** 的色域内颜色误判成越界。
///
/// ## 本实现的判据
///
/// 只用**设备值产生的颜色**建立边界:以 `Lab(L*=50, a*=0, b*=0)` 为中心,
/// 把方向切成扇区,每个扇区记录样本中的最大半径 r_max(球面直方图)。
/// 判定一个颜色是否在色域内,就是看它的半径是否不超过所在扇区的 r_max。
///
/// 这条判据**与映射误差完全无关**:边界点全部来自设备本身,
/// A2B/B2A 是否互逆不影响结果。扇区离散化带来的误差用相邻扇区插值削弱。
pub struct GamutBoundary {
  /// 每轴的扇区数(alpha 分 0..360、theta 分 0..180)。
  sectors : Int32
  /// 每个扇区的最大半径;0 且 `filled` 为 false 表示该扇区无样本。
  radii : Array[Double]
  /// 该扇区是否采到过样本。
  filled : Array[Bool]
  /// 建边界时用的样本数。
  samples : Int32
  /// 采样网格每轴的层数。
  steps : Int32
}

///|
/// Lab → 以 (L*=50, a*=0, b*=0) 为原点的球面坐标(r, alpha 度, theta 度)。
///
/// 与 lcms2 的 `ToSpherical`(`src/cmssm.c:119`)取同一约定,
/// 便于用它的色域边界描述符做对拍;实现是本项目自己写的。
fn lab_to_spherical(lab : Lab) -> (Double, Double, Double) {
  let l = lab.l - 50.0
  let a = lab.a
  let b = lab.b
  let r = @math.pow(l * l + a * a + b * b, 0.5)
  if r == 0.0 {
    return (0.0, 0.0, 0.0)
  }
  let pi = 3.141592653589793
  // alpha: 在 a-b 平面内的角度(0..360),从 b 轴起算
  let mut alpha = @math.atan2(a, b) * 180.0 / pi
  if alpha < 0.0 {
    alpha = alpha + 360.0
  }
  // theta: 与 L 轴的夹角(0..180)
  let theta = @math.atan2(@math.pow(a * a + b * b, 0.5), l) * 180.0 / pi
  (r, alpha, theta)
}

///|
/// 球面方向 → 扇区线性下标。
fn sector_of(alpha : Double, theta : Double, sectors : Int32) -> Int32 {
  let s = sectors.to_double()
  let mut ai = (alpha * s / 360.0).to_int()
  let mut ti = (theta * s / 180.0).to_int()
  if ai >= sectors {
    ai = sectors - 1
  }
  if ti >= sectors {
    ti = sectors - 1
  }
  if ai < 0 {
    ai = 0
  }
  if ti < 0 {
    ti = 0
  }
  ti * sectors + ai
}

///|
/// 把设备空间的采样网格映射成 PCS Lab 点,建出色域边界。
///
/// `steps` 是每轴的层数:CMYK 4 通道时 `steps=6` 得到 1296 个样本,
/// `steps=9` 得到 6561 个。矩阵/TRC 型 profile 按 3 通道处理。
///
/// 采样值取 `i/(steps-1)`,覆盖 0..1 全量程。
pub fn Profile::gamut_boundary(
  self : Profile,
  intent : Int32,
  steps : Int32,
) -> GamutBoundary raise IccError {
  if steps < 2 {
    raise BadTagData(
      tag="gamut",
      reason="色域边界的采样层数至少为 2,实际 \{steps}",
    )
  }
  let channels = self.device_channels(intent)
  if channels != 3 && channels != 4 {
    raise BadTagData(
      tag="gamut",
      reason="色域边界只支持 3 或 4 通道设备空间,实际 \{channels}",
    )
  }
  let sectors = 16
  let total = sectors * sectors
  let radii : Array[Double] = []
  let filled : Array[Bool] = []
  let mut init = 0
  while init < total {
    radii.push(0.0)
    filled.push(false)
    init = init + 1
  }
  let mut samples = 0
  let grid = gamut_sample_points(steps, channels)
  let mut index = 0
  while index < grid.length() {
    let device : Array[Double] = if channels == 3 {
      [grid[index], grid[index + 1], grid[index + 2]]
    } else {
      [grid[index], grid[index + 1], grid[index + 2], grid[index + 3]]
    }
    let lab = xyz_to_lab(self.device_to_pcs(intent, device))
    let (r, alpha, theta) = lab_to_spherical(lab)
    let sector = sector_of(alpha, theta, sectors)
    if !filled[sector] || r > radii[sector] {
      radii[sector] = r
      filled[sector] = true
    }
    samples = samples + 1
    index = index + channels
  }
  { sectors, radii, filled, samples, steps, }
}

///|
/// 设备通道数:LUT 型看 A2B 表,matrix/TRC 型按 3 通道。
fn Profile::device_channels(
  self : Profile,
  intent : Int32,
) -> Int32 raise IccError {
  if self.has_lut() {
    self.a2b(intent).input_channels()
  } else if self.has_matrix_trc() {
    3
  } else {
    raise BadTagData(
      tag="A2B0",
      reason="profile 既没有 matrix/TRC tag,也没有 A2B LUT tag,无法确定设备通道数",
    )
  }
}

///|
/// 采样网格的笛卡尔展开(3/4 通道通用)。
///
/// 返回一维数组,每 `channels` 个值为一个设备值组合。
pub fn gamut_sample_points(steps : Int32, channels : Int32) -> Array[Double] {
  let levels : Array[Double] = []
  let denom = (steps - 1).to_double()
  for i = 0; i < steps; i = i + 1 {
    levels.push(i.to_double() / denom)
  }
  let out : Array[Double] = []
  if channels == 3 {
    for a in levels {
      for b in levels {
        for c in levels {
          out.push(a)
          out.push(b)
          out.push(c)
        }
      }
    }
  } else {
    for a in levels {
      for b in levels {
        for c in levels {
          for d in levels {
            out.push(a)
            out.push(b)
            out.push(c)
            out.push(d)
          }
        }
      }
    }
  }
  out
}

///|
/// 某扇区及其 3×3 邻域内的最大半径。
///
/// 为什么要取邻域最大值:扇区是方向上的离散化,若只查本扇区,细采样得到的
/// 方向可能落在两个粗采样扇区之间,半径略大于任一侧,于是**色域内的颜色
/// 被判越界**。取邻域最大值把这种离散化误差压掉(代价是边界略微外扩,
/// 属于保守方向:宁可少报越界,也不误报)。
fn GamutBoundary::neighbourhood_max(
  self : GamutBoundary,
  sector : Int32,
) -> (Double, Bool) {
  let s = self.sectors
  let ti = sector / s
  let ai = sector % s
  let mut best = 0.0
  let mut any = false
  for dt = -1; dt <= 1; dt = dt + 1 {
    for da = -1; da <= 1; da = da + 1 {
      let t = ti + dt
      if t < 0 || t >= s {
        continue
      }
      let mut a = ai + da
      if a < 0 {
        a = a + s
      }
      if a >= s {
        a = a - s
      }
      let idx = t * s + a
      if self.filled[idx] {
        any = true
        if self.radii[idx] > best {
          best = self.radii[idx]
        }
      }
    }
  }
  (best, any)
}

///|
/// 该颜色是否在色域内:半径不超过所在扇区(含 3×3 邻域)的最大半径。
///
/// 空扇区(没有采样到的方向)保守地判为**不在**色域内,
/// 并可用 `GamutBoundary::coverage` 检查采样充分性。
pub fn GamutBoundary::contains(self : GamutBoundary, lab : Lab) -> Bool {
  let (r, alpha, theta) = lab_to_spherical(lab)
  let (rmax, any) = self.neighbourhood_max(
    sector_of(alpha, theta, self.sectors),
  )
  if !any {
    return false
  }
  r <= rmax * 1.0000001
}

///|
/// 到色域边界的带符号距离(Lab 单位,即 ΔE76 量级)。
///
/// 负值表示在色域内、离边界还有多远;正值表示越界了多少。
/// 这比「in/out」二元判定更有用:可以区分「刚好在边界上」与「远在界外」。
pub fn GamutBoundary::signed_distance(
  self : GamutBoundary,
  lab : Lab,
) -> Double {
  let (r, alpha, theta) = lab_to_spherical(lab)
  let (rmax, any) = self.neighbourhood_max(
    sector_of(alpha, theta, self.sectors),
  )
  if !any {
    return r
  }
  r - rmax
}

///|
/// 采样覆盖率:采到样本的扇区占比。用来判断采样层数是否足够
/// (覆盖率过低说明边界有洞,判定会保守地偏严)。
pub fn GamutBoundary::coverage(self : GamutBoundary) -> Double {
  let mut n = 0
  for f in self.filled {
    if f {
      n = n + 1
    }
  }
  n.to_double() / self.filled.length().to_double()
}

///|
/// 建边界时使用的样本数。
pub fn GamutBoundary::samples(self : GamutBoundary) -> Int32 {
  self.samples
}

///|
/// 每轴扇区数。
pub fn GamutBoundary::sectors(self : GamutBoundary) -> Int32 {
  self.sectors
}

///|
/// 解析采样层数参数:仅接受 2..12 的十进制整数。
///
/// 上限 12 是为了让 4 通道的笛卡尔积不至于爆炸(12^4 = 20736 个样本)。
pub fn parse_steps(text : String) -> Int32? {
  if text.length() == 0 || text.length() > 2 {
    return None
  }
  let mut value = 0
  for i = 0; i < text.length(); i = i + 1 {
    let c = text[i]
    if c < '0' || c > '9' {
      return None
    }
    value = value * 10 + (c.to_int() - '0'.to_int())
  }
  if value < 2 || value > 12 {
    None
  } else {
    Some(value)
  }
}