// Byte Compaction (ISO/IEC 15438 4.4.3).
//
// Bytes are processed in groups of six.  Each group is read as a big endian
// 48 bit integer and written back as five base 900 codewords; any remaining
// one to five bytes are emitted literally.

///|
/// Number of bytes compressed into five codewords per group.
pub let byte_group_size : Int = 6

///|
/// Number of codewords a full byte group produces.
pub let byte_group_codewords : Int = 5

///|
/// `true` if `count` bytes form complete six byte groups, which is the
/// condition for the 924 latch instead of the 901 latch.
pub fn is_byte_group_multiple(count : Int) -> Bool {
  count > 0 && count % byte_group_size == 0
}

///|
/// Number of base 900 codewords needed to describe a byte run of `count`
/// bytes, including the length count that 901 mode carries.
pub fn byte_capacity(count : Int) -> Int {
  if count <= 0 {
    return 0
  }
  let groups = count / byte_group_size
  let rest = count % byte_group_size
  groups * byte_group_codewords + rest
}

///|
/// Number of codewords needed for an explicit byte length count: one
/// codeword while the count is below 900, otherwise two.
pub fn byte_count_codewords(count : Int) -> Int {
  if count <= 0 {
    0
  } else if count < 900 {
    1
  } else {
    2
  }
}

///|
/// Convert six bytes to five base 900 codewords.
pub fn bytes_to_base900(bytes : Array[Int]) -> Array[Int] raise EncodeError {
  if bytes.length() != byte_group_size {
    raise UnrepresentableChar(bytes.length())
  }
  let mut value : Int64 = 0L
  for byte in bytes {
    if byte < 0 || byte > max_byte_value {
      raise UnrepresentableChar(byte)
    }
    value = value * 256L + byte.to_int64()
  }
  let group : Array[Int] = [0, 0, 0, 0, 0]
  let mut position = byte_group_codewords
  while position > 0 {
    position -= 1
    group[position] = (value % 900L).to_int()
    value = value / 900L
  }
  group
}

///|
/// Convert five base 900 codewords back to six bytes.
pub fn base900_to_bytes(words : Array[Int]) -> Array[Int] raise EncodeError {
  if words.length() != byte_group_codewords {
    raise UnrepresentableChar(words.length())
  }
  let mut value : Int64 = 0L
  for word in words {
    if word < 0 || word > 899 {
      raise UnrepresentableChar(word)
    }
    value = value * 900L + word.to_int64()
  }
  let bytes : Array[Int] = [0, 0, 0, 0, 0, 0]
  let mut position = byte_group_size
  while position > 0 {
    position -= 1
    bytes[position] = (value % 256L).to_int()
    value = value / 256L
  }
  bytes
}

///|
/// Encode a byte run with Byte Compaction.  Neither the 901/924 latch nor the
/// length count is included; `encode_high_level` adds those.
pub fn encode_byte(input : Array[Int]) -> Array[Int] raise EncodeError {
  encode_byte_segment(input, 0, input.length())
}

///|
/// Encode `input[start..start+count)` as six byte groups plus a literal tail.
pub fn encode_byte_segment(
  input : Array[Int],
  start : Int,
  count : Int,
) -> Array[Int] raise EncodeError {
  if count <= 0 {
    raise EmptyInput
  }
  let result : Array[Int] = []
  let mut index = 0
  while index + byte_group_size <= count {
    let group : Array[Int] = []
    let mut offset = 0
    while offset < byte_group_size {
      group.push(input[start + index + offset])
      offset += 1
    }
    for word in bytes_to_base900(group) {
      result.push(word)
    }
    index += byte_group_size
  }
  while index < count {
    let byte = input[start + index]
    if byte < 0 || byte > max_byte_value {
      raise UnrepresentableChar(byte)
    }
    result.push(byte)
    index += 1
  }
  result
}

///|
/// Decode Byte Compaction codewords (without latch and length count) back to
/// bytes.  Trailing incomplete groups are returned literally.
pub fn decode_byte(words : Array[Int]) -> Array[Int] raise EncodeError {
  if words.is_empty() {
    raise EmptyInput
  }
  let bytes : Array[Int] = []
  let mut index = 0
  while index + byte_group_codewords <= words.length() {
    let group : Array[Int] = []
    let mut offset = 0
    while offset < byte_group_codewords {
      group.push(words[index + offset])
      offset += 1
    }
    for byte in base900_to_bytes(group) {
      bytes.push(byte)
    }
    index += byte_group_codewords
  }
  while index < words.length() {
    bytes.push(words[index])
    index += 1
  }
  bytes
}