//! Device

use crate::{
    com::WeakPtr,
    command_list::{CmdListType, CommandSignature, IndirectArgument},
    descriptor::{CpuDescriptor, DescriptorHeapFlags, DescriptorHeapType, RenderTargetViewDesc},
    heap::{Heap, HeapFlags, HeapProperties},
    pso, query, queue, Blob, CachedPSO, CommandAllocator, CommandQueue, D3DResult, DescriptorHeap,
    Fence, GraphicsCommandList, NodeMask, PipelineState, QueryHeap, Resource, RootSignature,
    Shader, TextureAddressMode,
};
use std::ops::Range;
use winapi::{um::d3d12, Interface};

pub type Device = WeakPtr<d3d12::ID3D12Device>;

#[cfg(feature = "libloading")]
impl crate::D3D12Lib {
    pub fn create_device<I: Interface>(
        &self,
        adapter: WeakPtr<I>,
        feature_level: crate::FeatureLevel,
    ) -> Result<D3DResult<Device>, libloading::Error> {
        type Fun = extern "system" fn(
            *mut winapi::um::unknwnbase::IUnknown,
            winapi::um::d3dcommon::D3D_FEATURE_LEVEL,
            winapi::shared::guiddef::REFGUID,
            *mut *mut winapi::ctypes::c_void,
        ) -> crate::HRESULT;

        let mut device = Device::null();
        let hr = unsafe {
            let func: libloading::Symbol<Fun> = self.lib.get(b"D3D12CreateDevice")?;
            func(
                adapter.as_unknown() as *const _ as *mut _,
                feature_level as _,
                &d3d12::ID3D12Device::uuidof(),
                device.mut_void(),
            )
        };

        Ok((device, hr))
    }
}

impl Device {
    #[cfg(feature = "implicit-link")]
    pub fn create<I: Interface>(
        adapter: WeakPtr<I>,
        feature_level: crate::FeatureLevel,
    ) -> D3DResult<Self> {
        let mut device = Device::null();
        let hr = unsafe {
            d3d12::D3D12CreateDevice(
                adapter.as_unknown() as *const _ as *mut _,
                feature_level as _,
                &d3d12::ID3D12Device::uuidof(),
                device.mut_void(),
            )
        };

        (device, hr)
    }

    pub fn create_heap(
        &self,
        size_in_bytes: u64,
        properties: HeapProperties,
        alignment: u64,
        flags: HeapFlags,
    ) -> D3DResult<Heap> {
        let mut heap = Heap::null();

        let desc = d3d12::D3D12_HEAP_DESC {
            SizeInBytes: size_in_bytes,
            Properties: properties.0,
            Alignment: alignment,
            Flags: flags.bits(),
        };

        let hr = unsafe { self.CreateHeap(&desc, &d3d12::ID3D12Heap::uuidof(), heap.mut_void()) };

        (heap, hr)
    }

    pub fn create_command_allocator(&self, list_type: CmdListType) -> D3DResult<CommandAllocator> {
        let mut allocator = CommandAllocator::null();
        let hr = unsafe {
            self.CreateCommandAllocator(
                list_type as _,
                &d3d12::ID3D12CommandAllocator::uuidof(),
                allocator.mut_void(),
            )
        };

        (allocator, hr)
    }

    pub fn create_command_queue(
        &self,
        list_type: CmdListType,
        priority: queue::Priority,
        flags: queue::CommandQueueFlags,
        node_mask: NodeMask,
    ) -> D3DResult<CommandQueue> {
        let desc = d3d12::D3D12_COMMAND_QUEUE_DESC {
            Type: list_type as _,
            Priority: priority as _,
            Flags: flags.bits(),
            NodeMask: node_mask,
        };

        let mut queue = CommandQueue::null();
        let hr = unsafe {
            self.CreateCommandQueue(
                &desc,
                &d3d12::ID3D12CommandQueue::uuidof(),
                queue.mut_void(),
            )
        };

        (queue, hr)
    }

    pub fn create_descriptor_heap(
        &self,
        num_descriptors: u32,
        heap_type: DescriptorHeapType,
        flags: DescriptorHeapFlags,
        node_mask: NodeMask,
    ) -> D3DResult<DescriptorHeap> {
        let desc = d3d12::D3D12_DESCRIPTOR_HEAP_DESC {
            Type: heap_type as _,
            NumDescriptors: num_descriptors,
            Flags: flags.bits(),
            NodeMask: node_mask,
        };

        let mut heap = DescriptorHeap::null();
        let hr = unsafe {
            self.CreateDescriptorHeap(
                &desc,
                &d3d12::ID3D12DescriptorHeap::uuidof(),
                heap.mut_void(),
            )
        };

        (heap, hr)
    }

    pub fn get_descriptor_increment_size(&self, heap_type: DescriptorHeapType) -> u32 {
        unsafe { self.GetDescriptorHandleIncrementSize(heap_type as _) }
    }

