///|
let stub_rows : Int = 8

///|
let stub_sync : Int = 0b1010

///|
let stub_magic : Int = 0b1011

///|
let stub_dark : Int = 20

///|
let stub_light : Int = 230

///|
let stub_crc_poly : Int = 0x07

///|
let stub_bits_cap : Int = 64

///|
let stub_min_decode : Int = 46

///|
let stub_sr : FixedArray[Int] = [
  8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000,
  176400, 192000,
]

///|
let stub_wins : FixedArray[Int] = [256, 512, 1024, 2048]

///|
let stub_width_bits : FixedArray[Int] = [8, 12, 16, 0]

///|
fn stub_index(t : FixedArray[Int], v : Int) -> Int {
  let mut i = 0
  while i < t.length() {
    if t.unsafe_get(i) == v {
      return i
    }
    i = i + 1
  }
  -1
}

///|
fn stub_width_index(w : Int) -> Int {
  if w <= 0xff {
    0
  } else if w <= 0xfff {
    1
  } else {
    2
  }
}

///|
fn stub_bit_px(w : Int) -> Int {
  if w >= 160 {
    4
  } else if w >= 70 {
    2
  } else {
    1
  }
}

///|
fn stub_total_bits(w : Int) -> Int {
  25 + stub_width_bits.unsafe_get(stub_width_index(w))
}

///|
fn stub_span(w : Int) -> Int {
  2 + stub_total_bits(w) * stub_bit_px(w)
}

///|
fn stub_crc8(bits : FixedArray[Int], from : Int, len : Int) -> Int {
  let mut crc = 0xff
  for i = 0; i < len; i = i + 1 {
    crc = crc ^ (bits.unsafe_get(from + i) << 7)
    for _j = 0; _j < 8; _j = _j + 1 {
      crc = if (crc & 0x80) != 0 {
        ((crc << 1) ^ stub_crc_poly) & 0xff
      } else {
        (crc << 1) & 0xff
      }
    }
  }
  crc
}

///|
fn stub_push(bits : FixedArray[Int], at : Int, v : Int, n : Int) -> Int {
  for i = 0; i < n; i = i + 1 {
    bits.unsafe_set(at + i, (v >> (n - 1 - i)) & 1)
  }
  at + n
}

///|
fn stub_get(bits : FixedArray[Int], at : Int, n : Int) -> Int {
  let mut v = 0
  for i = 0; i < n; i = i + 1 {
    v = (v << 1) | bits.unsafe_get(at + i)
  }
  v
}

///|
fn stub_encode(
  bits : FixedArray[Int],
  w : Int,
  sr : Int,
  win : Int,
  exact : Int,
) -> Int {
  let si = stub_index(stub_sr, sr)
  let wi = stub_index(stub_wins, win)
  if si < 0 || wi < 0 || w < 2 || w > 0xffff {
    return 0
  }
  let mut at = 0
  at = stub_push(bits, at, stub_sync, 4)
  at = stub_push(bits, at, stub_magic, 4)
  at = stub_push(bits, at, stub_width_index(w), 2)
  at = stub_push(bits, at, w, stub_width_bits.unsafe_get(stub_width_index(w)))
  at = stub_push(bits, at, si, 4)
  at = stub_push(bits, at, wi, 2)
  at = stub_push(bits, at, exact, 1)
  at = stub_push(bits, at, stub_crc8(bits, 4, at - 4), 8)
  at
}

///|
fn stub_lum_at(bits : FixedArray[Int], x : Int, w : Int) -> Int {
  let span = stub_span(w)
  let step = stub_bit_px(w)
  let mut at = -1
  if x >= 2 && x < 2 + span {
    at = x - 2
  } else if w >= 2 * span + 6 && x >= w - span {
    at = x - (w - span)
  }
  if at < 2 {
    return stub_dark
  }
  let bit = bits.unsafe_get((at - 2) / step)
  if bit == 0 {
    stub_dark
  } else {
    stub_light
  }
}

///|
#export_name("dsp_stub_rows")
pub fn stub_rows_of() -> Int {
  stub_rows
}

///|
#export_name("dsp_stub_sr")
pub fn stub_sr_at(i : Int) -> Int {
  if i < 0 || i >= stub_sr.length() {
    0
  } else {
    stub_sr.unsafe_get(i)
  }
}

///|
#export_name("dsp_stub_win")
pub fn stub_win_at(i : Int) -> Int {
  if i < 0 || i >= stub_wins.length() {
    0
  } else {
    stub_wins.unsafe_get(i)
  }
}

///|
#export_name("dsp_stub_paint_bytes")
pub fn stub_paint_bytes(w : Int) -> Int {
  w * stub_rows * 4
}

///|
#export_name("dsp_stub_luma_bytes")
pub fn stub_luma_bytes(w : Int) -> Int {
  w
}

///|
#export_name("dsp_stub_decode_words")
pub fn stub_decode_words(n : Int) -> Int {
  3 * n + 8
}

///|
#export_name("dsp_stub_fits")
pub fn stub_fits(w : Int) -> Int {
  if w >= stub_span(w) + 2 && w >= stub_min_decode {
    1
  } else {
    0
  }
}

