// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

use deno_core::error::AnyError;
use deno_core::error::{bad_resource_id, not_supported};
use deno_core::ResourceId;
use deno_core::ZeroCopyBuf;
use deno_core::{OpState, Resource};
use serde::Deserialize;
use std::borrow::Cow;

use super::error::WebGpuResult;
pub(crate) struct WebGpuTexture(pub(crate) wgpu_core::id::TextureId);
impl Resource for WebGpuTexture {
  fn name(&self) -> Cow<str> {
    "webGPUTexture".into()
  }
}

pub(crate) struct WebGpuTextureView(pub(crate) wgpu_core::id::TextureViewId);
impl Resource for WebGpuTextureView {
  fn name(&self) -> Cow<str> {
    "webGPUTextureView".into()
  }
}

pub fn serialize_texture_format(
  format: &str,
) -> Result<wgpu_types::TextureFormat, AnyError> {
  Ok(match format {
    // 8-bit formats
    "r8unorm" => wgpu_types::TextureFormat::R8Unorm,
    "r8snorm" => wgpu_types::TextureFormat::R8Snorm,
    "r8uint" => wgpu_types::TextureFormat::R8Uint,
    "r8sint" => wgpu_types::TextureFormat::R8Sint,

    // 16-bit formats
    "r16uint" => wgpu_types::TextureFormat::R16Uint,
    "r16sint" => wgpu_types::TextureFormat::R16Sint,
    "r16float" => wgpu_types::TextureFormat::R16Float,
    "rg8unorm" => wgpu_types::TextureFormat::Rg8Unorm,
    "rg8snorm" => wgpu_types::TextureFormat::Rg8Snorm,
    "rg8uint" => wgpu_types::TextureFormat::Rg8Uint,
    "rg8sint" => wgpu_types::TextureFormat::Rg8Sint,

    // 32-bit formats
    "r32uint" => wgpu_types::TextureFormat::R32Uint,
    "r32sint" => wgpu_types::TextureFormat::R32Sint,
    "r32float" => wgpu_types::TextureFormat::R32Float,
    "rg16uint" => wgpu_types::TextureFormat::Rg16Uint,
    "rg16sint" => wgpu_types::TextureFormat::Rg16Sint,
    "rg16float" => wgpu_types::TextureFormat::Rg16Float,
    "rgba8unorm" => wgpu_types::TextureFormat::Rgba8Unorm,
    "rgba8unorm-srgb" => wgpu_types::TextureFormat::Rgba8UnormSrgb,
    "rgba8snorm" => wgpu_types::TextureFormat::Rgba8Snorm,
    "rgba8uint" => wgpu_types::TextureFormat::Rgba8Uint,
    "rgba8sint" => wgpu_types::TextureFormat::Rgba8Sint,
    "bgra8unorm" => wgpu_types::TextureFormat::Bgra8Unorm,
    "bgra8unorm-srgb" => wgpu_types::TextureFormat::Bgra8UnormSrgb,
    // Packed 32-bit formats
    "rgb9e5ufloat" => return Err(not_supported()), // wgpu#967
    "rgb10a2unorm" => wgpu_types::TextureFormat::Rgb10a2Unorm,
    "rg11b10ufloat" => wgpu_types::TextureFormat::Rg11b10Float,

    // 64-bit formats
    "rg32uint" => wgpu_types::TextureFormat::Rg32Uint,
    "rg32sint" => wgpu_types::TextureFormat::Rg32Sint,
    "rg32float" => wgpu_types::TextureFormat::Rg32Float,
    "rgba16uint" => wgpu_types::TextureFormat::Rgba16Uint,
    "rgba16sint" => wgpu_types::TextureFormat::Rgba16Sint,
    "rgba16float" => wgpu_types::TextureFormat::Rgba16Float,

    // 128-bit formats
    "rgba32uint" => wgpu_types::TextureFormat::Rgba32Uint,
    "rgba32sint" => wgpu_types::TextureFormat::Rgba32Sint,
    "rgba32float" => wgpu_types::TextureFormat::Rgba32Float,

    // Depth and stencil formats
    "stencil8" => return Err(not_supported()), // wgpu#967
    "depth16unorm" => return Err(not_supported()), // wgpu#967
    "depth24plus" => wgpu_types::TextureFormat::Depth24Plus,
    "depth24plus-stencil8" => wgpu_types::TextureFormat::Depth24PlusStencil8,
    "depth32float" => wgpu_types::TextureFormat::Depth32Float,

    // BC compressed formats usable if "texture-compression-bc" is both
    // supported by the device/user agent and enabled in requestDevice.
    "bc1-rgba-unorm" => wgpu_types::TextureFormat::Bc1RgbaUnorm,
    "bc1-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc1RgbaUnormSrgb,
    "bc2-rgba-unorm" => wgpu_types::TextureFormat::Bc2RgbaUnorm,
    "bc2-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc2RgbaUnormSrgb,
    "bc3-rgba-unorm" => wgpu_types::TextureFormat::Bc3RgbaUnorm,
    "bc3-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc3RgbaUnormSrgb,
    "bc4-r-unorm" => wgpu_types::TextureFormat::Bc4RUnorm,
    "bc4-r-snorm" => wgpu_types::TextureFormat::Bc4RSnorm,
    "bc5-rg-unorm" => wgpu_types::TextureFormat::Bc5RgUnorm,
    "bc5-rg-snorm" => wgpu_types::TextureFormat::Bc5RgSnorm,
    "bc6h-rgb-ufloat" => wgpu_types::TextureFormat::Bc6hRgbUfloat,
    "bc6h-rgb-float" => wgpu_types::TextureFormat::Bc6hRgbSfloat, // wgpu#967
    "bc7-rgba-unorm" => wgpu_types::TextureFormat::Bc7RgbaUnorm,
    "bc7-rgba-unorm-srgb" => wgpu_types::TextureFormat::Bc7RgbaUnormSrgb,

    // "depth24unorm-stencil8" extension
    "depth24unorm-stencil8" => return Err(not_supported()), // wgpu#967

    // "depth32float-stencil8" extension
    "depth32float-stencil8" => return Err(not_supported()), // wgpu#967
    _ => unreachable!(),
  })
}

