use crate::*;
use windows::core::{IUnknown, Interface};
use windows::Win32::Graphics::{Direct3D11::*, Direct3D11on12::*, Direct3D12::*, Dxgi::*};

#[derive(Clone, PartialEq, Eq)]
pub struct RenderTarget {
    wrapper: ID3D11Resource,
    bitmap: ID2D1Bitmap1,
}

unsafe impl Send for RenderTarget {}
unsafe impl Sync for RenderTarget {}

impl Target for RenderTarget {
    fn bitmap(&self) -> &ID2D1Bitmap1 {
        &self.bitmap
    }

    fn size(&self) -> Size {
        unsafe {
            let size = self.bitmap.GetSize();
            Size::new(size.width, size.height)
        }
    }

    fn physical_size(&self) -> gecl::Size<u32> {
        unsafe {
            let size = self.bitmap.GetPixelSize();
            gecl::Size::new(size.width, size.height)
        }
    }
}

#[derive(Clone)]
pub struct Direct3D12 {
    d3d11on12_device: ID3D11On12Device,
    d2d1_factory: ID2D1Factory1,
    d2d1_device_context: ID2D1DeviceContext,
    d3d11_device_context: ID3D11DeviceContext,
}

impl Direct3D12 {
    pub unsafe fn new<T, U>(d3d12_device: &T, command_queue: &U) -> Result<Self> {
        let d3d12_device: ID3D12Device = (*(d3d12_device as *const _ as *const IUnknown)).cast()?;
        let command_queue: ID3D12CommandQueue =
            (*(command_queue as *const _ as *const IUnknown)).cast()?;
        let (d3d11on12_device, d3d11_device_context) = {
            let queues = [Some(command_queue.cast::<IUnknown>().unwrap())];
            let mut p = None;
            let mut dc = None;
            D3D11On12CreateDevice(
                &d3d12_device,
                D3D11_CREATE_DEVICE_BGRA_SUPPORT.0,
                &[],
                &queues,
                0,
                &mut p,
                &mut dc,
                std::ptr::null_mut(),
            )
            .map(|_| (p.unwrap().cast::<ID3D11On12Device>().unwrap(), dc.unwrap()))?
        };
        let d2d1_factory = {
            let mut p: Option<ID2D1Factory1> = None;
            D2D1CreateFactory(
                D2D1_FACTORY_TYPE_MULTI_THREADED,
                &ID2D1Factory1::IID,
                std::ptr::null(),
                &mut p as *mut _ as _,
            )
            .map(|_| p.unwrap())?
        };
        let dxgi_device = d3d11on12_device.cast::<IDXGIDevice>()?;
        let d2d1_device = d2d1_factory.CreateDevice(&dxgi_device)?;
        let d2d1_device_context =
            d2d1_device.CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE)?;
        Ok(Self {
            d3d11on12_device,
            d2d1_factory,
            d2d1_device_context,
            d3d11_device_context,
        })
    }

    fn back_buffers(&self, swap_chain: &IDXGISwapChain1) -> Result<Vec<RenderTarget>> {
        unsafe {
            let desc = swap_chain.GetDesc1()?;
            let bmp_props = D2D1_BITMAP_PROPERTIES1 {
                pixelFormat: D2D1_PIXEL_FORMAT {
                    format: desc.Format,
                    alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
                },
                bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
                ..Default::default()
            };
            let mut targets = vec![];
            for i in 0..desc.BufferCount {
                let buffer: ID3D12Resource = swap_chain.GetBuffer(i)?;
                let flags = D3D11_RESOURCE_FLAGS {
                    BindFlags: D3D11_BIND_RENDER_TARGET.0,
                    ..Default::default()
                };
                let wrapper = {
                    let mut wrapper: Option<ID3D11Resource> = None;
                    self.d3d11on12_device
                        .CreateWrappedResource(
                            &buffer,
                            &flags,
                            D3D12_RESOURCE_STATE_RENDER_TARGET,
                            D3D12_RESOURCE_STATE_PRESENT,
                            &mut wrapper,
                        )
                        .map(|_| wrapper.unwrap())?
                };
                let surface = wrapper.cast::<IDXGISurface>()?;
                let bitmap = {
                    self.d2d1_device_context
                        .CreateBitmapFromDxgiSurface(&surface, &bmp_props)?
                };
                targets.push(RenderTarget { wrapper, bitmap });
            }
            Ok(targets)
        }
    }
}

impl Context<Direct3D12> {
    #[inline]
    pub unsafe fn create_back_buffers<T>(&self, swap_chain: &T) -> Result<Vec<RenderTarget>> {
        let p = swap_chain as *const _ as *const IUnknown;
        let swap_chain: IDXGISwapChain1 = (*p).cast()?;
        let ret = self.backend.back_buffers(&swap_chain);
        ret
    }

    pub unsafe fn create_render_target<T>(&self, target: &T) -> Result<RenderTarget> {
        let target = target as *const _ as *const IUnknown;
        let resource: ID3D12Resource = (*target).cast()?;
        let desc = resource.GetDesc();
        if cfg!(debug_assertions) {
            assert!(
                (desc.Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) != D3D12_RESOURCE_FLAG_NONE
            );
        }
        let wrapper = {
            let mut wrapper: Option<ID3D11Resource> = None;
            self.backend
                .d3d11on12_device
                .CreateWrappedResource(
                    &resource,
                    &D3D11_RESOURCE_FLAGS {
                        BindFlags: D3D11_BIND_RENDER_TARGET.0,
                        ..Default::default()
                    },
                    D3D12_RESOURCE_STATE_RENDER_TARGET,
                    D3D12_RESOURCE_STATE_COMMON,
                    &mut wrapper,
                )
                .map(|_| wrapper.unwrap())?
        };
        let surface: IDXGISurface = wrapper.cast()?;
        let bitmap = {
            self.backend
                .d2d1_device_context
                .CreateBitmapFromDxgiSurface(
                    &surface,
                    &D2D1_BITMAP_PROPERTIES1 {
                        pixelFormat: D2D1_PIXEL_FORMAT {
                            format: desc.Format,
                            alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
                        },
                        bitmapOptions: D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
                        ..Default::default()
                    },
                )?
        };
        Ok(RenderTarget { wrapper, bitmap })
    }

    #[inline]
    pub fn flush(&self) {
        unsafe {
            self.backend.d3d11_device_context.Flush();
        }
    }
}

unsafe impl Send for Direct3D12 {}
unsafe impl Sync for Direct3D12 {}

impl Backend for Direct3D12 {
    type RenderTarget = RenderTarget;

    #[inline]
    fn device_context(&self) -> &ID2D1DeviceContext {
        &self.d2d1_device_context
    }

    #[inline]
    fn d2d1_factory(&self) -> &ID2D1Factory1 {
        &self.d2d1_factory
    }

    #[inline]
    fn begin_draw(&self, target: &Self::RenderTarget) {
        unsafe {
            self.d3d11on12_device
                .AcquireWrappedResources(&[Some(target.wrapper.clone())]);
        }
    }

    #[inline]
    fn end_draw(&self, target: &Self::RenderTarget) {
        unsafe {
            self.d3d11on12_device
                .ReleaseWrappedResources(&[Some(target.wrapper.clone())]);
            self.d3d11_device_context.Flush();
            self.d3d11_device_context.ClearState();
        }
    }
}