    pub fn create_graphics_command_list(
        &self,
        list_type: CmdListType,
        allocator: CommandAllocator,
        initial: PipelineState,
        node_mask: NodeMask,
    ) -> D3DResult<GraphicsCommandList> {
        let mut command_list = GraphicsCommandList::null();
        let hr = unsafe {
            self.CreateCommandList(
                node_mask,
                list_type as _,
                allocator.as_mut_ptr(),
                initial.as_mut_ptr(),
                &d3d12::ID3D12GraphicsCommandList::uuidof(),
                command_list.mut_void(),
            )
        };

        (command_list, hr)
    }

    pub fn create_query_heap(
        &self,
        heap_ty: query::QueryHeapType,
        count: u32,
        node_mask: NodeMask,
    ) -> D3DResult<QueryHeap> {
        let desc = d3d12::D3D12_QUERY_HEAP_DESC {
            Type: heap_ty as _,
            Count: count,
            NodeMask: node_mask,
        };

        let mut query_heap = QueryHeap::null();
        let hr = unsafe {
            self.CreateQueryHeap(
                &desc,
                &d3d12::ID3D12QueryHeap::uuidof(),
                query_heap.mut_void(),
            )
        };

        (query_heap, hr)
    }

    pub fn create_graphics_pipeline_state(
        &self,
        _root_signature: RootSignature,
        _vs: Shader,
        _ps: Shader,
        _gs: Shader,
        _hs: Shader,
        _ds: Shader,
        _node_mask: NodeMask,
        _cached_pso: CachedPSO,
        _flags: pso::PipelineStateFlags,
    ) -> D3DResult<PipelineState> {
        unimplemented!()
    }

    pub fn create_compute_pipeline_state(
        &self,
        root_signature: RootSignature,
        cs: Shader,
        node_mask: NodeMask,
        cached_pso: CachedPSO,
        flags: pso::PipelineStateFlags,
    ) -> D3DResult<PipelineState> {
        let mut pipeline = PipelineState::null();
        let desc = d3d12::D3D12_COMPUTE_PIPELINE_STATE_DESC {
            pRootSignature: root_signature.as_mut_ptr(),
            CS: *cs,
            NodeMask: node_mask,
            CachedPSO: *cached_pso,
            Flags: flags.bits(),
        };

        let hr = unsafe {
            self.CreateComputePipelineState(
                &desc,
                &d3d12::ID3D12PipelineState::uuidof(),
                pipeline.mut_void(),
            )
        };

        (pipeline, hr)
    }

    pub fn create_sampler(
        &self,
        sampler: CpuDescriptor,
        filter: d3d12::D3D12_FILTER,
        address_mode: TextureAddressMode,
        mip_lod_bias: f32,
        max_anisotropy: u32,
        comparison_op: d3d12::D3D12_COMPARISON_FUNC,
        border_color: [f32; 4],
        lod: Range<f32>,
    ) {
        let desc = d3d12::D3D12_SAMPLER_DESC {
            Filter: filter,
            AddressU: address_mode[0],
            AddressV: address_mode[1],
            AddressW: address_mode[2],
            MipLODBias: mip_lod_bias,
            MaxAnisotropy: max_anisotropy,
            ComparisonFunc: comparison_op,
            BorderColor: border_color,
            MinLOD: lod.start,
            MaxLOD: lod.end,
        };

        unsafe {
            self.CreateSampler(&desc, sampler);
        }
    }

    pub fn create_root_signature(
        &self,
        blob: Blob,
        node_mask: NodeMask,
    ) -> D3DResult<RootSignature> {
        let mut signature = RootSignature::null();
        let hr = unsafe {
            self.CreateRootSignature(
                node_mask,
                blob.GetBufferPointer(),
                blob.GetBufferSize(),
                &d3d12::ID3D12RootSignature::uuidof(),
                signature.mut_void(),
            )
        };

        (signature, hr)
    }

    pub fn create_command_signature(
        &self,
        root_signature: RootSignature,
        arguments: &[IndirectArgument],
        stride: u32,
        node_mask: NodeMask,
    ) -> D3DResult<CommandSignature> {
        let mut signature = CommandSignature::null();
        let desc = d3d12::D3D12_COMMAND_SIGNATURE_DESC {
            ByteStride: stride,
            NumArgumentDescs: arguments.len() as _,
            pArgumentDescs: arguments.as_ptr() as *const _,
            NodeMask: node_mask,
        };

        let hr = unsafe {
            self.CreateCommandSignature(
                &desc,
                root_signature.as_mut_ptr(),
                &d3d12::ID3D12CommandSignature::uuidof(),
                signature.mut_void(),
            )
        };

        (signature, hr)
    }

    pub fn create_render_target_view(
        &self,
        resource: Resource,
        desc: &RenderTargetViewDesc,
        descriptor: CpuDescriptor,
    ) {
        unsafe {
            self.CreateRenderTargetView(resource.as_mut_ptr(), &desc.0 as *const _, descriptor);
        }
    }

    // TODO: interface not complete
    pub fn create_fence(&self, initial: u64) -> D3DResult<Fence> {
        let mut fence = Fence::null();
        let hr = unsafe {
            self.CreateFence(
                initial,
                d3d12::D3D12_FENCE_FLAG_NONE,
                &d3d12::ID3D12Fence::uuidof(),
                fence.mut_void(),
            )
        };

        (fence, hr)
    }
}
