// Copyright (c) 2025 lws
// JPEG baseline decoder — grayscale + YCbCr color
//
// Supports JFIF baseline JPEG (SOF0) with:
//   - Huffman-coded DC and AC coefficients (O(1) prefix table lookup)
//   - Zigzag deordering → dequantization → IDCT
//   - Grayscale (1-component) and YCbCr color (3-component, 4:4:4/4:2:2/4:2:0)
//   - Restart marker (RST) support
//
// Performance optimizations:
//   - IDCT coefficients and zigzag table as module-level constants
//   - Huffman decoding via a canonical prefix lookup table
//   - Bulk bit reading (reads bytes directly when 8+ bits needed)
//   - JBR uses byte_pos + bit_pos representation

//-----------------------------------------------------------------------------
// Module-level constants (pre-computed once, not per-call)
//-----------------------------------------------------------------------------

///|
/// Zigzag scan order (JPEG spec Figure K.2)
let zigzag : Array[Int] = [
  0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
  41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15,
  23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62,
  63,
]

///|
/// IDCT basis coefficients, scaled by 4096:
/// C(u)/2 * cos((2x+1)*u*pi/16), where C(0)=1/sqrt(2).
let dct_t : Array[Array[Int]] = [
  [1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448],
  [2009, 1703, 1138, 400, -400, -1138, -1703, -2009],
  [1892, 784, -784, -1892, -1892, -784, 784, 1892],
  [1703, -400, -2009, -1138, 1138, 2009, 400, -1703],
  [1448, -1448, -1448, 1448, 1448, -1448, -1448, 1448],
  [1138, -2009, 400, 1703, -1703, -400, 2009, -1138],
  [784, -1892, 1892, -784, -784, 1892, -1892, 784],
  [400, -1138, 1703, -2009, 2009, -1703, 1138, -400],
]

//-----------------------------------------------------------------------------
// Bit reader (MSB-first, used for JPEG Huffman-coded data)
//-----------------------------------------------------------------------------

///|
/// JPEG bit reader. Uses byte_pos + bit_pos representation for efficient
/// bulk reading.
/// Fields are named differently from BitReader to avoid type ambiguity.
priv struct JBR {
  buf : Bytes // entropy-coded data (with 0xFF00 stuffing already stripped)
  bp : Int // current byte position
  bitp : Int // next bit to read within byte (0=MSB, 7=LSB)
}

///|
fn JBR::new(data : Bytes) -> JBR {
  { buf: data, bp: 0, bitp: 0, }
}

///|
/// Read n bits MSB-first. Returns (new_reader, value).
/// Uses bulk byte reading when byte-aligned for efficiency.
fn JBR::read_bits(self : JBR, n : Int) -> (JBR, Int) raise Failure {
  if n == 0 {
    return (self, 0)
  }

  let mut bp_idx = self.bp
  let mut bit_pos = self.bitp
  let mut result = 0
  let mut needed = n

  // Fast path: byte-aligned, reading 8+ bits → read whole bytes
  while needed >= 8 && bit_pos == 0 {
    if bp_idx >= self.buf.length() {
      raise Failure::Failure("JPEG: unexpected end of entropy data")
    }
    result = (result << 8) | self.buf[bp_idx].to_int()
    bp_idx = bp_idx + 1
    needed = needed - 8
  }

  // Remaining bits (either not byte-aligned, or < 8 bits left)
  while needed > 0 {
    if bp_idx >= self.buf.length() {
      raise Failure::Failure("JPEG: unexpected end of entropy data")
    }
    let byte = self.buf[bp_idx].to_int()
    let bits_left = 8 - bit_pos
    let take = if needed < bits_left { needed } else { bits_left }
    // Extract bits from the current position within the byte (MSB-first)
    let val = (byte >> (bits_left - take)) & ((1 << take) - 1)
    result = (result << take) | val
    needed = needed - take
    if take == bits_left {
      bp_idx = bp_idx + 1
      bit_pos = 0
    } else {
      bit_pos = bit_pos + take
    }
  }

  ({ buf: self.buf, bp: bp_idx, bitp: bit_pos, }, result)
}

