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

///|
pub fn Queue::submit_one(self : Queue, cmd : CommandBuffer) -> Unit {
  let cmds : FixedArray[@c.CommandBuffer] = FixedArray::make(1, cmd.raw)
  @c.queue_submit(self.raw, 1UL, cmds)
}

///|
pub fn Queue::submit_one_for_index(self : Queue, cmd : CommandBuffer) -> UInt64 {
  let cmds : FixedArray[@c.CommandBuffer] = FixedArray::make(1, cmd.raw)
  @c.queue_submit_for_index(self.raw, 1UL, cmds)
}

///|
pub fn Queue::submit(self : Queue, cmds : Array[CommandBuffer]) -> Unit {
  let count = cmds.length()
  if count == 0 {
    return
  }
  let raw_cmds : Array[@c.CommandBuffer] = cmds.map(cmd => cmd.raw)
  let fixed = FixedArray::from_array(raw_cmds[:])
  @c.queue_submit(self.raw, count.to_uint64(), fixed)
}

///|
pub fn Queue::submit_for_index(
  self : Queue,
  cmds : Array[CommandBuffer],
) -> UInt64 {
  let count = cmds.length()
  if count == 0 {
    return 0UL
  }
  let raw_cmds : Array[@c.CommandBuffer] = cmds.map(cmd => cmd.raw)
  let fixed = FixedArray::from_array(raw_cmds[:])
  @c.queue_submit_for_index(self.raw, count.to_uint64(), fixed)
}

///|
pub fn Queue::on_submitted_work_done_sync(
  self : Queue,
  instance : Instance,
) -> UInt {
  @c.queue_on_submitted_work_done_sync(instance.raw, self.raw)
}

///|
pub fn Queue::on_submitted_work_done_sync_or_raise(
  self : Queue,
  instance : Instance,
) -> Unit raise QueueWorkDoneError {
  let status = self.on_submitted_work_done_sync(instance)
  if status != QUEUE_WORK_DONE_STATUS_SUCCESS {
    raise QueueWorkDoneError(status)
  }
}

///|
pub fn Queue::on_submitted_work_done_future_id_u64(self : Queue) -> UInt64 {
  @c.queue_on_submitted_work_done_future_id_u64(self.raw)
}

///|
pub fn Queue::on_submitted_work_done_async_status_u32(
  self : Queue,
  future_id : UInt64,
) -> UInt {
  let _ = self
  @c.queue_on_submitted_work_done_async_status_u32(future_id)
}

///|
pub fn Queue::on_submitted_work_done_async_clear(
  self : Queue,
  future_id : UInt64,
) -> Unit {
  let _ = self
  @c.queue_on_submitted_work_done_async_clear(future_id)
}

///|
pub fn Queue::submit_one_and_map_read_async(
  self : Queue,
  cmd : CommandBuffer,
  buffer : Buffer,
  offset : UInt64,
  size : UInt64,
) -> UInt64 {
  @c.queue_submit_one_and_map_read_async(
    self.raw,
    cmd.raw,
    buffer.raw,
    offset,
    size,
  )
}

///|
pub fn Queue::map_read_async_status_u32(
  self : Queue,
  future_id : UInt64,
) -> UInt {
  let _ = self
  @c.queue_map_read_async_status_u32(future_id)
}

///|
pub fn Queue::map_read_async_read(self : Queue, future_id : UInt64) -> Bytes {
  let _ = self
  let size = @c.queue_map_read_async_size_u64(future_id)
  if size == 0UL {
    return []
  }
  let out = Bytes::makei(size.to_int(), _ => 0)
  if @c.queue_map_read_async_read(future_id, out, size) {
    out
  } else {
    []
  }
}

///|
pub fn Queue::map_read_async_clear(self : Queue, future_id : UInt64) -> Unit {
  let _ = self
  @c.queue_map_read_async_clear(future_id)
}

