// Port of datamatrix-rs GenericDataEncoder and six encodation implementations.
// Copyright 2021 Jannik Schürg, MIT; see THIRD_PARTY_NOTICES.md.

///|
priv struct Encoder {
  data : Bytes
  options : EncodeOptions
  out : Array[Int]
  mut pos : Int
  mut mode : Int
  mut pending : Int?
  mut switches : Array[(Int, Int)]
}

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

///|
fn Encoder::space(self : Encoder, extra : Int) -> Int raise EncodeError {
  match space_left(self.out.length() + extra, self.options) {
    Some(v) => v
    None => raise CapacityExceeded
  }
}

///|
fn Encoder::ascii_end(self : Encoder) -> Unit {
  self.mode = 0
  self.switches = [(0, 0)]
}

///|
fn Encoder::eat(self : Encoder) -> Int {
  let c = self.data[self.pos].to_int()
  self.pos += 1
  c
}

///|
fn Encoder::switch_mode(self : Encoder) -> Bool {
  let left = self.left()
  let mode = if left > 0 && left == self.switches[0].0 {
    let (_, mode) = self.switches.remove(0)
    mode
  } else {
    self.mode
  }
  if mode != self.mode {
    self.mode = mode
    if mode != 0 {
      self.pending = Some([0, 231, 240, 238, 230, 239][mode])
    }
    true
  } else {
    false
  }
}

///|
fn Encoder::two_digits(self : Encoder) -> Bool {
  self.left() >= 2 &&
  digit(self.data[self.pos]) &&
  digit(self.data[self.pos + 1])
}

///|
fn Encoder::ascii(self : Encoder) -> Unit {
  while true {
    if self.switch_mode() {
      return
    }
    if self.two_digits() {
      let a = self.eat()
      let b = self.eat()
      self.out.push((a - 48) * 10 + b - 48 + 130)
      continue
    }
    if self.left() == 0 {
      return
    }
    let c = self.eat()
    if c <= 127 {
      self.out.push(c + 1)
    } else {
      self.out.push(235)
      self.out.push(c - 127)
    }
  }
}