//-----------------------------------------------------------------------------
// JPEG Huffman table with O(1) prefix lookup
//-----------------------------------------------------------------------------

///|
/// JPEG Huffman table entry.
/// Fields are named sym/len to avoid conflict with PNG's HEntry (symbol/length).
priv struct JHTEntry {
  sym : Int
  len : Int
}

///|
/// JPEG Huffman table with prefix lookup for O(1) decoding.
/// Uses the same approach as PNG's huff_decode (png.mbt:65-69).
priv struct JHT {
  table : Array[JHTEntry]
  tbl_bits : Int
}

///|
/// Build a fast prefix-lookup Huffman table from BITS and HUFFVAL arrays.
/// This eliminates the O(n) bit-at-a-time scan with an O(1) table lookup.
fn build_jht(bits : Array[Int], hv_in : Array[Int]) -> JHT raise Failure {
  // Find the maximum code length
  let mut max_len = 0
  for l = 1; l <= 16; l = l + 1 {
    if bits[l - 1] > 0 {
      max_len = l
    }
  }
  if max_len == 0 {
    raise Failure::Failure("JPEG: empty Huffman table")
  }
  // JPEG reserves the all-ones code for entropy padding (T.81 C.2).
  let mut remaining = 1
  for l = 1; l <= 16; l = l + 1 {
    remaining = (remaining << 1) - bits[l - 1]
    if remaining <= 0 {
      raise Failure::Failure("JPEG: invalid Huffman code lengths")
    }
  }

  // Compute canonical Huffman codes for each length (MSB-first like JPEG)
  let huffcode = Array::make(17, Array::make(0, 0))
  let mut code = 0
  for l = 1; l <= 16; l = l + 1 {
    let count = bits[l - 1]
    if count > 0 {
      let codes = Array::make(count, 0)
      for i = 0; i < count; i = i + 1 {
        codes[i] = code
        code = code + 1
      }
      huffcode[l] = codes
    }
    code = code << 1
  }

  // Build prefix table of size 2^tbl_bits
  let tbl_bits = max_len
  let ts = 1 << tbl_bits
  let sentinel = { sym: -1, len: 0, }
  let tbl = Array::make(ts, sentinel)

  let mut si = 0
  for l = 1; l <= max_len; l = l + 1 {
    let count = bits[l - 1]
    if count > 0 {
      let codes = huffcode[l]
      for i = 0; i < count; i = i + 1 {
        let c = codes[i]
        let sym_val = hv_in[si]
        si = si + 1
        // Fill all table entries starting with this prefix
        let entry = { sym: sym_val, len: l, }
        let base = c << (tbl_bits - l)
        let step = 1 << (tbl_bits - l)
        for j = 0; j < step && base + j < ts; j = j + 1 {
          tbl[base + j] = entry
        }
      }
    }
  }

  { table: tbl, tbl_bits, }
}

///|
/// Decode a symbol using the prefix lookup table.
/// Reads one bit at a time (avoiding push-back complexity), but uses the
/// prefix table for O(1) validation at each bit length.
fn jht_decode(ht : JHT, r : JBR) -> (JBR, Int) raise Failure {
  let mut reader = r
  let mut code = 0
  for l = 1; l <= 16; l = l + 1 {
    let (nr, bit) = reader.read_bits(1)
    reader = nr
    code = (code << 1) | bit
    // Check if this prefix matches a code of length l in the table
    if l <= ht.tbl_bits {
      let idx = code << (ht.tbl_bits - l)
      let e = ht.table[idx]
      if e.len == l {
        return (reader, e.sym)
      }
    }
  }
  raise Failure::Failure("JPEG: bad Huffman code")
}

//-----------------------------------------------------------------------------
// IDCT (Inverse Discrete Cosine Transform)
//-----------------------------------------------------------------------------