///|
pub fn Queue::on_submitted_work_done_async_take_or_raise(
  self : Queue,
  future_id : UInt64,
) -> Unit raise QueueWorkDoneError {
  let status = self.on_submitted_work_done_async_status_u32(future_id)
  self.on_submitted_work_done_async_clear(future_id)
  if status != QUEUE_WORK_DONE_STATUS_SUCCESS {
    raise QueueWorkDoneError(status)
  }
}

///|
pub fn Queue::on_submitted_work_done_wait_or_raise(
  self : Queue,
  instance : Instance,
  timeout_ns? : UInt64 = 0UL,
) -> Unit raise QueueWorkDoneWaitError {
  if timeout_ns == 0UL {
    let status = self.on_submitted_work_done_sync(instance)
    if status != QUEUE_WORK_DONE_STATUS_SUCCESS {
      raise QueueWorkDoneWaitStatusFailed(status)
    }
    return
  }
  let future_id = self.on_submitted_work_done_future_id_u64()
  if future_id == 0UL {
    raise QueueWorkDoneWaitFutureUnavailable
  }
  let wait = instance.wait_any_one(future_id, timeout_ns~)
  if wait.status != WAIT_STATUS_SUCCESS {
    self.on_submitted_work_done_async_clear(future_id)
    raise QueueWorkDoneWaitAnyFailed(wait.status)
  }
  let status = self.on_submitted_work_done_async_status_u32(future_id)
  self.on_submitted_work_done_async_clear(future_id)
  if status != QUEUE_WORK_DONE_STATUS_SUCCESS {
    raise QueueWorkDoneWaitStatusFailed(status)
  }
}

///|
pub fn Queue::timestamp_period(self : Queue) -> Float {
  @c.queue_get_timestamp_period(self.raw)
}

///|
pub fn Queue::write_buffer(
  self : Queue,
  buffer : Buffer,
  buffer_offset : UInt64,
  data : Bytes,
) -> Unit {
  let size = data.length().to_uint64()
  let padded_size = align_up(size, 4UL)
  if padded_size == size {
    @c.queue_write_buffer(self.raw, buffer.raw, buffer_offset, data, size)
  } else {
    let len = data.length()
    let padded = Bytes::makei(padded_size.to_int(), i => {
      if i < len {
        data[i]
      } else {
        0
      }
    })
    @c.queue_write_buffer(
      self.raw,
      buffer.raw,
      buffer_offset,
      padded,
      padded_size,
    )
  }
}

///|
pub fn Queue::write_texture_rgba8_2d(
  self : Queue,
  texture : Texture,
  width : UInt,
  height : UInt,
  data : Bytes,
) -> Unit {
  let row_bytes = (width * 4U).reinterpret_as_int()
  let bytes_per_row = align_up_int(row_bytes, 256)
  let rows = height.reinterpret_as_int()
  let required_len = bytes_per_row * rows
  let payload = if data.length() == required_len {
    data
  } else {
    let src_len = data.length()
    Bytes::makei(required_len, i => {
      let row = i / bytes_per_row
      let col = i % bytes_per_row
      let src_index = row * row_bytes + col
      if col < row_bytes && src_index < src_len {
        data[src_index]
      } else {
        0
      }
    })
  }
  @c.queue_write_texture_rgba8_2d(
    self.raw,
    texture.raw,
    width,
    height,
    payload,
    payload.length().to_uint64(),
  )
}

///|
pub fn Queue::write_texture_rgba8_2d_mip_layer(
  self : Queue,
  texture : Texture,
  mip_level : UInt,
  array_layer : UInt,
  width : UInt,
  height : UInt,
  data : Bytes,
) -> Unit {
  let row_bytes = (width * 4U).reinterpret_as_int()
  let bytes_per_row = align_up_int(row_bytes, 256)
  let rows = height.reinterpret_as_int()
  let required_len = bytes_per_row * rows
  let payload = if data.length() == required_len {
    data
  } else {
    let src_len = data.length()
    Bytes::makei(required_len, i => {
      let row = i / bytes_per_row
      let col = i % bytes_per_row
      let src_index = row * row_bytes + col
      if col < row_bytes && src_index < src_len {
        data[src_index]
      } else {
        0
      }
    })
  }
  @c.queue_write_texture_rgba8_2d_mip_layer(
    self.raw,
    texture.raw,
    mip_level,
    array_layer,
    width,
    height,
    payload,
    payload.length().to_uint64(),
  )
}

