// Data decoding adapted from datamatrix-rs src/decodation (MIT).

///|
pub suberror DecodeError {
  InvalidMatrix(String)
  UnsupportedMatrixSize
  CorrectionFailed
  InvalidCodeword(String)
  InvalidText
} derive(@debug.Debug)

///|
pub extend DecodeError with @debug.Debug::{to_repr}

///|
pub struct DecodedMessage {
  priv payload : Bytes
  priv ecis : Array[(Int, Int)]
  gs1 : Bool
  macro_kind : Int
  reader_programming : Bool
  append : AppendHeader?
  priv separators : Array[Int]
  corrected_codewords : Int
}

///|
pub fn DecodedMessage::bytes(self : DecodedMessage) -> Bytes {
  self.payload
}

///|
pub fn DecodedMessage::eci_changes(self : DecodedMessage) -> Array[(Int, Int)] {
  self.ecis.copy()
}

///|
pub fn DecodedMessage::text(self : DecodedMessage) -> String raise DecodeError {
  let out = StringBuilder()
  fn chunk(start : Int, end : Int, eci : Int) -> Unit raise DecodeError {
    let mut pos = start
    for sep in self.separators {
      if sep < start || sep >= end {
        continue
      }
      if sep > pos {
        out.write_string(
          @charset.decode(
            Bytes::makei(sep - pos, fn(i) { self.payload[pos + i] }),
            eci,
          ) catch {
            _ => raise InvalidText
          },
        )
      }
      out.write_char('\u001d')
      pos = sep + 1
    }
    if end > pos {
      out.write_string(
        @charset.decode(
          Bytes::makei(end - pos, fn(i) { self.payload[pos + i] }),
          eci,
        ) catch {
          _ => raise InvalidText
        },
      )
    }
  }
  let mut start = 0
  let mut active = 3
  for (offset, eci) in self.ecis {
    chunk(start, offset, active)
    start = offset
    active = eci
  }
  chunk(
    start,
    self.payload.length() - (if self.macro_kind != 0 { 2 } else { 0 }),
    active,
  )
  if self.macro_kind != 0 {
    out.write_string("\u001e\u0004")
  }
  out.to_string()
}

///|
priv struct CodeReader {
  data : Array[Int]
  mut pos : Int
}

///|
fn CodeReader::take(self : CodeReader) -> Int raise DecodeError {
  if self.pos >= self.data.length() {
    raise InvalidCodeword("truncated data")
  }
  let value = self.data[self.pos]
  self.pos += 1
  value
}

///|
fn CodeReader::left(self : CodeReader) -> Int {
  self.data.length() - self.pos
}

///|
fn CodeReader::unrandom(self : CodeReader) -> Int raise DecodeError {
  let value = self.take() - (149 * self.pos % 255 + 1)
  if value < 0 {
    value + 256
  } else {
    value
  }
}

