// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Generates one serialized ChaCha block with scalar rounds.
#cfg(not(any(target="native", target="wasm")))
fn chachaBlock(
  key : FixedArray[UInt],
  count : UInt,
  nonce : FixedArray[UInt],
  round : UInt,
  output : FixedArray[Byte],
) -> Unit {
  guard! key.length() == 8
  guard! nonce.length() == 3
  guard! output.length() == 64
  let state = FixedArray::make(16, 0U)
  state[0] = 0X61707865U
  state[1] = 0X3320646eU
  state[2] = 0X79622d32U
  state[3] = 0X6b206574U
  key.blit_to(state, len=8, dst_offset=4)
  state[12] = count
  nonce.blit_to(state, len=3, dst_offset=13)
  state.chachaBlockLoop(round)
  state[0] += 0X61707865U
  state[1] += 0X3320646eU
  state[2] += 0X79622d32U
  state[3] += 0X6b206574U
  for i in 0..<8 {
    state[i + 4] += key[i]
  }
  state[12] += count
  for i in 0..<3 {
    state[i + 13] += nonce[i]
  }
  for i in 0..<16 {
    let word = state[i]
    output[i * 4] = word.to_byte()
    output[i * 4 + 1] = (word >> 8).to_byte()
    output[i * 4 + 2] = (word >> 16).to_byte()
    output[i * 4 + 3] = (word >> 24).to_byte()
  }
}

///|
/// Generates one ChaCha block with one state row per vector. Each lane holds
/// one consecutive word, so a row rotation aligns the lanes for the diagonal
/// rounds and its inverse restores the serialized state order.
#cfg(any(target="native", target="wasm"))
fn chachaBlock(
  key : FixedArray[UInt],
  count : UInt,
  nonce : FixedArray[UInt],
  round : UInt,
  output : FixedArray[Byte],
) -> Unit {
  guard! key.length() == 8
  guard! nonce.length() == 3
  let initial_a = @v128.i32x4_const(
    0X61707865, 0X3320646e, 0X79622d32, 0X6b206574,
  )
  let initial_b = @v128.i32x4_const(key[0], key[1], key[2], key[3])
  let initial_c = @v128.i32x4_const(key[4], key[5], key[6], key[7])
  let initial_d = @v128.i32x4_const(count, nonce[0], nonce[1], nonce[2])
  let mut a = initial_a
  let mut b = initial_b
  let mut c = initial_c
  let mut d = initial_d
  for _ in 0U.. Unit {
  for i in 0.. Unit {
  data.blit_to(
    target,
    len=length,
    src_offset=input_offset,
    dst_offset=target_offset,
  )
  let simd_end = length / 16 * 16
  for relative_offset = 0
      relative_offset < simd_end
      relative_offset = relative_offset + 16 {
    @v128.v128_store(
      target,
      target_offset + relative_offset,
      @v128.v128_xor(
        @v128.v128_load(target, target_offset + relative_offset),
        @v128.v128_load(key_stream, key_stream_offset + relative_offset),
      ),
    )
  }
  for relative_offset in simd_end..