// Decoding: the inverse of the compactions, used for round-trip verification.
//
// PDF417 puts great care into *encoding* and leaves a good deal of work to the
// decoder.  Text Compaction, for instance, has no explicit end-of-segment
// marker: padding of a trailing odd value is indistinguishable from a real
// punctuation shift, so a decoder has to fall back on plausibility checks.
// This module documents and implements those rules so that a generated symbol
// can be read back and compared with its input.

///|
/// Reverse the Text Compaction sub-mode automaton.  `values` is a sequence of
/// values 0..29 without the trailing pad heuristic applied.
pub fn decode_text_values(values : Array[Int]) -> Array[Int] raise EncodeError {
  let output : Array[Int] = []
  let mut index = 0
  let mut submode = Alpha
  while index < values.length() {
    let value = values[index]
    match submode {
      Alpha =>
        if value < 26 {
          output.push(value + 65)
        } else if value == 26 {
          output.push(32)
        } else if value == 27 {
          submode = Lower
        } else if value == 28 {
          submode = Mixed
        } else if index + 1 < values.length() {
          index += 1
          let character = punctuation_lookup(values[index])
          output.push(character)
        } else {
          raise UnrepresentableChar(value)
        }
      Lower =>
        if value < 26 {
          output.push(value + 97)
        } else if value == 26 {
          output.push(32)
        } else if value == 27 {
          submode = Alpha
        } else if value == 28 {
          submode = Mixed
        } else if index + 1 < values.length() {
          index += 1
          let character = punctuation_lookup(values[index])
          output.push(character)
        } else {
          raise UnrepresentableChar(value)
        }
      Mixed =>
        if value < 25 {
          output.push(mixed_lookup(value))
        } else if value == 25 {
          submode = Punctuation
        } else if value == 26 {
          output.push(32)
        } else if value == 27 {
          submode = Lower
        } else if value == 28 {
          submode = Alpha
        } else if index + 1 < values.length() {
          index += 1
          let character = punctuation_lookup(values[index])
          output.push(character)
        } else {
          raise UnrepresentableChar(value)
        }
      Punctuation =>
        if value < 30 {
          output.push(punctuation_lookup(value))
        } else {
          raise UnrepresentableChar(value)
        }
    }
    index += 1
  }
  output
}

///|
/// Character for a Punctuation sub-mode value.
fn punctuation_lookup(value : Int) -> Int raise EncodeError {
  if value < 0 || value >= punctuation_char.length() {
    raise UnrepresentableChar(value)
  }
  let character = punctuation_char[value]
  if character < 0 {
    raise UnrepresentableChar(value)
  }
  character
}

///|
/// Character for a Mixed sub-mode value.
fn mixed_lookup(value : Int) -> Int raise EncodeError {
  if value < 0 || value >= mixed_char.length() {
    raise UnrepresentableChar(value)
  }
  let character = mixed_char[value]
  if character < 0 {
    raise UnrepresentableChar(value)
  }
  character
}

///|
/// Unpack codewords into Text Compaction values, dropping a trailing
/// punctuation-shift pad when the resulting stream still decodes cleanly.
pub fn unpack_text_values(words : Array[Int]) -> Array[Int] {
  let values : Array[Int] = []
  for word in words {
    if word < 0 || word > 928 {
      return []
    }
    values.push(word / 30)
    values.push(word % 30)
  }
  if values.length() == 0 || values[values.length() - 1] != tc_text_pad {
    return values
  }
  // The stream ends in 29, which is either the pad for an odd number of
  // values or a genuine punctuation shift followed by a value.  Both readings
  // are usually decodable, so `padded_ok` (which for a real character
  // sequence consumes the last codeword as a pending shift) decides.
  let trimmed : Array[Int] = []
  for index in 0..<(values.length() - 1) {
    trimmed.push(values[index])
  }
  let padded_ok = text_values_decode(values)
  let trimmed_ok = text_values_decode(trimmed)
  if padded_ok {
    values
  } else if trimmed_ok {
    trimmed
  } else {
    values
  }
}

///|
/// `true` when `values` can be decoded without raising.
fn text_values_decode(values : Array[Int]) -> Bool {
  try decode_text_values(values) catch {
    _ => false
  } noraise {
    _ => true
  }
}