///|
/// 2-D IDCT: keep signed precision between passes, then level-shift once.
/// Int64 intermediates prevent overflow from high-frequency coefficients.
fn idct_2d(block : Array[Int]) -> Array[Int] {
  let t = Array::make(64, 0L)
  for v = 0; v < 8; v = v + 1 {
    for x = 0; x < 8; x = x + 1 {
      let mut sum = 0L
      for u = 0; u < 8; u = u + 1 {
        sum = sum +
          Int64::from_int(block[v * 8 + u]) * Int64::from_int(dct_t[u][x])
      }
      t[v * 8 + x] = sum
    }
  }
  let res = Array::make(64, 0)
  for y = 0; y < 8; y = y + 1 {
    for x = 0; x < 8; x = x + 1 {
      let mut sum = 0L
      for v = 0; v < 8; v = v + 1 {
        sum = sum + t[v * 8 + x] * Int64::from_int(dct_t[v][y])
      }
      let pixel = ((sum + 8388608L) >> 24) + 128L
      res[y * 8 + x] = if pixel < 0L {
        0
      } else if pixel > 255L {
        255
      } else {
        pixel.to_int()
      }
    }
  }
  res
}

//-----------------------------------------------------------------------------
// JPEG Decoder Helper Functions
//-----------------------------------------------------------------------------

///|
/// Decode DC coefficient. Returns (reader, new_value, new_pred).
fn jpg_dc(r : JBR, ht : JHT, pred : Int) -> (JBR, Int, Int) raise Failure {
  let (r2, cat) = jht_decode(ht, r)
  if cat > 11 {
    raise Failure::Failure("JPEG: invalid DC category")
  }
  if cat == 0 {
    return (r2, pred, pred)
  }
  let (r3, bits) = r2.read_bits(cat)
  let half = 1 << (cat - 1)
  let diff = if bits < half { bits - ((1 << cat) - 1) } else { bits }
  let new_val = pred + diff
  if new_val < -2048 || new_val > 2047 {
    raise Failure::Failure("JPEG: DC coefficient out of range")
  }
  (r3, new_val, new_val)
}

///|
/// Decode AC coefficients for one 8x8 block.
/// Writes decoded values to block[] at zigzag positions.
/// Returns the updated reader.
fn jpg_ac(r : JBR, ht : JHT, block : Array[Int]) -> JBR raise Failure {
  let mut reader = r
  let mut k = 1
  while k < 64 {
    let (r2, sym) = jht_decode(ht, reader)
    reader = r2
    if sym == 0 {
      return reader
    } // EOB
    let rs = sym >> 4
    let sz = sym & 0xF
    if sz > 10 || (sz == 0 && rs != 15) {
      raise Failure::Failure("JPEG: invalid AC category")
    }
    if sz == 0 && rs == 15 {
      // ZRL: 16 zeros
      k = k + 16
      if k > 64 {
        raise Failure::Failure("JPEG: AC zero run exceeds block")
      }
      continue
    }
    k = k + rs
    if k >= 64 {
      raise Failure::Failure("JPEG: AC run exceeds block")
    }
    if sz > 0 {
      let (r3, bits) = reader.read_bits(sz)
      reader = r3
      let half = 1 << (sz - 1)
      let val = if bits < half { bits - ((1 << sz) - 1) } else { bits }
      let zi = zigzag[k]
      if zi < 64 {
        block[zi] = val
      }
    }
    k = k + 1
  }
  reader
}

///|
/// Decode one 8x8 block. Returns (reader, new_dc_pred, pixel_array[64]).
fn jpg_block(
  r : JBR,
  dc_ht : JHT,
  ac_ht : JHT,
  qt : Array[Int],
  dc_pred : Int,
) -> (JBR, Int, Array[Int]) raise Failure {
  let block = Array::make(64, 0)
  let (r2, _dc_val, ndc) = jpg_dc(r, dc_ht, dc_pred)
  let r3 = jpg_ac(r2, ac_ht, block)
  block[0] = ndc
  // Dequantize
  for i = 0; i < 64; i = i + 1 {
    block[i] = block[i] * qt[i]
  }
  (r3, ndc, idct_2d(block))
}

//-----------------------------------------------------------------------------
// YCbCr → RGB conversion (ITU-R BT.601, 12-bit fixed point)
//-----------------------------------------------------------------------------