///|
pub fn Queue::write_texture_2d_bytes_per_pixel(
  self : Queue,
  texture : Texture,
  width : UInt,
  height : UInt,
  bytes_per_pixel : UInt,
  data : Bytes,
) -> Unit {
  let row_bytes = (width * bytes_per_pixel).reinterpret_as_int()
  let bytes_per_row = align_up_int(row_bytes, 256)
  let rows = height.reinterpret_as_int()
  let required_len = bytes_per_row * rows
  let payload = if data.length() == required_len {
    data
  } else {
    let src_len = data.length()
    Bytes::makei(required_len, i => {
      let row = i / bytes_per_row
      let col = i % bytes_per_row
      let src_index = row * row_bytes + col
      if col < row_bytes && src_index < src_len {
        data[src_index]
      } else {
        0
      }
    })
  }
  @c.queue_write_texture_2d_bytes_per_pixel(
    self.raw,
    texture.raw,
    width,
    height,
    bytes_per_pixel,
    payload,
    payload.length().to_uint64(),
  )
}

///|
pub fn Queue::write_texture_2d_bytes_per_pixel_mip_layer(
  self : Queue,
  texture : Texture,
  mip_level : UInt,
  array_layer : UInt,
  width : UInt,
  height : UInt,
  bytes_per_pixel : UInt,
  data : Bytes,
) -> Unit {
  let row_bytes = (width * bytes_per_pixel).reinterpret_as_int()
  let bytes_per_row = align_up_int(row_bytes, 256)
  let rows = height.reinterpret_as_int()
  let required_len = bytes_per_row * rows
  let payload = if data.length() == required_len {
    data
  } else {
    let src_len = data.length()
    Bytes::makei(required_len, i => {
      let row = i / bytes_per_row
      let col = i % bytes_per_row
      let src_index = row * row_bytes + col
      if col < row_bytes && src_index < src_len {
        data[src_index]
      } else {
        0
      }
    })
  }
  @c.queue_write_texture_2d_bytes_per_pixel_mip_layer(
    self.raw,
    texture.raw,
    mip_level,
    array_layer,
    width,
    height,
    bytes_per_pixel,
    payload,
    payload.length().to_uint64(),
  )
}

///|
pub fn Queue::write_texture_ptr(
  self : Queue,
  destination : @c.WGPUTexelCopyTextureInfoPtr,
  data : Bytes,
  data_layout : @c.WGPUTexelCopyBufferLayoutPtr,
  write_size : @c.WGPUExtent3DPtr,
  data_size? : UInt64 = 0UL,
) -> Unit {
  let size = if data_size == 0UL {
    data.length().to_uint64()
  } else {
    data_size
  }
  @c.wgpuQueueWriteTexture_bytes(
    self.raw,
    destination,
    data,
    size,
    data_layout,
    write_size,
  )
}

///|
pub fn Queue::set_label(self : Queue, label : String) -> Unit {
  let bytes = utf8_bytes(label)
  @c.queue_set_label_utf8(self.raw, bytes, bytes.length().to_uint64())
}

///|
pub fn Queue::release(self : Queue) -> Unit {
  @c.queue_release(self.raw)
}

///|
pub fn Queue::add_ref(self : Queue) -> Queue {
  @c.wgpuQueueAddRef(self.raw)
  { raw: self.raw }
}

///|
pub fn Queue::raw_handle(self : Queue) -> @c.Queue {
  self.raw
}