///|
fn Encoder::base256(self : Encoder) -> Unit raise EncodeError {
  let start = self.out.length()
  self.out.push(0)
  while true {
    if self.left() > 0 {
      self.out.push(self.eat())
    }
    if self.left() == 0 || self.switch_mode() {
      break
    }
  }
  let left = self.space(0)
  if self.left() > 0 || left > 0 {
    let count = self.out.length() - start - 1
    if count <= 249 {
      self.out[start] = count
    } else if count <= 1555 {
      self.out[start] = count / 250 + 249
      self.out.insert(start + 1, count % 250)
    } else {
      raise CapacityExceeded
    }
  }
  for i in start.. Unit raise EncodeError {
  let switching = self.left() > 0
  if !switching {
    let size_left = self.space(buffer.length())
    let left = size_left + buffer.length()
    if left == 2 && buffer.length() == 2 {
      buffer.push(0)
      append_triplets(self.out, buffer)
      return
    }
    if left == 2 && buffer.length() == 1 {
      self.out.push(254)
      self.ascii_end()
      self.pos -= 1
      return
    }
    if left == 1 && buffer.length() == 1 && last <= 127 {
      self.ascii_end()
      self.pos -= 1
      return
    }
  }
  if !buffer.is_empty() {
    buffer.push(1)
    if buffer.length() == 2 {
      buffer.push(30)
    }
    append_triplets(self.out, buffer)
    if !switching {
      self.ascii_end()
    }
  }
  if self.left() > 0 {
    if self.left() == 2 && self.two_digits() {
      let space = self.space(1)
      self.ascii_end()
      if space >= 1 {
        self.out.push(254)
      }
      return
    }
    self.out.push(254)
  } else if self.space(0) > 0 {
    self.out.push(254)
    if !switching {
      self.ascii_end()
    }
  }
}

///|
fn Encoder::c40(self : Encoder, text : Bool) -> Unit raise EncodeError {
  let buffer = []
  let mut last = 0
  while self.left() > 0 {
    let c = self.eat()
    if buffer.is_empty() &&
      c >= 48 &&
      c <= 57 &&
      self.left() == 1 &&
      digit(self.data[self.pos]) {
      self.pos -= 1
      break
    }
    for v in c40_values(c, text) {
      buffer.push(v)
    }
    last = c
    while buffer.length() >= 3 {
      append_triplets(self.out, [buffer[0], buffer[1], buffer[2]])
      for _ in 0..<3 {
        ignore(buffer.remove(0))
      }
    }
    if self.switch_mode() {
      break
    }
  }
  self.c40_end(last, buffer)
}

///|
fn Encoder::x12(self : Encoder) -> Unit raise EncodeError {
  let mut switched = false
  while self.left() >= 3 {
    let values = []
    for _ in 0..<3 {
      match x12_value(self.eat()) {
        Some(v) => values.push(v)
        None => raise InvalidOption("internal X12 planning mismatch")
      }
    }
    append_triplets(self.out, values)
    if self.switch_mode() {
      switched = true
      break
    }
  }
  let one = self.left() <= 2 && ascii_size(self.data, self.pos) == 1
  if one && self.space(1) == 0 {
    self.ascii_end()
    return
  }
  if self.left() > 0 || self.space(0) > 0 {
    if !switched {
      self.ascii_end()
    }
    self.out.push(254)
  }
}

///|
fn Encoder::edifact_end(
  self : Encoder,
  symbols : Array[Int],
) -> Unit raise EncodeError {
  if symbols.length() + self.left() <= 4 {
    let rest = Array::makei(symbols.length() + self.left(), fn(i) {
      if i < symbols.length() {
        symbols[i].to_byte()
      } else {
        self.data[self.pos + i - symbols.length()]
      }
    })
    let size = ascii_size(Bytes::from_array(rest), 0)
    if size <= 2 {
      match space_left(self.out.length() + size, self.options) {
        Some(space) =>
          if space + size <= 2 && size <= space + size {
            self.pos -= symbols.length()
            self.ascii_end()
            return
          }
        None => ()
      }
    }
  }
  if symbols.is_empty() {
    if self.left() == 0 {
      if self.space(0) > 0 {
        self.out.push(124)
        self.ascii_end()
      }
    } else {
      self.out.push(124)
    }
  } else {
    if self.left() == 0 {
      if self.space(symbols.length()) > 0 || symbols.length() == 3 {
        symbols.push(31)
        self.ascii_end()
      }
    } else {
      symbols.push(31)
    }
    append_edifact(self.out, symbols)
  }
}

///|
fn Encoder::edifact(self : Encoder) -> Unit raise EncodeError {
  let symbols = []
  while self.left() > 0 {
    symbols.push(self.eat())
    if symbols.length() == 4 {
      append_edifact(self.out, symbols)
      symbols.clear()
      if self.switch_mode() {
        break
      }
    } else if self.switch_mode() {
      break
    }
  }
  self.edifact_end(symbols)
}

///|
fn planned_codewords(
  data : Bytes,
  eci : Bool,
  options : EncodeOptions,
) -> Array[Int] raise EncodeError {
  planned_with_prefix(data, if eci { [241, 27] } else { [] }, options)
}

///|
fn planned_with_prefix(
  data : Bytes,
  out : Array[Int],
  options : EncodeOptions,
) -> Array[Int] raise EncodeError {
  ignore(select_size(0, options))
  let enc : Encoder = {
    data,
    options,
    out,
    pos: 0,
    mode: 0,
    pending: None,
    switches: optimize(data, out.length(), options),
  }
  let mut stalled = 0
  while enc.left() > 0 {
    match enc.pending {
      Some(v) => {
        out.push(v)
        enc.pending = None
      }
      None => ()
    }
    let before = out.length()
    match enc.mode {
      0 => enc.ascii()
      1 => enc.base256()
      2 => enc.edifact()
      3 => enc.x12()
      4 => enc.c40(false)
      _ => enc.c40(true)
    }
    if out.length() - before <= 1 {
      stalled += 1
      if stalled > 5 {
        raise InvalidOption("internal encoder failed to make progress")
      }
    } else {
      stalled = 0
    }
  }
  let s = select_size(out.length(), options)
  if s.capacity > out.length() && enc.mode != 0 {
    out.push(254)
  }
  out
}