///|
/// Convert YCbCr to RGB using ITU-R BT.601 coefficients with 12 fractional bits.
/// All values are clamped to [0, 255].
fn ycbcr_to_rgb(y : Int, cb : Int, cr : Int) -> (Int, Int, Int) {
  let cb_off = cb - 128
  let cr_off = cr - 128

  // R = Y + 1.40200 * (Cr-128)  →  R = Y + 5743 * Cr_off / 4096
  let r = y + ((5743 * cr_off + 2048) >> 12)
  // G = Y - 0.34414 * (Cb-128) - 0.71414 * (Cr-128)
  let g = y - ((1410 * cb_off + 2925 * cr_off + 2048) >> 12)
  // B = Y + 1.77200 * (Cb-128)  →  B = Y + 7258 * Cb_off / 4096
  let b = y + ((7258 * cb_off + 2048) >> 12)

  let r_clamp = if r < 0 { 0 } else if r > 255 { 255 } else { r }
  let g_clamp = if g < 0 { 0 } else if g > 255 { 255 } else { g }
  let b_clamp = if b < 0 { 0 } else if b > 255 { 255 } else { b }

  (r_clamp, g_clamp, b_clamp)
}

///|
/// Interpolate subsampled chroma at pixel centers with replicated edge samples.
/// Alternate rounding follows the conventional libjpeg triangle upsampler.
fn jpg_chroma(
  plane : Array[Byte],
  stride : Int,
  width : Int,
  height : Int,
  x : Int,
  y : Int,
  horizontal : Int,
  vertical : Int,
) -> Int {
  let sx = x / horizontal
  let sy = y / vertical
  if horizontal == 1 || width <= 2 {
    return plane[sy * stride + sx].to_int()
  }
  let nx = if x % 2 == 0 {
    if sx > 0 {
      sx - 1
    } else {
      sx
    }
  } else if sx + 1 < width {
    sx + 1
  } else {
    sx
  }
  let center = plane[sy * stride + sx].to_int()
  let neighbor = plane[sy * stride + nx].to_int()
  if vertical == 1 {
    let bias = if x % 2 == 0 { 1 } else { 2 }
    return (center * 3 + neighbor + bias) >> 2
  }
  let ny = if y % 2 == 0 {
    if sy > 0 {
      sy - 1
    } else {
      sy
    }
  } else if sy + 1 < height {
    sy + 1
  } else {
    sy
  }
  let center_sum = center * 3 + plane[ny * stride + sx].to_int()
  let neighbor_sum = neighbor * 3 + plane[ny * stride + nx].to_int()
  let bias = if x % 2 == 0 { 8 } else { 7 }
  (center_sum * 3 + neighbor_sum + bias) >> 4
}

//-----------------------------------------------------------------------------
// Main JPEG decoder
//-----------------------------------------------------------------------------

///|
/// Read a complete marker and its bounded payload, accepting marker fill bytes.
fn jpg_segment(data : Bytes, offset : Int) -> (Int, Int, Int) raise Failure {
  let mut pos = offset
  if pos >= data.length() || data[pos].to_int() != 0xFF {
    raise Failure::Failure("JPEG: expected marker")
  }
  while pos < data.length() && data[pos].to_int() == 0xFF {
    pos = pos + 1
  }
  if pos >= data.length() {
    raise Failure::Failure("JPEG: truncated marker")
  }
  let marker = data[pos].to_int()
  pos = pos + 1
  if marker == 0xD9 {
    return (marker, pos, pos)
  }
  if marker == 0 ||
    marker == 0xD8 ||
    marker == 1 ||
    (marker >= 0xD0 && marker <= 0xD7) {
    raise Failure::Failure("JPEG: unexpected standalone marker")
  }
  if data.length() - pos < 2 {
    raise Failure::Failure("JPEG: truncated segment length")
  }
  let length = (data[pos].to_int() << 8) | data[pos + 1].to_int()
  if length < 2 || length > data.length() - pos {
    raise Failure::Failure("JPEG: invalid segment length")
  }
  (marker, pos + 2, pos + length)
}