///|
/// Decode a Text Compaction codeword run back to characters.
pub fn decode_text(words : Array[Int]) -> Array[Int] raise EncodeError {
  if words.is_empty() {
    raise EmptyInput
  }
  decode_text_values(unpack_text_values(words))
}

///|
/// Decode a high level data codeword sequence (without the symbol length
/// descriptor) back to characters.
///
/// Text and byte segments are decoded exactly.  Numeric segments are decoded
/// greedily from the latch up to the next mode latch, which is how the
/// encoder emits them in the common single-segment case.
pub fn decode_high_level(words : Array[Int]) -> Array[Int] raise EncodeError {
  if words.is_empty() {
    raise EmptyInput
  }
  let output : Array[Int] = []
  let mut index = 0
  let mut mode = Text
  while index < words.length() {
    let word = words[index]
    if word == latch_text {
      mode = Text
      index += 1
      continue
    }
    if word == latch_numeric {
      mode = Numeric
      index += 1
      continue
    }
    if word == latch_byte || word == latch_byte_padded {
      mode = Byte
      index += 1
      let mut byte_count = 0
      if word == latch_byte_padded {
        byte_count = words[index]
        index += 1
        if byte_count >= 900 {
          byte_count = byte_count * 900 + words[index]
          index += 1
        }
      }
      if byte_count == 0 {
        // 924 mode: consume the rest of the sequence.
        let body : Array[Int] = []
        while index < words.length() {
          body.push(words[index])
          index += 1
        }
        for byte in decode_byte(body) {
          output.push(byte)
        }
      } else {
        let consumed = byte_capacity(byte_count)
        let body : Array[Int] = []
        let mut taken = 0
        while taken < consumed && index < words.length() {
          body.push(words[index])
          index += 1
          taken += 1
        }
        for byte in decode_byte(body) {
          output.push(byte)
        }
      }
      continue
    }
    if word == shift_byte {
      mode = Byte
      index += 1
      if index < words.length() {
        output.push(words[index])
        index += 1
      }
      mode = Text
      continue
    }
    if mode == Text {
      let body : Array[Int] = []
      while index < words.length() &&
            words[index] != latch_text &&
            words[index] != latch_numeric &&
            words[index] != latch_byte &&
            words[index] != latch_byte_padded {
        body.push(words[index])
        index += 1
      }
      for character in decode_text(body) {
        output.push(character)
      }
      continue
    }
    if mode == Numeric {
      let body : Array[Int] = []
      while index < words.length() &&
            words[index] != latch_text &&
            words[index] != latch_numeric &&
            words[index] != latch_byte &&
            words[index] != latch_byte_padded {
        body.push(words[index])
        index += 1
      }
      for digit in decode_numeric(body) {
        output.push(digit)
      }
      continue
    }
    raise UnrepresentableChar(word)
  }
  output
}

///|
/// Decoding without error handling.
pub fn decode_high_level_opt(words : Array[Int]) -> Array[Int]? {
  try decode_high_level(words) catch {
    _ => None
  } noraise {
    characters => Some(characters)
  }
}

///|
/// `true` when the high level decoding of `words` reproduces `input`.
pub fn round_trip_matches(input : Array[Int], words : Array[Int]) -> Bool {
  match decode_high_level_opt(words) {
    Some(decoded) => decoded == input
    None => false
  }
}

///|
/// Convert a string to its UTF-8 byte values.  Handy for callers that want
/// non-Latin-1 payloads, since the library itself works on 0..255 code units.
pub fn utf8_bytes(text : String) -> Array[Int] {
  let bytes : Array[Int] = []
  for character in text.iter() {
    let code = character.to_int()
    if code < 0x80 {
      bytes.push(code)
    } else if code < 0x800 {
      bytes.push(0xC0 | (code >> 6))
      bytes.push(0x80 | (code & 0x3F))
    } else if code < 0x10000 {
      bytes.push(0xE0 | (code >> 12))
      bytes.push(0x80 | ((code >> 6) & 0x3F))
      bytes.push(0x80 | (code & 0x3F))
    } else {
      bytes.push(0xF0 | (code >> 18))
      bytes.push(0x80 | ((code >> 12) & 0x3F))
      bytes.push(0x80 | ((code >> 6) & 0x3F))
      bytes.push(0x80 | (code & 0x3F))
    }
  }
  bytes
}