use crate::{
    conv,
    device::{all_buffer_stages, all_image_stages},
    hub::{HUB, Token},
    resource::TexturePlacement,
    swap_chain::SwapChainLink,
    BufferAddress,
    BufferId,
    BufferUsage,
    CommandEncoderId,
    Extent3d,
    Origin3d,
    TextureId,
    TextureUsage,
};
use copyless::VecHelper as _;
use hal::command::RawCommandBuffer;
use std::iter;
const BITS_PER_BYTE: u32 = 8;
#[repr(C)]
#[derive(Debug)]
pub struct BufferCopyView {
    pub buffer: BufferId,
    pub offset: BufferAddress,
    pub row_pitch: u32,
    pub image_height: u32,
}
#[repr(C)]
#[derive(Debug)]
pub struct TextureCopyView {
    pub texture: TextureId,
    pub mip_level: u32,
    pub array_layer: u32,
    pub origin: Origin3d,
}
impl TextureCopyView {
    
    
    fn to_selector(&self, aspects: hal::format::Aspects) -> hal::image::SubresourceRange {
        let level = self.mip_level as hal::image::Level;
        let layer = self.array_layer as hal::image::Layer;
        hal::image::SubresourceRange {
            aspects,
            levels: level .. level + 1,
            layers: layer .. layer + 1,
        }
    }
    fn to_sub_layers(
        &self, aspects: hal::format::Aspects
    ) -> hal::image::SubresourceLayers {
        let layer = self.array_layer as hal::image::Layer;
        hal::image::SubresourceLayers {
            aspects,
            level: self.mip_level as hal::image::Level,
            layers: layer .. layer + 1,
        }
    }
}
#[no_mangle]
pub extern "C" fn wgpu_command_encoder_copy_buffer_to_buffer(
    command_encoder_id: CommandEncoderId,
    source: BufferId,
    source_offset: BufferAddress,
    destination: BufferId,
    destination_offset: BufferAddress,
    size: BufferAddress,
) {
    let mut token = Token::root();
    let (mut cmb_guard, mut token) = HUB.command_buffers.write(&mut token);
    let cmb = &mut cmb_guard[command_encoder_id];
    let (buffer_guard, _) = HUB.buffers.read(&mut token);
    
    
    let mut barriers = Vec::new();
    let (src_buffer, src_pending) = cmb
        .trackers
        .buffers
        .use_replace(&*buffer_guard, source, (), BufferUsage::COPY_SRC);
    barriers.extend(src_pending.map(|pending| hal::memory::Barrier::Buffer {
        states: pending.to_states(),
        target: &src_buffer.raw,
        families: None,
        range: None .. None,
    }));
    let (dst_buffer, dst_pending) = cmb
        .trackers
        .buffers
        .use_replace(&*buffer_guard, destination, (), BufferUsage::COPY_DST);
    barriers.extend(dst_pending.map(|pending| hal::memory::Barrier::Buffer {
        states: pending.to_states(),
        target: &dst_buffer.raw,
        families: None,
        range: None .. None,
    }));
    let region = hal::command::BufferCopy {
        src: source_offset,
        dst: destination_offset,
        size,
    };
    let cmb_raw = cmb.raw.last_mut().unwrap();
    unsafe {
        cmb_raw.pipeline_barrier(
            all_buffer_stages() .. all_buffer_stages(),
            hal::memory::Dependencies::empty(),
            barriers,
        );
        cmb_raw.copy_buffer(&src_buffer.raw, &dst_buffer.raw, iter::once(region));
    }
}
#[no_mangle]
pub extern "C" fn wgpu_command_encoder_copy_buffer_to_texture(
    command_encoder_id: CommandEncoderId,
    source: &BufferCopyView,
    destination: &TextureCopyView,
    copy_size: Extent3d,
) {
    let mut token = Token::root();
    let (mut cmb_guard, mut token) = HUB.command_buffers.write(&mut token);
    let cmb = &mut cmb_guard[command_encoder_id];
    let (buffer_guard, mut token) = HUB.buffers.read(&mut token);
    let (texture_guard, _) = HUB.textures.read(&mut token);
    let aspects = texture_guard[destination.texture].full_range.aspects;
    let (src_buffer, src_pending) = cmb
        .trackers
        .buffers
        .use_replace(&*buffer_guard, source.buffer, (), BufferUsage::COPY_SRC);
    let src_barriers = src_pending.map(|pending| hal::memory::Barrier::Buffer {
        states: pending.to_states(),
        target: &src_buffer.raw,
        families: None,
        range: None .. None,
    });
    let (dst_texture, dst_pending) = cmb.trackers.textures.use_replace(
        &*texture_guard,
        destination.texture,
        destination.to_selector(aspects),
        TextureUsage::COPY_DST,
    );
    let dst_barriers = dst_pending.map(|pending| hal::memory::Barrier::Image {
        states: pending.to_states(),
        target: &dst_texture.raw,
        families: None,
        range: pending.selector,
    });
    if let TexturePlacement::SwapChain(ref link) = dst_texture.placement {
        cmb.swap_chain_links.alloc().init(SwapChainLink {
            swap_chain_id: link.swap_chain_id.clone(),
            epoch: *link.epoch.lock(),
            image_index: link.image_index,
        });
    }
    let aspects = dst_texture.full_range.aspects;
    let bytes_per_texel = conv::map_texture_format(dst_texture.format)
        .surface_desc()
        .bits as u32
        / BITS_PER_BYTE;
    let buffer_width = source.row_pitch / bytes_per_texel;
    assert_eq!(source.row_pitch % bytes_per_texel, 0);
    let region = hal::command::BufferImageCopy {
        buffer_offset: source.offset,
        buffer_width,
        buffer_height: source.image_height,
        image_layers: destination.to_sub_layers(aspects),
        image_offset: conv::map_origin(destination.origin),
        image_extent: conv::map_extent(copy_size),
    };
    let cmb_raw = cmb.raw.last_mut().unwrap();
    let stages = all_buffer_stages() | all_image_stages();
    unsafe {
        cmb_raw.pipeline_barrier(
            stages .. stages,
            hal::memory::Dependencies::empty(),
            src_barriers.chain(dst_barriers),
        );
        cmb_raw.copy_buffer_to_image(
            &src_buffer.raw,
            &dst_texture.raw,
            hal::image::Layout::TransferDstOptimal,
            iter::once(region),
        );
    }
}
#[no_mangle]
pub extern "C" fn wgpu_command_encoder_copy_texture_to_buffer(
    command_encoder_id: CommandEncoderId,
    source: &TextureCopyView,
    destination: &BufferCopyView,
    copy_size: Extent3d,
) {
    let mut token = Token::root();
    let (mut cmb_guard, mut token) = HUB.command_buffers.write(&mut token);
    let cmb = &mut cmb_guard[command_encoder_id];
    let (buffer_guard, mut token) = HUB.buffers.read(&mut token);
    let (texture_guard, _) = HUB.textures.read(&mut token);
    let aspects = texture_guard[source.texture].full_range.aspects;
    let (src_texture, src_pending) = cmb.trackers.textures.use_replace(
        &*texture_guard,
        source.texture,
        source.to_selector(aspects),
        TextureUsage::COPY_SRC,
    );
    let src_barriers = src_pending.map(|pending| hal::memory::Barrier::Image {
        states: pending.to_states(),
        target: &src_texture.raw,
        families: None,
        range: pending.selector,
    });
    match src_texture.placement {
        TexturePlacement::SwapChain(_) => unimplemented!(),
        TexturePlacement::Void => unreachable!(),
        TexturePlacement::Memory(_) => (),
    }
    let (dst_buffer, dst_barriers) = cmb.trackers.buffers.use_replace(
        &*buffer_guard,
        destination.buffer,
        (),
        BufferUsage::COPY_DST,
    );
    let dst_barrier = dst_barriers.map(|pending| hal::memory::Barrier::Buffer {
        states: pending.to_states(),
        target: &dst_buffer.raw,
        families: None,
        range: None .. None,
    });
    let aspects = src_texture.full_range.aspects;
    let bytes_per_texel = conv::map_texture_format(src_texture.format)
        .surface_desc()
        .bits as u32
        / BITS_PER_BYTE;
    let buffer_width = destination.row_pitch / bytes_per_texel;
    assert_eq!(destination.row_pitch % bytes_per_texel, 0);
    let region = hal::command::BufferImageCopy {
        buffer_offset: destination.offset,
        buffer_width,
        buffer_height: destination.image_height,
        image_layers: source.to_sub_layers(aspects),
        image_offset: conv::map_origin(source.origin),
        image_extent: conv::map_extent(copy_size),
    };
    let cmb_raw = cmb.raw.last_mut().unwrap();
    let stages = all_buffer_stages() | all_image_stages();
    unsafe {
        cmb_raw.pipeline_barrier(
            stages .. stages,
            hal::memory::Dependencies::empty(),
            src_barriers.chain(dst_barrier),
        );
        cmb_raw.copy_image_to_buffer(
            &src_texture.raw,
            hal::image::Layout::TransferSrcOptimal,
            &dst_buffer.raw,
            iter::once(region),
        );
    }
}
#[no_mangle]
pub extern "C" fn wgpu_command_encoder_copy_texture_to_texture(
    command_encoder_id: CommandEncoderId,
    source: &TextureCopyView,
    destination: &TextureCopyView,
    copy_size: Extent3d,
) {
    let mut token = Token::root();
    let (mut cmb_guard, mut token) = HUB.command_buffers.write(&mut token);
    let cmb = &mut cmb_guard[command_encoder_id];
    let (_, mut token) = HUB.buffers.read(&mut token); 
    let (texture_guard, _) = HUB.textures.read(&mut token);
    
    
    let mut barriers = Vec::new();
    let aspects = texture_guard[source.texture].full_range.aspects &
        texture_guard[destination.texture].full_range.aspects;
    let (src_texture, src_pending) = cmb.trackers.textures.use_replace(
        &*texture_guard,
        source.texture,
        source.to_selector(aspects),
        TextureUsage::COPY_SRC,
    );
    barriers.extend(src_pending.map(|pending| hal::memory::Barrier::Image {
        states: pending.to_states(),
        target: &src_texture.raw,
        families: None,
        range: pending.selector,
    }));
    let (dst_texture, dst_pending) = cmb.trackers.textures.use_replace(
        &*texture_guard,
        destination.texture,
        destination.to_selector(aspects),
        TextureUsage::COPY_DST,
    );
    barriers.extend(dst_pending.map(|pending| hal::memory::Barrier::Image {
        states: pending.to_states(),
        target: &dst_texture.raw,
        families: None,
        range: pending.selector,
    }));
    if let TexturePlacement::SwapChain(ref link) = dst_texture.placement {
        cmb.swap_chain_links.alloc().init(SwapChainLink {
            swap_chain_id: link.swap_chain_id.clone(),
            epoch: *link.epoch.lock(),
            image_index: link.image_index,
        });
    }
    let aspects = src_texture.full_range.aspects & dst_texture.full_range.aspects;
    let region = hal::command::ImageCopy {
        src_subresource: source.to_sub_layers(aspects),
        src_offset: conv::map_origin(source.origin),
        dst_subresource: destination.to_sub_layers(aspects),
        dst_offset: conv::map_origin(destination.origin),
        extent: conv::map_extent(copy_size),
    };
    let cmb_raw = cmb.raw.last_mut().unwrap();
    unsafe {
        cmb_raw.pipeline_barrier(
            all_image_stages() .. all_image_stages(),
            hal::memory::Dependencies::empty(),
            barriers,
        );
        cmb_raw.copy_image(
            &src_texture.raw,
            hal::image::Layout::TransferSrcOptimal,
            &dst_texture.raw,
            hal::image::Layout::TransferDstOptimal,
            iter::once(region),
        );
    }
}