///|
/// Validate the supported SOF0 frame and return its component descriptors.
fn jpg_frame(
  data : Bytes,
  pos : Int,
  end : Int,
) -> (Int, Int, Array[Int], Array[Int], Array[Int], Array[Int]) raise Failure {
  if end - pos < 6 || data[pos].to_int() != 8 {
    raise Failure::Failure("JPEG: baseline requires eight-bit samples")
  }
  let h = (data[pos + 1].to_int() << 8) | data[pos + 2].to_int()
  let w = (data[pos + 3].to_int() << 8) | data[pos + 4].to_int()
  let nc = data[pos + 5].to_int()
  if (nc != 1 && nc != 3) || end - pos != 6 + nc * 3 {
    raise Failure::Failure("JPEG: unsupported or invalid component count")
  }
  let _ = checked_image_size(w, h, nc)
  let ids = Array::make(nc, 0)
  let hs = Array::make(nc, 0)
  let vs = Array::make(nc, 0)
  let tables = Array::make(nc, 0)
  for i = 0; i < nc; i = i + 1 {
    let start = pos + 6 + i * 3
    ids[i] = data[start].to_int()
    let sampling = data[start + 1].to_int()
    hs[i] = sampling >> 4
    vs[i] = sampling & 15
    tables[i] = data[start + 2].to_int()
    if tables[i] >= 4 {
      raise Failure::Failure("JPEG: invalid quantization table selector")
    }
    for j = 0; j < i; j = j + 1 {
      if ids[j] == ids[i] {
        raise Failure::Failure("JPEG: duplicate frame component")
      }
    }
  }
  if nc == 1 {
    if hs[0] < 1 || hs[0] > 4 || vs[0] < 1 || vs[0] > 4 {
      raise Failure::Failure("JPEG: invalid grayscale sampling")
    }
    // A noninterleaved grayscale MCU is one block regardless of frame sampling.
    hs[0] = 1
    vs[0] = 1
    // The output path implements conventional JFIF Y,Cb,Cr component order.
  } else if ids != [1, 2, 3] ||
    hs[1] != 1 ||
    vs[1] != 1 ||
    hs[2] != 1 ||
    vs[2] != 1 ||
    !((hs[0] == 1 && vs[0] == 1) || (hs[0] == 2 && (vs[0] == 1 || vs[0] == 2))) {
    raise Failure::Failure("JPEG: unsupported color components or sampling")
  }
  (w, h, ids, hs, vs, tables)
}

///|
/// An entropy segment must end with at most seven one-valued padding bits.
fn jpg_finish_segment(reader : JBR) -> Unit raise Failure {
  let mut end = reader.bp
  if reader.bitp != 0 {
    let count = 8 - reader.bitp
    let mask = (1 << count) - 1
    if (reader.buf[end].to_int() & mask) != mask {
      raise Failure::Failure("JPEG: invalid entropy padding")
    }
    end = end + 1
  }
  if end != reader.buf.length() {
    raise Failure::Failure("JPEG: excess entropy data or misplaced restart")
  }
}

///|
/// Keep restart boundaries while removing entropy byte stuffing.
fn jpg_entropy(
  data : Bytes,
  start : Int,
  restart_interval : Int,
  segment_count : Int,
) -> Array[Bytes] raise Failure {
  let segments = []
  let mut bytes = Buffer()
  let mut pos = start
  let mut next_restart = 0
  let mut byte_count = 0
  while pos < data.length() {
    if data[pos].to_int() != 0xFF {
      if byte_count >= MAX_DECODE_BYTES {
        raise Failure::Failure("JPEG: entropy data exceeds 256 MiB limit")
      }
      bytes.write_byte(data[pos])
      byte_count = byte_count + 1
      pos = pos + 1
      continue
    }
    let marker_start = pos
    pos = pos + 1
    if pos >= data.length() {
      raise Failure::Failure("JPEG: truncated entropy marker")
    }
    if data[pos].to_int() == 0 {
      if byte_count >= MAX_DECODE_BYTES {
        raise Failure::Failure("JPEG: entropy data exceeds 256 MiB limit")
      }
      bytes.write_byte(b'\xFF')
      byte_count = byte_count + 1
      pos = pos + 1
      continue
    }
    while pos < data.length() && data[pos].to_int() == 0xFF {
      pos = pos + 1
    }
    if pos >= data.length() {
      raise Failure::Failure("JPEG: truncated entropy marker")
    }
    let marker = data[pos].to_int()
    pos = pos + 1
    if marker == 0xD9 {
      segments.push(bytes.to_bytes())
      return segments
    }
    if (marker >= 0xE0 && marker <= 0xEF) || marker == 0xFE {
      // APP/COM segments may follow the final scan before EOI (T.81 B.2).
      // They terminate entropy data, so never append their payload to it.
      let mut trailing = marker_start
      while trailing < data.length() {
        let (next_marker, _, end) = jpg_segment(data, trailing)
        if next_marker == 0xD9 {
          segments.push(bytes.to_bytes())
          return segments
        }
        if !((next_marker >= 0xE0 && next_marker <= 0xEF) || next_marker == 0xFE) {
          raise Failure::Failure(
            "JPEG: only a single baseline scan is supported",
          )
        }
        trailing = end
      }
      raise Failure::Failure("JPEG: missing EOI marker")
    }
    if marker >= 0xD0 && marker <= 0xD7 {
      if restart_interval == 0 ||
        marker != 0xD0 + next_restart ||
        segments.length() >= segment_count - 1 ||
        bytes.length() == 0 {
        raise Failure::Failure("JPEG: unexpected restart marker")
      }
      segments.push(bytes.to_bytes())
      bytes = Buffer()
      next_restart = (next_restart + 1) % 8
    } else {
      raise Failure::Failure("JPEG: only a single baseline scan is supported")
    }
  }
  raise Failure::Failure("JPEG: missing EOI marker")
}

