///| Write bytes representation of an `Int` by specified endianness into target `FixedArray[Byte]` at certain offset.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_into_bytes(
  v : Int,
  endian : Endianness,
  target : FixedArray[Byte],
  offset : Int
) -> Unit {
  // assert_true!(offset + 4 <= target.length())
  for bs = 0; bs < 4; bs = bs + 1 {
    let bd = endianness_index(bs, 4, endian)
    target[offset + bd] = ((v >> (bs << 3)) & 0xFF).to_byte()
  }
}

///| Write bytes representation of an `Int` by little-endian into target `FixedArray[Byte]` at certain offset.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_into_bytes_le(
  v : Int,
  target : FixedArray[Byte],
  offset : Int
) -> Unit {
  int_into_bytes(v, LittleEndian, target, offset)
}

///| Write bytes representation of an `Int` by big-endian into target `FixedArray[Byte]` at certain offset.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_into_bytes_be(
  v : Int,
  target : FixedArray[Byte],
  offset : Int
) -> Unit {
  int_into_bytes(v, BigEndian, target, offset)
}

///|
test "int_into_bytes(series)" {
  let cases : Array[
    (Int, Int, FixedArray[Byte], FixedArray[Byte], FixedArray[Byte]),
  ] = [
    (
      0x07_06_05_04,
      0,
      FixedArray::from_array([0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc]),
      FixedArray::from_array([0x04, 0x05, 0x06, 0x07, 0xcc, 0xcc]),
      FixedArray::from_array([0x07, 0x06, 0x05, 0x04, 0xcc, 0xcc]),
    ),
    (
      0xff_0b_0c_0d,
      1,
      FixedArray::from_array([0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa]),
      FixedArray::from_array([0xaa, 0x0d, 0x0c, 0x0b, 0xff, 0xaa]),
      FixedArray::from_array([0xaa, 0xff, 0x0b, 0x0c, 0x0d, 0xaa]),
    ),
    (
      0x44_33_22_11,
      2,
      FixedArray::from_array([0xdf, 0xfd, 0xdf, 0xdf, 0xdf, 0xdf]),
      FixedArray::from_array([0xdf, 0xfd, 0x11, 0x22, 0x33, 0x44]),
      FixedArray::from_array([0xdf, 0xfd, 0x44, 0x33, 0x22, 0x11]),
    ),
  ]
  for icase = 0; icase < cases.length(); icase = icase + 1 {
    let (input, pos, original, le_expected, be_expected) = cases[icase]
    let buffer = original.copy()
    int_into_bytes(input, LittleEndian, buffer, pos)
    test_bytes_eq(buffer, le_expected)
    let buffer = original.copy()
    int_into_bytes(input, BigEndian, buffer, pos)
    test_bytes_eq(buffer, be_expected)
    let buffer = original.copy()
    int_into_bytes(input, NetworkEndian, buffer, pos)
    test_bytes_eq(buffer, be_expected)
    let buffer = original.copy()
    int_into_bytes_le(input, buffer, pos)
    test_bytes_eq(buffer, le_expected)
    let buffer = original.copy()
    int_into_bytes_be(input, buffer, pos)
    test_bytes_eq(buffer, be_expected)
  }
}

///| Get bytes representation of an `Int` by specified endianness.
pub fn int_to_bytes(v : Int, endian : Endianness) -> FixedArray[Byte] {
  let ret : FixedArray[Byte] = FixedArray::make(4, 0)
  int_into_bytes(v, endian, ret, 0)
  ret
}

///| Get bytes representation of an `Int` by little-endian.
pub fn int_to_bytes_le(v : Int) -> FixedArray[Byte] {
  let ret : FixedArray[Byte] = FixedArray::make(4, 0)
  int_into_bytes(v, LittleEndian, ret, 0)
  ret
}

///| Get bytes representation of an `Int` by big-endian.
pub fn int_to_bytes_be(v : Int) -> FixedArray[Byte] {
  let ret : FixedArray[Byte] = FixedArray::make(4, 0)
  int_into_bytes(v, BigEndian, ret, 0)
  ret
}

