// 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.

///|
fn parse_frame_size_info(
  src : Bytes,
  start : Int,
) -> (Int, UInt64) raise ZstdError {
  let src_len = src.length()
  ensure_range(src_len, start, 4)
  let magic = read_u32_le(src, start)
  if (magic & skippable_magic_mask) == skippable_magic_start {
    ensure_range(src_len, start, 8)
    let user_size = read_u32_le(src, start + 4).to_uint64()
    let frame_size = user_size + (8 : UInt64)
    let remaining = (src_len - start).to_uint64()
    if frame_size > remaining {
      raise SrcSizeWrong
    }
    (frame_size.to_int(), (0 : UInt64))
  } else if magic != zstd_magic_number {
    raise CorruptionDetected
  } else {
    ensure_range(src_len, start, 5)
    let descriptor = src[start + 4].to_uint()
    if ((descriptor >> 3) & 1) == 1 {
      raise CorruptionDetected
    }

    let frame_content_size_flag = descriptor >> 6
    let single_segment = ((descriptor >> 5) & 1) == 1
    let checksum_flag = ((descriptor >> 2) & 1) == 1
    let dict_id_flag = descriptor & 0x3

    let mut pos = start + 5
    let mut block_size_max = zstd_block_size_max
    if !single_segment {
      ensure_range(src_len, pos, 1)
      let window_size = window_size_from_descriptor(src[pos].to_uint())
      block_size_max = min_u64(window_size, zstd_block_size_max)
      pos = pos + 1
    }

    let dict_size = dict_id_size(dict_id_flag)
    ensure_range(src_len, pos, dict_size)
    pos = pos + dict_size

    let fcs_size = frame_content_size_size(
      frame_content_size_flag, single_segment,
    )
    ensure_range(src_len, pos, fcs_size)

    let mut has_content_size = false
    let mut content_size : UInt64 = 0
    match fcs_size {
      0 => ()
      1 => {
        has_content_size = true
        content_size = src[pos].to_uint().to_uint64()
      }
      2 => {
        has_content_size = true
        content_size = src[pos].to_uint().to_uint64() +
          (src[pos + 1].to_uint().to_uint64() << 8) +
          (256 : UInt64)
      }
      4 => {
        has_content_size = true
        content_size = read_u32_le(src, pos).to_uint64()
      }
      8 => {
        has_content_size = true
        content_size = read_u64_le(src, pos)
      }
      _ => raise CorruptionDetected
    }
    pos = pos + fcs_size

    if single_segment {
      if !has_content_size {
        raise CorruptionDetected
      }
      block_size_max = min_u64(content_size, zstd_block_size_max)
    }

    let mut nb_blocks : UInt64 = 0
    let mut last_block = false
    while !last_block {
      ensure_range(src_len, pos, 3)
      let block_header : UInt = src[pos].to_uint() +
        (src[pos + 1].to_uint() << 8) +
        (src[pos + 2].to_uint() << 16)
      pos = pos + 3

      last_block = (block_header & 1) == 1
      let block_type = (block_header >> 1) & 0x3
      let block_size = (block_header >> 3).to_uint64()
      let c_block_size = match block_type {
        0 => block_size
        1 => (1 : UInt64)
        2 => block_size
        _ => raise CorruptionDetected
      }

      let remaining = (src_len - pos).to_uint64()
      if c_block_size > remaining {
        raise SrcSizeWrong
      }
      pos = pos + c_block_size.to_int()
      nb_blocks = nb_blocks + 1
    }

    if checksum_flag {
      ensure_range(src_len, pos, 4)
      pos = pos + 4
    }

    let frame_bound = if has_content_size {
      content_size
    } else {
      nb_blocks * block_size_max
    }
    (pos - start, frame_bound)
  }
}

///|
fn window_size_from_descriptor(descriptor : UInt) -> UInt64 {
  let exponent = descriptor >> 3
  let mantissa = descriptor & 0x7
  let window_log = 10 + exponent.reinterpret_as_int()
  let window_base = (1 : UInt64) << window_log
  let window_add = (window_base >> 3) * mantissa.to_uint64()
  window_base + window_add
}

///|
fn frame_content_size_size(flag : UInt, single_segment : Bool) -> Int {
  if flag == 0 {
    if single_segment {
      1
    } else {
      0
    }
  } else if flag == 1 {
    2
  } else if flag == 2 {
    4
  } else {
    8
  }
}

///|
fn dict_id_size(flag : UInt) -> Int {
  if flag == 0 {
    0
  } else if flag == 1 {
    1
  } else if flag == 2 {
    2
  } else {
    4
  }
}