///|
/// Decode an eight-bit single-scan baseline JPEG (grayscale or JFIF YCbCr).
pub fn decode_jpeg(data : Bytes) -> Image raise Failure {
  if data.length() < 2 || data[0].to_int() != 0xFF || data[1].to_int() != 0xD8 {
    raise Failure::Failure("JPEG: missing SOI marker")
  }
  let mut pos = 2
  let mut w = 0
  let mut h = 0
  let mut comp_id = []
  let mut comp_h = []
  let mut comp_v = []
  let mut comp_qt = []
  let qt = Array::make(4, Array::make(0, 0))
  let sentinel = JHT::{ table: [], tbl_bits: 0, }
  let dc_ht = Array::make(4, sentinel)
  let ac_ht = Array::make(4, sentinel)
  let mut restart_interval = 0
  let mut adobe_transform = -1
  while pos < data.length() {
    let (marker, start, end) = jpg_segment(data, pos)
    pos = start
    if marker == 0xDB {
      if pos == end {
        raise Failure::Failure("JPEG: empty quantization segment")
      }
      while pos < end {
        let info = data[pos].to_int()
        pos = pos + 1
        if info >> 4 != 0 || (info & 15) >= 4 || end - pos < 64 {
          raise Failure::Failure("JPEG: invalid baseline quantization table")
        }
        let table = Array::make(64, 0)
        for i = 0; i < 64; i = i + 1 {
          let value = data[pos + i].to_int()
          if value == 0 {
            raise Failure::Failure("JPEG: zero quantization value")
          }
          table[zigzag[i]] = value
        }
        qt[info & 15] = table
        pos = pos + 64
      }
    } else if marker == 0xC0 {
      if w != 0 {
        raise Failure::Failure("JPEG: duplicate frame")
      }
      let (fw, fh, ids, hs, vs, tables) = jpg_frame(data, start, end)
      w = fw
      h = fh
      comp_id = ids
      comp_h = hs
      comp_v = vs
      comp_qt = tables
    } else if marker == 0xC4 {
      if pos == end {
        raise Failure::Failure("JPEG: empty Huffman segment")
      }
      while pos < end {
        if end - pos < 17 {
          raise Failure::Failure("JPEG: truncated Huffman table")
        }
        let info = data[pos].to_int()
        pos = pos + 1
        let tc = info >> 4
        let th = info & 15
        if tc > 1 || th > 1 {
          raise Failure::Failure("JPEG: invalid baseline Huffman selector")
        }
        let bits = Array::make(16, 0)
        let mut total = 0
        for i = 0; i < 16; i = i + 1 {
          bits[i] = data[pos + i].to_int()
          total = total + bits[i]
        }
        pos = pos + 16
        if total > 256 || total > end - pos {
          raise Failure::Failure("JPEG: invalid Huffman symbol count")
        }
        let values = Array::make(total, 0)
        let seen = Array::make(256, false)
        for i = 0; i < total; i = i + 1 {
          let value = data[pos + i].to_int()
          if seen[value] ||
            (tc == 0 && value > 11) ||
            (
              tc == 1 &&
              (
                (value & 15) > 10 ||
                ((value & 15) == 0 && value != 0 && value != 0xF0)
              )
            ) {
            raise Failure::Failure("JPEG: invalid Huffman symbol")
          }
          seen[value] = true
          values[i] = value
        }
        pos = pos + total
        let table = build_jht(bits, values)
        if tc == 0 {
          dc_ht[th] = table
        } else {
          ac_ht[th] = table
        }
      }
    } else if marker == 0xDD {
      if end - start != 2 {
        raise Failure::Failure("JPEG: invalid restart interval segment")
      }
      restart_interval = (data[start].to_int() << 8) | data[start + 1].to_int()
    } else if marker == 0xEE &&
      end - start >= 12 &&
      data[start:start + 5] == b"Adobe" {
      adobe_transform = data[start + 11].to_int()
    } else if marker == 0xDA {
      let nc = comp_id.length()
      if w == 0 ||
        end - start < 1 ||
        data[start].to_int() != nc ||
        end - start != 1 + nc * 2 + 3 {
        raise Failure::Failure("JPEG: invalid or unsupported scan components")
      }
      if nc == 3 && adobe_transform != -1 && adobe_transform != 1 {
        raise Failure::Failure("JPEG: unsupported Adobe color transform")
      }
      if data[end - 3].to_int() != 0 ||
        data[end - 2].to_int() != 63 ||
        data[end - 1].to_int() != 0 {
        raise Failure::Failure("JPEG: unsupported nonbaseline scan parameters")
      }
      let scan_comp = Array::make(nc, 0)
      let scan_dc = Array::make(nc, 0)
      let scan_ac = Array::make(nc, 0)
      let selected = Array::make(nc, false)
      for i = 0; i < nc; i = i + 1 {
        let id = data[start + 1 + i * 2].to_int()
        let selectors = data[start + 2 + i * 2].to_int()
        let dc = selectors >> 4
        let ac = selectors & 15
        let mut ci = -1
        for j = 0; j < nc; j = j + 1 {
          if comp_id[j] == id {
            ci = j
          }
        }
        if ci < 0 || dc > 1 || ac > 1 {
          raise Failure::Failure("JPEG: invalid scan selector")
        }
        if selected[ci] ||
          dc_ht[dc].tbl_bits == 0 ||
          ac_ht[ac].tbl_bits == 0 ||
          qt[comp_qt[ci]].length() != 64 {
          raise Failure::Failure(
            "JPEG: duplicate component or missing coding table",
          )
        }
        selected[ci] = true
        scan_comp[i] = ci
        scan_dc[i] = dc
        scan_ac[i] = ac
      }
      let max_h = comp_h[0]
      let max_v = comp_v[0]
      let mcus_x = (w + max_h * 8 - 1) / (max_h * 8)
      let mcus_y = (h + max_v * 8 - 1) / (max_v * 8)
      let segment_count = if restart_interval > 0 {
        (mcus_x * mcus_y - 1) / restart_interval + 1
      } else {
        1
      }
      let segments = jpg_entropy(data, end, restart_interval, segment_count)
      let widths = Array::make(nc, 0)
      let planes = Array::make(nc, Array::make(0, b'\x00'))
      for i = 0; i < nc; i = i + 1 {
        widths[i] = mcus_x * comp_h[i] * 8
        let plane_h = mcus_y * comp_v[i] * 8
        let size = checked_image_size(widths[i], plane_h, 1)
        planes[i] = Array::make(size, b'\x00')
      }
      let predictors = Array::make(nc, 0)
      let mut segment = 0
      let mut reader = JBR::new(segments[0])
      let mut mcu_count = 0
      for my = 0; my < mcus_y; my = my + 1 {
        for mx = 0; mx < mcus_x; mx = mx + 1 {
          for sc = 0; sc < nc; sc = sc + 1 {
            let ci = scan_comp[sc]
            for vy = 0; vy < comp_v[ci]; vy = vy + 1 {
              for vx = 0; vx < comp_h[ci]; vx = vx + 1 {
                let (next, dc, pixels) = jpg_block(
                  reader,
                  dc_ht[scan_dc[sc]],
                  ac_ht[scan_ac[sc]],
                  qt[comp_qt[ci]],
                  predictors[ci],
                )
                reader = next
                predictors[ci] = dc
                let block_x = (mx * comp_h[ci] + vx) * 8
                let block_y = (my * comp_v[ci] + vy) * 8
                for py = 0; py < 8; py = py + 1 {
                  for px = 0; px < 8; px = px + 1 {
                    planes[ci][(block_y + py) * widths[ci] + block_x + px] = pixels[py *
                    8 +
                    px].to_byte()
                  }
                }
              }
            }
          }
          mcu_count = mcu_count + 1
          if restart_interval > 0 &&
            mcu_count % restart_interval == 0 &&
            mcu_count < mcus_x * mcus_y {
            jpg_finish_segment(reader)
            segment = segment + 1
            if segment >= segments.length() {
              raise Failure::Failure("JPEG: missing restart marker")
            }
            reader = JBR::new(segments[segment])
            for i = 0; i < nc; i = i + 1 {
              predictors[i] = 0
            }
          }
        }
      }
      jpg_finish_segment(reader)
      if segment != segments.length() - 1 {
        raise Failure::Failure("JPEG: excess restart marker")
      }
      let size = checked_image_size(w, h, nc)
      let out = Array::make(size, b'\x00')
      for y = 0; y < h; y = y + 1 {
        for x = 0; x < w; x = x + 1 {
          if nc == 1 {
            out[y * w + x] = planes[0][y * widths[0] + x]
          } else {
            let chroma_w = (w + max_h - 1) / max_h
            let chroma_h = (h + max_v - 1) / max_v
            let (r, g, b) = ycbcr_to_rgb(
              planes[0][y * widths[0] + x].to_int(),
              jpg_chroma(
                planes[1],
                widths[1],
                chroma_w,
                chroma_h,
                x,
                y,
                max_h,
                max_v,
              ),
              jpg_chroma(
                planes[2],
                widths[2],
                chroma_w,
                chroma_h,
                x,
                y,
                max_h,
                max_v,
              ),
            )
            let offset = (y * w + x) * 3
            out[offset] = r.to_byte()
            out[offset + 1] = g.to_byte()
            out[offset + 2] = b.to_byte()
          }
        }
      }
      let format = if nc == 1 { PixelFormat::Gray8 } else { PixelFormat::RGB8 }
      return Image::new(w, h, format, Bytes::from_array(out))
    } else if (marker >= 0xC1 && marker <= 0xCF) ||
      marker == 0xDC ||
      marker == 0xDE ||
      marker == 0xDF {
      raise Failure::Failure("JPEG: unsupported coding process")
    } else if marker == 0xD9 {
      break
    } else if !((marker >= 0xE0 && marker <= 0xEF) || marker == 0xFE) {
      raise Failure::Failure("JPEG: unsupported marker")
    }
    pos = end
  }
  raise Failure::Failure("JPEG: no SOS marker found")
}

///|
/// Read dimensions of a supported baseline frame without decoding pixels.
pub fn jpeg_dimensions(data : Bytes) -> (Int, Int) raise Failure {
  if data.length() < 2 || data[0].to_int() != 0xFF || data[1].to_int() != 0xD8 {
    raise Failure::Failure("JPEG: missing SOI marker")
  }
  let mut pos = 2
  while pos < data.length() {
    let (marker, start, end) = jpg_segment(data, pos)
    if marker == 0xC0 {
      let (w, h, _, _, _, _) = jpg_frame(data, start, end)
      return (w, h)
    }
    if marker == 0xDA || marker == 0xD9 {
      break
    }
    if marker >= 0xC1 && marker <= 0xCF && marker != 0xC4 {
      raise Failure::Failure("JPEG: unsupported coding process")
    }
    pos = end
  }
  raise Failure::Failure("JPEG: no SOF0 marker found")
}