pub fn serialize_dimension(
  dimension: &str,
) -> wgpu_types::TextureViewDimension {
  match dimension {
    "1d" => wgpu_types::TextureViewDimension::D1,
    "2d" => wgpu_types::TextureViewDimension::D2,
    "2d-array" => wgpu_types::TextureViewDimension::D2Array,
    "cube" => wgpu_types::TextureViewDimension::Cube,
    "cube-array" => wgpu_types::TextureViewDimension::CubeArray,
    "3d" => wgpu_types::TextureViewDimension::D3,
    _ => unreachable!(),
  }
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GpuExtent3D {
  pub width: Option<u32>,
  pub height: Option<u32>,
  pub depth: Option<u32>,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTextureArgs {
  device_rid: ResourceId,
  label: Option<String>,
  size: GpuExtent3D,
  mip_level_count: Option<u32>,
  sample_count: Option<u32>,
  dimension: Option<String>,
  format: String,
  usage: u32,
}

pub fn op_webgpu_create_texture(
  state: &mut OpState,
  args: CreateTextureArgs,
  _zero_copy: Option<ZeroCopyBuf>,
) -> Result<WebGpuResult, AnyError> {
  let instance = state.borrow::<super::Instance>();
  let device_resource = state
    .resource_table
    .get::<super::WebGpuDevice>(args.device_rid)
    .ok_or_else(bad_resource_id)?;
  let device = device_resource.0;

  let descriptor = wgpu_core::resource::TextureDescriptor {
    label: args.label.map(Cow::from),
    size: wgpu_types::Extent3d {
      width: args.size.width.unwrap_or(1),
      height: args.size.height.unwrap_or(1),
      depth: args.size.depth.unwrap_or(1),
    },
    mip_level_count: args.mip_level_count.unwrap_or(1),
    sample_count: args.sample_count.unwrap_or(1),
    dimension: match args.dimension {
      Some(dimension) => match dimension.as_str() {
        "1d" => wgpu_types::TextureDimension::D1,
        "2d" => wgpu_types::TextureDimension::D2,
        "3d" => wgpu_types::TextureDimension::D3,
        _ => unreachable!(),
      },
      None => wgpu_types::TextureDimension::D2,
    },
    format: serialize_texture_format(&args.format)?,
    usage: wgpu_types::TextureUsage::from_bits(args.usage).unwrap(),
  };

  let (texture, maybe_err) = gfx_select!(device => instance.device_create_texture(
    device,
    &descriptor,
    std::marker::PhantomData
  ));

  let rid = state.resource_table.add(WebGpuTexture(texture));

  Ok(WebGpuResult::rid_err(rid, maybe_err))
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTextureViewArgs {
  texture_rid: ResourceId,
  label: Option<String>,
  format: Option<String>,
  dimension: Option<String>,
  aspect: Option<String>,
  base_mip_level: Option<u32>,
  mip_level_count: Option<u32>,
  base_array_layer: Option<u32>,
  array_layer_count: Option<u32>,
}

pub fn op_webgpu_create_texture_view(
  state: &mut OpState,
  args: CreateTextureViewArgs,
  _zero_copy: Option<ZeroCopyBuf>,
) -> Result<WebGpuResult, AnyError> {
  let instance = state.borrow::<super::Instance>();
  let texture_resource = state
    .resource_table
    .get::<WebGpuTexture>(args.texture_rid)
    .ok_or_else(bad_resource_id)?;
  let texture = texture_resource.0;

  let descriptor = wgpu_core::resource::TextureViewDescriptor {
    label: args.label.map(Cow::from),
    format: args
      .format
      .map(|s| serialize_texture_format(&s))
      .transpose()?,
    dimension: args.dimension.map(|s| serialize_dimension(&s)),
    aspect: match args.aspect {
      Some(aspect) => match aspect.as_str() {
        "all" => wgpu_types::TextureAspect::All,
        "stencil-only" => wgpu_types::TextureAspect::StencilOnly,
        "depth-only" => wgpu_types::TextureAspect::DepthOnly,
        _ => unreachable!(),
      },
      None => wgpu_types::TextureAspect::All,
    },
    base_mip_level: args.base_mip_level.unwrap_or(0),
    level_count: std::num::NonZeroU32::new(args.mip_level_count.unwrap_or(0)),
    base_array_layer: args.base_array_layer.unwrap_or(0),
    array_layer_count: std::num::NonZeroU32::new(
      args.array_layer_count.unwrap_or(0),
    ),
  };

  let (texture_view, maybe_err) = gfx_select!(texture => instance.texture_create_view(
    texture,
    &descriptor,
    std::marker::PhantomData
  ));

  let rid = state.resource_table.add(WebGpuTextureView(texture_view));

  Ok(WebGpuResult::rid_err(rid, maybe_err))
}