///|
fn decode_data(
  data : Array[Int],
  corrected : Int,
) -> DecodedMessage raise DecodeError {
  let r = CodeReader::{ data, pos: 0, }
  let out : Array[Byte] = []
  let ecis = []
  let separators = []
  let mut gs1 = false
  let mut macro_kind = 0
  let mut reader_programming = false
  let mut append : AppendHeader? = None
  while r.left() > 0 {
    let cw = r.take()
    if cw >= 1 && cw <= 128 {
      out.push((cw - 1).to_byte())
      continue
    }
    if cw >= 130 && cw <= 229 {
      out.push((48 + (cw - 130) / 10).to_byte())
      out.push((48 + (cw - 130) % 10).to_byte())
      continue
    }
    match cw {
      232 =>
        if r.pos == 1 || (r.pos == 5 && append is Some(_)) {
          gs1 = true
        } else {
          separators.push(out.length())
          out.push(b'\x1d')
        }
      233 => {
        if r.pos != 1 {
          raise InvalidCodeword("structured append position")
        }
        let sequence = r.take()
        let a : AppendHeader = {
          index: (sequence >> 4) + 1,
          count: 17 - (sequence & 15),
          file_id1: r.take(),
          file_id2: r.take(),
        }
        if !valid_append(a) {
          raise InvalidCodeword("structured append header")
        }
        append = Some(a)
      }
      234 => {
        if r.pos != 1 {
          raise InvalidCodeword("reader programming position")
        }
        reader_programming = true
      }
      236 | 237 => {
        if r.pos != 1 {
          raise InvalidCodeword("macro position")
        }
        macro_kind = cw - 231
        for b in b"[)>\x1e0" {
          out.push(b)
        }
        out.push((48 + macro_kind).to_byte())
        out.push(b'\x1d')
      }
      129 =>
        while r.left() > 0 {
          let value = r.take()
          let pad = 129 + (149 * r.pos % 253 + 1)
          if value != (if pad > 254 { pad - 254 } else { pad }) {
            raise InvalidCodeword("padding")
          }
        }
      235 => {
        let x = r.take()
        if x < 1 || x > 128 {
          raise InvalidCodeword("upper shift")
        }
        out.push((x + 127).to_byte())
      }
      241 => {
        let a = r.take()
        let eci = if a >= 1 && a <= 127 {
          a - 1
        } else if a >= 128 && a <= 191 {
          let b = r.take()
          if b < 1 || b > 254 {
            raise InvalidCodeword("ECI")
          }
          (a - 128) * 254 + b + 126
        } else if a >= 192 && a <= 207 {
          let b = r.take()
          let c = r.take()
          if b < 1 || b > 254 || c < 1 || c > 254 {
            raise InvalidCodeword("ECI")
          }
          (a - 192) * 64516 + (b - 1) * 254 + c + 16382
        } else {
          raise InvalidCodeword("ECI")
        }
        if eci > 999999 {
          raise InvalidCodeword("ECI range")
        }
        ecis.push((out.length(), eci))
      }
      231 => {
        let a = r.unrandom()
        let n = if a == 0 {
          r.left()
        } else if a < 250 {
          a
        } else {
          (a - 249) * 250 + r.unrandom()
        }
        for _ in 0.. {
        let mut shift = 0
        let mut upper = false
        while r.left() > 0 {
          if r.data[r.pos] == 254 {
            ignore(r.take())
            break
          }
          if r.left() == 1 {
            break
          }
          let full = r.take() * 256 + r.take() - 1
          if full < 0 || full > 63999 {
            raise InvalidCodeword("triplet")
          }
          for v in [full / 1600, full / 40 % 40, full % 40] {
            if cw == 238 {
              let x = match v {
                0 => 13
                1 => 42
                2 => 62
                3 => 32
                _ => if v < 14 { v + 44 } else { v + 51 }
              }
              out.push(x.to_byte())
              continue
            }
            if shift == 0 && v < 3 {
              shift = v + 1
              continue
            }
            let x = if shift == 0 {
              if v == 3 {
                32
              } else if v < 14 {
                v + 44
              } else {
                v + (if cw == 230 { 51 } else { 83 })
              }
            } else if shift == 1 {
              if v > 31 {
                raise InvalidCodeword("shift1")
              }
              v
            } else if shift == 2 {
              if v == 30 {
                upper = true
                shift = 0
                continue
              }
              if v == 27 {
                separators.push(out.length())
                out.push(b'\x1d')
                shift = 0
                continue
              }
              if v > 26 {
                raise InvalidCodeword("shift2")
              }
              if v < 15 {
                v + 33
              } else if v < 22 {
                v + 43
              } else {
                v + 69
              }
            } else {
              if v > 31 {
                raise InvalidCodeword("shift3")
              }
              if cw == 230 {
                v + 96
              } else if v == 0 {
                96
              } else if v < 27 {
                v + 64
              } else {
                v + 96
              }
            }
            out.push((x + (if upper { 128 } else { 0 })).to_byte())
            shift = 0
            upper = false
          }
        }
        if upper {
          raise InvalidCodeword("unfinished upper shift")
        }
      }
      240 => {
        let mut ended = false
        while r.left() > 2 && !ended {
          let start = r.pos
          let bits = r.data[start] * 65536 +
            r.data[start + 1] * 256 +
            r.data[start + 2]
          for i in 0..<4 {
            let v = (bits >> (18 - 6 * i)) & 63
            if v == 31 {
              r.pos = start + ((i + 1) * 6 + 7) / 8
              ended = true
              break
            }
            out.push((if v < 32 { v + 64 } else { v }).to_byte())
          }
          if !ended {
            r.pos += 3
          }
        }
      }
      254 => if r.left() != 0 { raise InvalidCodeword("unexpected unlatch") }
      _ => raise InvalidCodeword("unsupported control or invalid codeword")
    }
  }
  if macro_kind != 0 {
    out.push(b'\x1e')
    out.push(b'\x04')
  }
  {
    payload: Bytes::from_array(out),
    ecis,
    separators,
    gs1,
    macro_kind,
    reader_programming,
    append,
    corrected_codewords: corrected,
  }
}

///|
/// Decode a canonical matrix including finder borders, one boolean per module.
pub fn decode_matrix(
  matrix : Array[Array[Bool]],
) -> DecodedMessage raise DecodeError {
  let h = matrix.length()
  if h == 0 {
    raise InvalidMatrix("empty matrix")
  }
  let w = matrix[0].length()
  if matrix.iter().any(fn(row) { row.length() != w }) {
    raise InvalidMatrix("ragged matrix")
  }
  let matches = (sizes + dmre_sizes).filter(fn(s) {
    s.width == w && s.height == h
  })
  if matches.is_empty() {
    raise UnsupportedMatrixSize
  }
  let size = matches[0]
  let rw = w / size.regions_x - 2
  let rh = h / size.regions_y - 2
  for ry in 0.. Array[Int] {
  self.separators.copy()
}