///|
test "int_to_bytes(series)" {
  let input = 0xff_0b_0c_0d
  let le_expected = b"\x0d\x0c\x0b\xff".to_fixedarray()
  let be_expected = b"\xff\x0b\x0c\x0d".to_fixedarray()
  test_bytes_eq(int_to_bytes(input, LittleEndian), le_expected)
  test_bytes_eq(int_to_bytes(input, BigEndian), be_expected)
  test_bytes_eq(int_to_bytes(input, NetworkEndian), be_expected)
  test_bytes_eq(int_to_bytes_le(input), le_expected)
  test_bytes_eq(int_to_bytes_be(input), be_expected)
}

///| Extract an `Int` from certain position of `FixedArray[Byte]` by specified endianness.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_from_bytes(
  bytes : FixedArray[Byte],
  offset : Int,
  endian : Endianness,
  source~ : SourceLoc = _
) -> Int {
  if offset + 4 > bytes.length() {
    abort("FAILED: \{source} `\{offset} + 4 <= \{bytes.length()}` is not true")
  }
  let mut r = 0
  for bd = 0; bd < 4; bd = bd + 1 {
    let bs = offset + endianness_index(bd, 4, endian)
    r = r | ((bytes[bs].to_int() & 0xff) << (bd << 3))
  }
  r
}

///| Extract an `Int` from certain position of `FixedArray[Byte]` by little-endian.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_le_from_bytes(bytes : FixedArray[Byte], offset : Int) -> Int {
  int_from_bytes(bytes, offset, LittleEndian)
}

///| Extract an `Int` from certain position of `FixedArray[Byte]` by big-endian.
/// 
/// # Panics
/// 
/// 4 bytes of data are required from offset position. Panics when insufficient.
pub fn int_be_from_bytes(bytes : FixedArray[Byte], offset : Int) -> Int {
  int_from_bytes(bytes, offset, BigEndian)
}

///|
test "int_from_bytes(series)" {
  let input : FixedArray[Byte] = FixedArray::from_array([
    0x7c, 0x47, 0x18, 0x49, 0xff, 0x6c,
  ])
  let cases = [
    (0, 0x49_18_47_7c, 0x7c_47_18_49),
    (1, 0xff_49_18_47, 0x47_18_49_ff),
    (2, 0x6c_ff_49_18, 0x18_49_ff_6c),
  ]
  for icase = 0; icase < cases.length(); icase = icase + 1 {
    let (pos, le_expected, be_expected) = cases[icase]
    assert_eq(int_from_bytes(input, pos, LittleEndian), le_expected)
    assert_eq(int_le_from_bytes(input, pos), le_expected)
    assert_eq(int_from_bytes(input, pos, BigEndian), be_expected)
    assert_eq(int_from_bytes(input, pos, NetworkEndian), be_expected)
    assert_eq(int_be_from_bytes(input, pos), be_expected)
  }
}

///|
fn test_bytes_eq(
  l : FixedArray[Byte],
  r : FixedArray[Byte],
  source~ : SourceLoc = _
) -> Unit raise {
  if l.length() == r.length() {
    for i = 0; i < l.length(); i = i + 1 {
      if l[i] != r[i] {
        let msg = StringBuilder::new(size_hint=16)
        msg.write_string("byte mismatch at \{i}: ")
        msg.write_string(l[i].to_string())
        msg.write_string(" != ")
        msg.write_string(r[i].to_string())
        msg.write_string(" (\{source})")
        fail(msg.to_string())
      }
    }
  } else {
    let msg = StringBuilder::new(size_hint=16)
    msg.write_string("length mismatch: ")
    msg.write_string(l.length().to_string())
    msg.write_string(" != ")
    msg.write_string(r.length().to_string())
    fail(msg.to_string())
  }
}

///|
/// Converts a character representing a digit to its corresponding integer value.
///
/// Parameters:
///
/// * `char` : A character in the range '0' to '9'.
///
/// Returns the integer value of the digit.
///
/// Throws a panic error if the input character is not a valid digit.
///
/// Example:
///
/// ```moonbit
/// test "digit_2_int" {
///   inspect!(digit_2_int('5'), content="5")
///   inspect!(digit_2_int('0'), content="0")
/// }
/// 
/// test "panic digit_2_int/invalid_char" {
///   let _ = digit_2_int('a')
/// 
/// }
/// ```
pub fn digit_char_2_int(char : Char) -> Int {
  if DEBUGGER && (char < '0' || char > '9') {
    panic()
  }
  return char.to_int() - 0x30
}