///|
#export_name("dsp_stub_paint")
pub fn stub_paint(h : Int, w : Int, sr : Int, win : Int, exact : Int) -> Int {
  if job_seg(h) < 0 {
    return 0
  }
  let bits = FixedArray::make(stub_bits_cap, 0)
  let n = stub_encode(bits, w, sr, win, exact)
  if n == 0 || w < stub_span(w) + 2 || job_bytes_of(h) < stub_paint_bytes(w) {
    return 0
  }
  let dst = job_b(h)
  let alpha : UInt = 255 << 24
  for x = 0; x < w; x = x + 1 {
    let lum = stub_lum_at(bits, x, w)
    let px = (lum | (lum << 8) | (lum << 16)).reinterpret_as_uint() | alpha
    for y = 0; y < stub_rows; y = y + 1 {
      dst.unsafe_write_uint32_le((y * w + x) * 4, px)
    }
  }
  1
}

///|
#export_name("dsp_stub_luma")
pub fn stub_luma(h : Int, w : Int, sr : Int, win : Int, exact : Int) -> Int {
  if job_seg(h) < 0 {
    return 0
  }
  let bits = FixedArray::make(stub_bits_cap, 0)
  let n = stub_encode(bits, w, sr, win, exact)
  if n == 0 || w < stub_span(w) + 2 || job_bytes_of(h) < stub_luma_bytes(w) {
    return 0
  }
  let dst = job_b(h)
  for x = 0; x < w; x = x + 1 {
    dst.unsafe_set(x, stub_lum_at(bits, x, w).to_byte())
  }
  1
}

///|
fn stub_run(mem : FixedArray[Double], runs : Int, i : Int, field : Int) -> Int {
  mem.unsafe_get(runs + 2 * i + field).to_int()
}

///|
#export_name("dsp_stub_decode")
pub fn stub_decode(h : Int, n : Int) -> Int {
  if job_seg(h) < 0 {
    return 0
  }
  if n < stub_min_decode || job_words_of(h) < stub_decode_words(n) {
    return 0
  }
  let mem = job_d(h)
  let prof = 0
  let runs = n
  let mut lo = mem.unsafe_get(prof)
  let mut hi = lo
  for i = 1; i < n; i = i + 1 {
    let v = mem.unsafe_get(prof + i)
    lo = lo.min(v)
    hi = hi.max(v)
  }
  if hi - lo < 120.0 {
    return 0
  }
  let th = (lo + hi) / 2.0
  let mut nr = 0
  let mut cur = if mem.unsafe_get(prof) >= th { 1.0 } else { 0.0 }
  let mut len = 0
  for i = 0; i < n; i = i + 1 {
    let v = if mem.unsafe_get(prof + i) >= th { 1.0 } else { 0.0 }
    if v == cur {
      len = len + 1
    } else {
      mem.unsafe_set(runs + 2 * nr, cur)
      mem.unsafe_set(runs + 2 * nr + 1, len.to_double())
      nr = nr + 1
      cur = v
      len = 1
    }
  }
  mem.unsafe_set(runs + 2 * nr, cur)
  mem.unsafe_set(runs + 2 * nr + 1, len.to_double())
  nr = nr + 1
  let bits = FixedArray::make(stub_bits_cap, 0)
  let mut r = 0
  while r + 4 < nr {
    let base = runs + 2 * r
    let c0 = mem.unsafe_get(base).to_int()
    let l0 = mem.unsafe_get(base + 1)
    let c1 = mem.unsafe_get(base + 2).to_int()
    let l1 = mem.unsafe_get(base + 3)
    let c2 = mem.unsafe_get(base + 4).to_int()
    let l2 = mem.unsafe_get(base + 5)
    let c3 = mem.unsafe_get(base + 6).to_int()
    let l3 = mem.unsafe_get(base + 7)
    let unit = (l0 + l1 + l2 + l3) / 4.0
    let ok = c0 == 1 &&
      c1 == 0 &&
      c2 == 1 &&
      c3 == 0 &&
      unit >= 0.3 &&
      (l0 / unit).round() == 1.0 &&
      (l1 / unit).round() == 1.0 &&
      (l2 / unit).round() == 1.0 &&
      (l3 / unit).round() == 1.0
    if ok {
      let mut nb = 0
      let mut total = -1
      let mut wbits = 0
      let mut bad = false
      let mut jj = r + 4
      while jj < nr {
        let v = stub_run(mem, runs, jj, 0)
        let l = stub_run(mem, runs, jj, 1)
        let mut count = (l.to_double() / unit).round().to_int()
        if count < 1 {
          count = 1
        }
        let mut k = 0
        while k < count {
          if nb >= stub_bits_cap {
            bad = true
            break
          }
          bits.unsafe_set(nb, v)
          nb = nb + 1
          if total < 0 && nb >= 6 {
            let magic = (bits.unsafe_get(0) << 3) |
              (bits.unsafe_get(1) << 2) |
              (bits.unsafe_get(2) << 1) |
              bits.unsafe_get(3)
            wbits = stub_width_bits.unsafe_get(
              bits.unsafe_get(4) * 2 + bits.unsafe_get(5),
            )
            if magic != stub_magic || wbits == 0 {
              bad = true
              break
            }
            total = 6 + wbits + 15
          }
          if total > 0 && nb >= total {
            break
          }
          k = k + 1
        }
        if bad || (total > 0 && nb >= total) {
          break
        }
        jj = jj + 1
      }
      if !bad && total >= 0 && nb == total {
        let crc = stub_crc8(bits, 0, 13 + wbits)
        if crc == stub_get(bits, 13 + wbits, 8) {
          let width = stub_get(bits, 6, wbits)
          let si = stub_get(bits, 6 + wbits, 4)
          let wi = stub_get(bits, 10 + wbits, 2)
          if si < stub_sr.length() && width >= 2 {
            let ex = stub_get(bits, 12 + wbits, 1)
            return width | (si << 16) | (wi << 20) | (ex << 22)
          }
        }
      }
    }
    r = r + 1
  }
  0
}