///| A built-in 8x8 bitmap font for the digits `0`-`9`. It serves two roles:

///|

///| as the reference templates for classification, and as a way to render

///| test and demo images. Each glyph is eight rows of eight bits, MSB first

///| (bit 7 is the leftmost pixel); a set bit is ink.

///| The eight row bytes of the glyph for `digit`, or all paper if `digit` is
///

///|
/// outside `0..=9`.
pub fn font_rows(digit : Int) -> Array[Int] {
  match digit {
    0 => [0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38]
    1 => [0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E]
    2 => [0x3C, 0x66, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x7E]
    3 => [0x3C, 0x66, 0x06, 0x1C, 0x06, 0x06, 0x66, 0x3C]
    4 => [0x0C, 0x1C, 0x2C, 0x4C, 0xCC, 0xFE, 0x0C, 0x0C]
    5 => [0x7E, 0x60, 0x60, 0x7C, 0x06, 0x06, 0x66, 0x3C]
    6 => [0x3C, 0x66, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x3C]
    7 => [0x7E, 0x06, 0x0C, 0x18, 0x18, 0x18, 0x18, 0x18]
    8 => [0x3C, 0x66, 0x66, 0x3C, 0x66, 0x66, 0x66, 0x3C]
    9 => [0x3C, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x66, 0x3C]
    _ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
  }
}

///| The eight row bytes of the glyph for uppercase letter `c`, or all paper if
///

///|
/// `c` is outside `A`-`Z`. Same bit order and ink convention as `font_rows`.
pub fn font_rows_upper(c : Char) -> Array[Int] {
  match c.to_int() {
    0x41 => [0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66] // A
    0x42 => [0x7C, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x7C] // B
    0x43 => [0x3C, 0x66, 0x60, 0x60, 0x60, 0x60, 0x66, 0x3C] // C
    0x44 => [0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x6C, 0x78] // D
    0x45 => [0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x7E] // E
    0x46 => [0x7E, 0x60, 0x60, 0x78, 0x60, 0x60, 0x60, 0x60] // F
    0x47 => [0x3C, 0x66, 0x60, 0x60, 0x6E, 0x66, 0x66, 0x3C] // G
    0x48 => [0x66, 0x66, 0x66, 0x7E, 0x66, 0x66, 0x66, 0x66] // H
    0x49 => [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E] // I
    0x4A => [0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x6C, 0x38] // J
    0x4B => [0x66, 0x6C, 0x78, 0x70, 0x78, 0x6C, 0x66, 0x66] // K
    0x4C => [0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E] // L
    0x4D => [0x63, 0x77, 0x7F, 0x6B, 0x63, 0x63, 0x63, 0x63] // M
    0x4E => [0x66, 0x76, 0x7E, 0x7E, 0x6E, 0x66, 0x66, 0x66] // N
    0x4F => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C] // O
    0x50 => [0x7C, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60] // P
    0x51 => [0x3C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x0E] // Q
    0x52 => [0x7C, 0x66, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0x66] // R
    0x53 => [0x3C, 0x66, 0x60, 0x3C, 0x06, 0x06, 0x66, 0x3C] // S
    0x54 => [0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18] // T
    0x55 => [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C] // U
    0x56 => [0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18] // V
    0x57 => [0x63, 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63] // W
    0x58 => [0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66, 0x66, 0x66] // X
    0x59 => [0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18] // Y
    0x5A => [0x7E, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x7E] // Z
    _ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
  }
}

///| The eight row bytes of the lowercase glyph for `c`, or all paper outside

///| `a`-`z`. `i` and `j` put their dot on the top row, separated from the stem,

///|
/// so those two are two-component glyphs.
pub fn font_rows_lower(c : Char) -> Array[Int] {
  match c.to_int() {
    0x61 => [0x00, 0x00, 0x3C, 0x06, 0x3E, 0x66, 0x3E, 0x00] // a
    0x62 => [0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x7C, 0x00] // b
    0x63 => [0x00, 0x00, 0x3C, 0x66, 0x60, 0x60, 0x66, 0x3C] // c
    0x64 => [0x06, 0x06, 0x3E, 0x66, 0x66, 0x66, 0x3E, 0x00] // d
    0x65 => [0x00, 0x00, 0x3C, 0x66, 0x7E, 0x60, 0x66, 0x3C] // e
    0x66 => [0x1C, 0x36, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x30] // f
    0x67 => [0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x3C] // g
    0x68 => [0x60, 0x60, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00] // h
    0x69 => [0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C] // i
    0x6A => [0x0C, 0x00, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x38] // j
    0x6B => [0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0x00] // k
    0x6C => [0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C] // l
    0x6D => [0x00, 0x00, 0x63, 0x77, 0x7F, 0x6B, 0x63, 0x00] // m
    0x6E => [0x00, 0x00, 0x7C, 0x66, 0x66, 0x66, 0x66, 0x00] // n
    0x6F => [0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C] // o
    0x70 => [0x00, 0x00, 0x7C, 0x66, 0x66, 0x7C, 0x60, 0x60] // p
    0x71 => [0x00, 0x00, 0x3E, 0x66, 0x66, 0x3E, 0x06, 0x06] // q
    0x72 => [0x00, 0x00, 0x7C, 0x66, 0x60, 0x60, 0x60, 0x00] // r
    0x73 => [0x00, 0x00, 0x3E, 0x60, 0x3C, 0x06, 0x06, 0x3C] // s
    0x74 => [0x30, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x36, 0x1C] // t
    0x75 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3E, 0x00] // u
    0x76 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18] // v
    0x77 => [0x00, 0x00, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63] // w
    0x78 => [0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x3C, 0x66] // x
    0x79 => [0x00, 0x00, 0x66, 0x66, 0x66, 0x3E, 0x06, 0x3C] // y
    0x7A => [0x00, 0x00, 0x7E, 0x0C, 0x18, 0x30, 0x60, 0x7E] // z
    _ => [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
  }
}

