/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use dom_struct::dom_struct;
use webgpu_traits::{WebGPU, WebGPUComputePass, WebGPURequest};

use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUComputePassEncoderMethods;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::USVString;
use crate::dom::globalscope::GlobalScope;
use crate::dom::webgpu::gpubindgroup::GPUBindGroup;
use crate::dom::webgpu::gpubuffer::GPUBuffer;
use crate::dom::webgpu::gpucommandencoder::GPUCommandEncoder;
use crate::dom::webgpu::gpucomputepipeline::GPUComputePipeline;
use crate::script_runtime::CanGc;

#[dom_struct]
pub(crate) struct GPUComputePassEncoder {
    reflector_: Reflector,
    #[ignore_malloc_size_of = "defined in webgpu"]
    #[no_trace]
    channel: WebGPU,
    label: DomRefCell<USVString>,
    #[no_trace]
    compute_pass: WebGPUComputePass,
    command_encoder: Dom<GPUCommandEncoder>,
}

impl GPUComputePassEncoder {
    fn new_inherited(
        channel: WebGPU,
        parent: &GPUCommandEncoder,
        compute_pass: WebGPUComputePass,
        label: USVString,
    ) -> Self {
        Self {
            channel,
            reflector_: Reflector::new(),
            label: DomRefCell::new(label),
            compute_pass,
            command_encoder: Dom::from_ref(parent),
        }
    }

    pub(crate) fn new(
        global: &GlobalScope,
        channel: WebGPU,
        parent: &GPUCommandEncoder,
        compute_pass: WebGPUComputePass,
        label: USVString,
        can_gc: CanGc,
    ) -> DomRoot<Self> {
        reflect_dom_object(
            Box::new(GPUComputePassEncoder::new_inherited(
                channel,
                parent,
                compute_pass,
                label,
            )),
            global,
            can_gc,
        )
    }
}

impl GPUComputePassEncoderMethods<crate::DomTypeHolder> for GPUComputePassEncoder {
    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
    fn Label(&self) -> USVString {
        self.label.borrow().clone()
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
    fn SetLabel(&self, value: USVString) {
        *self.label.borrow_mut() = value;
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatchworkgroups>
    fn DispatchWorkgroups(&self, x: u32, y: u32, z: u32) {
        if let Err(e) = self
            .channel
            .0
            .send(WebGPURequest::ComputePassDispatchWorkgroups {
                compute_pass_id: self.compute_pass.0,
                x,
                y,
                z,
                device_id: self.command_encoder.device_id().0,
            })
        {
            warn!("Error sending WebGPURequest::ComputePassDispatchWorkgroups: {e:?}")
        }
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatchworkgroupsindirect>
    fn DispatchWorkgroupsIndirect(&self, buffer: &GPUBuffer, offset: u64) {
        if let Err(e) = self
            .channel
            .0
            .send(WebGPURequest::ComputePassDispatchWorkgroupsIndirect {
                compute_pass_id: self.compute_pass.0,
                buffer_id: buffer.id().0,
                offset,
                device_id: self.command_encoder.device_id().0,
            })
        {
            warn!("Error sending WebGPURequest::ComputePassDispatchWorkgroupsIndirect: {e:?}")
        }
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-endpass>
    fn End(&self) {
        if let Err(e) = self.channel.0.send(WebGPURequest::EndComputePass {
            compute_pass_id: self.compute_pass.0,
            device_id: self.command_encoder.device_id().0,
            command_encoder_id: self.command_encoder.id().0,
        }) {
            warn!("Failed to send WebGPURequest::EndComputePass: {e:?}");
        }
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup>
    fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, offsets: Vec<u32>) {
        if let Err(e) = self.channel.0.send(WebGPURequest::ComputePassSetBindGroup {
            compute_pass_id: self.compute_pass.0,
            index,
            bind_group_id: bind_group.id().0,
            offsets,
            device_id: self.command_encoder.device_id().0,
        }) {
            warn!("Error sending WebGPURequest::ComputePassSetBindGroup: {e:?}")
        }
    }

    /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-setpipeline>
    fn SetPipeline(&self, pipeline: &GPUComputePipeline) {
        if let Err(e) = self.channel.0.send(WebGPURequest::ComputePassSetPipeline {
            compute_pass_id: self.compute_pass.0,
            pipeline_id: pipeline.id().0,
            device_id: self.command_encoder.device_id().0,
        }) {
            warn!("Error sending WebGPURequest::ComputePassSetPipeline: {e:?}")
        }
    }
}

impl Drop for GPUComputePassEncoder {
    fn drop(&mut self) {
        if let Err(e) = self
            .channel
            .0
            .send(WebGPURequest::DropComputePass(self.compute_pass.0))
        {
            warn!("Failed to send WebGPURequest::DropComputePass with {e:?}");
        }
    }
}