///|
pub fn code128_checksum(symbols : ArrayView[Int]) -> Int {
  if symbols.is_empty() {
    0
  } else {
    let sum = for i = 1, acc = symbols[0]; i < symbols.length(); i = i + 1 {
      continue i + 1, acc + symbols[i] * i
    } nobreak {
      acc
    }
    sum % 103
  }
}

///|
pub fn verify_code128_symbols(symbols_with_check : ArrayView[Int]) -> Bool {
  let len = symbols_with_check.length()
  if len < 3 {
    false
  } else if symbols_with_check[len - 1] == 106 {
    let check_pos = len - 2
    code128_checksum(symbols_with_check[:check_pos]) ==
    symbols_with_check[check_pos]
  } else {
    let body = symbols_with_check[:len - 1]
    code128_checksum(body) == symbols_with_check[len - 1]
  }
}

///|
pub fn is_valid_code128_symbol(symbol : Int) -> Bool {
  symbol >= 0 && symbol <= 106
}

///|
pub struct Code128Encoded {
  data : String
  symbols : Array[Int]
  checksum : Int
  symbol_count : Int
  module_count : Int
  set_switches : Int
} derive(Debug, Eq)

///|
pub enum Code128Error {
  UnsupportedCharacter(Int)
  InvalidFNC1Placement
  EmptyPayload
} derive(Debug, Eq)

///|
fn validate_fnc1_placement(data : String) -> Code128Error? {
  if data.get_char(data.length() - 1) == Some('\u{1D}') {
    Some(InvalidFNC1Placement)
  } else {
    None
  }
}

///|
pub fn encode_code128(data : String) -> Result[Code128Encoded, Code128Error] {
  if data.is_empty() {
    return Err(EmptyPayload)
  }
  match validate_fnc1_placement(data) {
    Some(error) => return Err(error)
    None => ()
  }
  let symbols : Array[Int] = []
  let mut index = 0
  let mut switches = 0
  let mut mode = if starts_with_fnc1(data) {
    if has_numeric_pair(data, 1) {
      2
    } else {
      1
    }
  } else if has_numeric_pair(data, 0) && numeric_run(data, 0) >= 4 {
    2
  } else {
    1
  }
  symbols.push(if mode == 2 { 105 } else { 104 })
  if starts_with_fnc1(data) {
    symbols.push(102)
    index = 1
  }
  while index < data.length() {
    if mode == 2 {
      if is_fnc1_at(data, index) {
        symbols.push(102)
        index = index + 1
      } else if has_numeric_pair(data, index) {
        symbols.push(two_digit_value(data, index))
        index = index + 2
      } else {
        symbols.push(100)
        mode = 1
        switches = switches + 1
      }
    } else if is_fnc1_at(data, index) {
      symbols.push(102)
      index = index + 1
    } else if numeric_run(data, index) >= 4 && has_numeric_pair(data, index) {
      symbols.push(99)
      mode = 2
      switches = switches + 1
    } else {
      let ch = data.get_char(index).unwrap()
      if ch.to_int() < 32 || ch.to_int() > 127 {
        return Err(UnsupportedCharacter(ch.to_int()))
      }
      symbols.push(ch.to_int() - 32)
      index = index + 1
    }
  }
  let checksum = code128_checksum(symbols)
  symbols.push(checksum)
  symbols.push(106)
  Ok({
    data,
    symbols,
    checksum,
    symbol_count: symbols.length(),
    module_count: code128_module_count(symbols),
    set_switches: switches,
  })
}

///|
fn starts_with_fnc1(data : String) -> Bool {
  data.get_char(0) == Some('\u{1D}')
}

///|
fn is_fnc1_at(data : String, index : Int) -> Bool {
  data.get_char(index) == Some('\u{1D}')
}

///|
fn has_numeric_pair(data : String, index : Int) -> Bool {
  data.get_char(index) is Some(first) &&
  data.get_char(index + 1) is Some(second) &&
  first.is_ascii_digit() &&
  second.is_ascii_digit()
}

///|
fn numeric_run(data : String, index : Int) -> Int {
  let mut count = 0
  let mut pos = index
  while pos < data.length() && data.get_char(pos).unwrap().is_ascii_digit() {
    count = count + 1
    pos = pos + 1
  }
  count
}

///|
fn two_digit_value(data : String, index : Int) -> Int {
  (data[index].to_int() - ('0' : UInt16).to_int()) * 10 +
  data[index + 1].to_int() -
  ('0' : UInt16).to_int()
}

///|
pub fn code128_module_count(symbols : ArrayView[Int]) -> Int {
  let mut total = 0
  for symbol in symbols {
    let width = code128_width(symbol)
    total = total + width
  }
  total
}

///|
pub fn encode_gs1_128(
  message : GS1Message,
) -> Result[Code128Encoded, Code128Error] {
  let parts : Array[String] = []
  for index, element in message.elements {
    parts.push(element.value)
    if index + 1 < message.elements.length() && element.ai.kind is Variable {
      parts.push("\u{1D}")
    }
  }
  encode_code128("\u{1D}" + parts.join(""))
}

///|
pub fn render_code128_ascii(
  encoded : Code128Encoded,
  quiet_zone? : Int = 10,
  bar? : String = "██",
  space? : String = "  ",
) -> String {
  let output = StringBuilder()
  for _ in 0.. Bool {
  symbols.length() >= 2 && is_start_code(symbols[0]) && symbols[1] == 102
}

///|
fn is_start_code(symbol : Int) -> Bool {
  symbol == 103 || symbol == 104 || symbol == 105
}