///| The eight row bytes for any supported glyph: digits `0`-`9` and upper- or
///

///|
/// lowercase letters `A`-`Z` / `a`-`z`. Any other character yields all paper.
pub fn char_rows(c : Char) -> Array[Int] {
  let code = c.to_int()
  if code >= 0x30 && code <= 0x39 {
    font_rows(code - 0x30)
  } else if code >= 0x41 && code <= 0x5A {
    font_rows_upper(c)
  } else {
    font_rows_lower(c)
  }
}

///| Expand eight row bytes into a 64-cell feature grid, row-major, where each
///

///| cell is `255` for ink and `0` for paper. This is the reference

///|
/// representation consumed by the classifier.
pub fn rows_to_grid(rows : Array[Int]) -> Array[Int] {
  let grid = Array::make(64, 0)
  for y = 0; y < 8; y = y + 1 {
    let row = rows[y]
    for x = 0; x < 8; x = x + 1 {
      if ((row >> (7 - x)) & 1) == 1 {
        grid[y * 8 + x] = 255
      }
    }
  }
  grid
}

///| Render a glyph given as eight row bytes into an `8 * scale` by `8 * scale`
///

///| grayscale image, with ink as `0` and paper as `255`. This is the bridge

///|
/// used to turn font data into images for recognition tests and demos.
pub fn render_rows(rows : Array[Int], scale : Int) -> Image {
  let size = 8 * scale
  let pixels = Bytes::makei(size * size, fn(i) {
    let x = i % size
    let y = i / size
    let fx = x / scale
    let fy = y / scale
    if ((rows[fy] >> (7 - fx)) & 1) == 1 {
      b'\x00'
    } else {
      b'\xff'
    }
  })
  Image::new(size, size, pixels)
}

///| Render a string of digits and letters side by side into a single

///|

///| grayscale image, using `scale` pixels per font cell and a `gap`-pixel

///|

///| gutter between glyphs. Characters outside `0`-`9`, `A`-`Z` and `a`-`z` are

///|
/// as paper.
pub fn render_text(text : String, scale : Int, gap : Int) -> Image {
  let n = text.length()
  let rows_by_slot : Array[Array[Int]] = Array(capacity=n)
  for ch in text {
    rows_by_slot.push(char_rows(ch))
  }
  let glyph = 8 * scale
  let gutter = if n > 0 { (n - 1) * gap } else { 0 }
  let total_w = n * glyph + gutter
  let stride_w = glyph + gap
  let pixels = Bytes::makei(total_w * glyph, fn(i) {
    let x = i % total_w
    let y = i / total_w
    let slot = x / stride_w
    let x_in_slot = x - slot * stride_w
    if x_in_slot >= glyph {
      b'\xff'
    } else {
      let fx = x_in_slot / scale
      let fy = y / scale
      let rows = rows_by_slot[slot]
      if ((rows[fy] >> (7 - fx)) & 1) == 1 {
        b'\x00'
      } else {
        b'\xff'
      }
    }
  })
  Image::new(total_w, glyph, pixels)
}

///| Render several lines of digits stacked vertically into one image, with

///|

///| `gap_x` pixels between glyphs on a line and `gap_y` pixels between rows.

///| All lines are left-aligned; shorter lines are padded with paper on the

///|
/// right.
pub fn render_text_block(
  lines : Array[String],
  scale : Int,
  gap_x : Int,
  gap_y : Int,
) -> Image {
  let glyph = 8 * scale
  let n = lines.length()
  let line_imgs : Array[Image] = Array(capacity=n)
  let mut max_w = 0
  for i = 0; i < n; i = i + 1 {
    let li = render_text(lines[i], scale, gap_x)
    if li.width > max_w {
      max_w = li.width
    }
    line_imgs.push(li)
  }
  let row_h = glyph + gap_y
  let gutter = if n > 0 { (n - 1) * gap_y } else { 0 }
  let total_h = n * glyph + gutter
  let pixels = Bytes::makei(max_w * total_h, fn(i) {
    let x = i % max_w
    let y = i / max_w
    let line_i = y / row_h
    let y_in = y - line_i * row_h
    if y_in >= glyph {
      b'\xff'
    } else {
      let li = line_imgs[line_i]
      if x >= li.width {
        b'\xff'
      } else {
        li.pixels.at(y_in * li.width + x)
      }
    }
  })
  Image::new(max_w, total_h, pixels)
}