#![allow(dead_code)]

use std::{
    collections::BTreeMap,
    fmt::{Debug, Display},
    sync::Arc,
};

use crate::{
    buffers::{Buffer, BufferDesc},
    commands::{self, traits::CommandBufferExt},
    def_monotonic_id,
    device::{self, DeviceOwned},
    images::{self, Image, ImageDesc},
    util::{self, Rgba, WithLifetime},
};
use ash::vk;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct GraphResourceId(pub(crate) u32);

impl Display for GraphResourceId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "#{}", self.0)
    }
}

#[derive(Debug, Clone)]
pub enum GraphResourceDesc {
    Image(ImageDesc),
    Buffer(BufferDesc),
}

impl From<GraphResourceDesc> for GraphResource {
    fn from(value: GraphResourceDesc) -> Self {
        match value {
            GraphResourceDesc::Image(image_desc) => Self::ImageDesc(image_desc),
            GraphResourceDesc::Buffer(buffer_desc) => Self::BufferDesc(buffer_desc),
        }
    }
}

#[derive(Default, Debug, PartialEq, Eq)]
pub enum GraphResource {
    Framebuffer(Arc<Image>),
    ImportedImage(Arc<Image>),
    ImportedBuffer(Arc<Buffer>),
    Image(Arc<Image>),
    Buffer(Buffer),
    ImageDesc(ImageDesc),
    BufferDesc(BufferDesc),
    #[default]
    Default,
}

impl GraphResource {
    fn simple_hash(&self) -> u64 {
        use std::hash::{Hash, Hasher};
        let mut state = std::hash::DefaultHasher::new();
        let discr = core::mem::discriminant(self);
        discr.hash(&mut state);

        match self {
            GraphResource::Framebuffer(swapchain_frame) => (swapchain_frame.raw()).hash(&mut state),
            GraphResource::ImportedImage(image) => image.raw().hash(&mut state),
            GraphResource::ImportedBuffer(buffer) => buffer.raw().hash(&mut state),
            GraphResource::Image(image) => image.raw().hash(&mut state),
            GraphResource::Buffer(buffer) => buffer.raw().hash(&mut state),
            GraphResource::ImageDesc(image_desc) => image_desc.hash(&mut state),
            GraphResource::BufferDesc(buffer_desc) => buffer_desc.hash(&mut state),
            GraphResource::Default => {}
        }

        state.finish()
    }
}

#[derive(Debug, Clone, Copy)]
pub enum LoadOp {
    Clear(Rgba),
    Load,
    DontCare,
}

#[derive(Debug, Clone, Copy)]
pub enum StoreOp {
    DontCare,
    Store,
}

pub struct RenderContext<'a> {
    pub device: device::Device,
    pub cmd: commands::SingleUseCommand,
    pub resources: &'a [GraphResource],
    pub framebuffer: Option<GraphResourceId>,
}

impl RenderContext<'_> {
    pub fn get_image(&self, id: GraphResourceId) -> Option<&Arc<Image>> {
        self.resources.get(id.0 as usize).and_then(|res| match res {
            GraphResource::ImportedImage(arc) => Some(arc),
            GraphResource::Image(image) => Some(image),
            GraphResource::Framebuffer(fb) => Some(fb),
            _ => None,
        })
    }
    pub fn get_buffer(&self, id: GraphResourceId) -> Option<&Buffer> {
        self.resources.get(id.0 as usize).and_then(|res| match res {
            GraphResource::ImportedBuffer(arc) => Some(arc.as_ref()),
            GraphResource::Buffer(buffer) => Some(buffer),
            _ => None,
        })
    }
    pub fn get_framebuffer(&self) -> Option<&Image> {
        self.framebuffer
            .and_then(|rid| self.resources.get(rid.0 as usize))
            .and_then(|res| match res {
                GraphResource::Framebuffer(arc) => Some(arc.as_ref()),
                _ => None,
            })
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Access {
    pub stage: vk::PipelineStageFlags2,
    pub mask: vk::AccessFlags2,
    pub layout: vk::ImageLayout,
}

impl core::ops::BitOr for Access {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self::Output {
        //assert_eq!(self.layout, rhs.layout);
        Self {
            stage: self.stage | rhs.stage,
            mask: self.mask | rhs.mask,
            layout: self.layout.max(rhs.layout),
        }
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct AccessMask {
    pub stage: vk::PipelineStageFlags2,
    pub mask: vk::AccessFlags2,
}
impl AccessMask {
    pub fn empty() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::NONE,
            mask: vk::AccessFlags2::empty(),
        }
    }
    pub fn is_empty(&self) -> bool {
        self.stage.is_empty() && self.mask.is_empty()
    }
}
impl core::ops::BitOr for AccessMask {
    type Output = Self;

    fn bitor(self, rhs: Self) -> Self::Output {
        Self {
            stage: self.stage | rhs.stage,
            mask: self.mask | rhs.mask,
        }
    }
}
impl core::ops::Not for AccessMask {
    type Output = Self;

    fn not(self) -> Self::Output {
        Self {
            stage: !self.stage,
            mask: !self.mask,
        }
    }
}

impl core::ops::BitAnd for AccessMask {
    type Output = Self;

    fn bitand(self, rhs: Self) -> Self::Output {
        Self {
            stage: self.stage & rhs.stage,
            mask: self.mask & rhs.mask,
        }
    }
}

impl core::ops::BitXor for AccessMask {
    type Output = Self;

    fn bitxor(self, rhs: Self) -> Self::Output {
        Self {
            stage: self.stage ^ rhs.stage,
            mask: self.mask ^ rhs.mask,
        }
    }
}

impl Access {
    pub fn into_access_mask(&self) -> AccessMask {
        AccessMask {
            stage: self.stage,
            mask: self.mask,
        }
    }
    pub fn empty() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::NONE,
            mask: vk::AccessFlags2::empty(),
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    pub fn undefined() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::NONE,
            mask: vk::AccessFlags2::empty(),
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    /// Only use this for `Ord`!
    pub fn min() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::from_raw(u64::MAX),
            mask: vk::AccessFlags2::from_raw(u64::MAX),
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    /// Only use this for `Ord`!
    pub fn max() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::from_raw(u64::MAX),
            mask: vk::AccessFlags2::from_raw(u64::MAX),
            layout: vk::ImageLayout::from_raw(i32::MAX),
        }
    }
    pub fn general() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::NONE,
            mask: vk::AccessFlags2::empty(),
            layout: vk::ImageLayout::GENERAL,
        }
    }
    pub fn transfer_read() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::TRANSFER,
            mask: vk::AccessFlags2::TRANSFER_READ,
            layout: vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
        }
    }
    pub fn transfer_write() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::TRANSFER,
            mask: vk::AccessFlags2::TRANSFER_WRITE,
            layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL,
        }
    }
    pub fn vertex_read() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT,
            mask: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    pub fn index_read() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::INDEX_INPUT,
            mask: vk::AccessFlags2::INDEX_READ,
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    pub fn indirect_read() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::DRAW_INDIRECT,
            mask: vk::AccessFlags2::INDIRECT_COMMAND_READ,
            layout: vk::ImageLayout::UNDEFINED,
        }
    }
    pub fn color_attachment_read_only() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
            mask: vk::AccessFlags2::COLOR_ATTACHMENT_READ,
            layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
        }
    }
    pub fn color_attachment_write_only() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
            mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
            layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
        }
    }
    pub fn color_attachment_read_write() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
            mask: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE
                | vk::AccessFlags2::COLOR_ATTACHMENT_READ,
            layout: vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
        }
    }
    pub fn present() -> Self {
        Self {
            stage: vk::PipelineStageFlags2::NONE,
            mask: vk::AccessFlags2::empty(),
            layout: vk::ImageLayout::PRESENT_SRC_KHR,
        }
    }
}

pub type RecordFn = dyn FnOnce(&RenderContext) -> crate::Result<()> + Send;

pub struct PassDesc {
    // this pass performs `Access` read on `GraphResourceId`.
    // some `GraphResourceId` may occur multiple times.
    pub reads: Vec<(GraphResourceId, Access)>,
    // this pass performs `Access` write on `GraphResourceId`.
    // some `GraphResourceId` may occur multiple times.
    pub writes: Vec<(GraphResourceId, Access)>,
    pub record: Option<Box<RecordFn>>,
}

impl Default for PassDesc {
    fn default() -> Self {
        Self {
            reads: Default::default(),
            writes: Default::default(),
            record: None,
        }
    }
}

impl Debug for PassDesc {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("PassDesc")
            .field("reads", &self.reads)
            .field("write", &self.writes)
            .finish_non_exhaustive()
    }
}

def_monotonic_id!(pub RenderGraphPassId);

// Non-imported resources remain `RenderGraphResourceDesc`s because they may be
// able to be aliased.
// This should be dual to liveness/register allocation in a compiler.
// Dummy-impl is just allocating every resource_desc itself. 5head-impl is trying
// to find resource_descs which are eq, but whose liveness doesn't overlap.
#[derive(Debug)]
pub struct RenderGraph {
    resources: Vec<GraphResource>,
    pass_descs: Vec<PassDesc>,
    /// the rendergraph produces these resources. Any passes on which these
    /// outputs do not depend are pruned.
    outputs: BTreeMap<GraphResourceId, Access>,
    pub(crate) framebuffer: Option<GraphResourceId>,
}

impl RenderGraph {
    pub fn new() -> Self {
        let mut pass_descs = Vec::new();
        pass_descs.push(PassDesc::default());

        Self {
            resources: Vec::new(),
            pass_descs,
            outputs: BTreeMap::new(),
            framebuffer: None,
        }
    }

    pub fn get_framebuffer(&self) -> Option<GraphResourceId> {
        self.framebuffer
    }

    fn get_next_resource_id(&mut self) -> GraphResourceId {
        GraphResourceId(self.resources.len() as u32)
    }

    fn input_pass_mut(&mut self) -> &mut PassDesc {
        &mut self.pass_descs[0]
    }

    pub fn add_resource(&mut self, desc: GraphResourceDesc) -> GraphResourceId {
        let id = self.get_next_resource_id();
        self.resources.push(desc.into());
        id
    }

    pub fn mark_as_output(&mut self, id: GraphResourceId, access: Access) {
        _ = self.outputs.try_insert(id, access);
    }

    pub fn import_resource(&mut self, res: GraphResource, access: Access) -> GraphResourceId {
        if let Some(i) = self
            .resources
            .iter()
            .position(|other| res.simple_hash() == other.simple_hash())
        {
            GraphResourceId(i as u32)
        } else {
            let id = self.get_next_resource_id();
            self.resources.push(res);
            self.input_pass_mut().writes.push((id, access));
            id
        }
    }

    pub fn import_image(&mut self, image: Arc<Image>, access: Access) -> GraphResourceId {
        let res = GraphResource::ImportedImage(image);
        self.import_resource(res, access)
    }

    pub fn import_buffer(&mut self, buffer: Arc<Buffer>, access: Access) -> GraphResourceId {
        let res = GraphResource::ImportedBuffer(buffer);
        self.import_resource(res, access)
    }

    pub fn import_framebuffer(&mut self, frame: Arc<Image>) -> GraphResourceId {
        let rid = self.import_resource(
            GraphResource::Framebuffer(frame.clone()),
            Access::undefined(),
        );
        self.mark_as_output(rid, Access::present());
        self.framebuffer = Some(rid);
        rid
    }

    pub fn add_pass(&mut self, pass: PassDesc) {
        self.pass_descs.push(pass);
    }

    // https://blog.traverseresearch.nl/render-graph-101-f42646255636
    // https://github.com/EmbarkStudios/kajiya/blob/main/crates/lib/kajiya-rg/src/graph.rs
    // https://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/
    pub fn resolve(
        &mut self,
        device: device::Device,
    ) -> crate::Result<WithLifetime<'_, commands::CommandList<commands::SingleUseCommand>>> {
        let output_reads = self
            .outputs
            .iter()
            .map(|(rid, access)| (*rid, *access))
            .collect::<Vec<_>>();

        self.add_pass(PassDesc {
            reads: output_reads,
            writes: vec![],
            ..Default::default()
        });

        let topo = util::timed("Resolving Render Graph", || {
            let mut refmap =
                graph_resolver::NodeRefsMap::new(self.resources.len(), self.pass_descs.len());

            refmap.allocate_ref_ranges(&self.pass_descs);
            refmap.ref_passes(&self.pass_descs);
            let dag = refmap.build_dag();
            let topo = refmap.toposort_dag(dag);
            topo
        });

        // create internal resources:
        util::timed("Create internal RenderGraph resources:", || {
            for (i, res) in self.resources.iter_mut().enumerate() {
                match res {
                    GraphResource::ImageDesc(image_desc) => {
                        tracing::trace!("creating resource #{i:?} with {image_desc:?}");
                        *res = GraphResource::Image(Arc::new(Image::new(
                            device.clone(),
                            image_desc.clone(),
                        )?));
                    }
                    GraphResource::BufferDesc(buffer_desc) => {
                        tracing::trace!("creating resource #{i:?} with {buffer_desc:?}");
                        *res = GraphResource::Buffer(Buffer::new(
                            device.clone(),
                            buffer_desc.clone(),
                        )?);
                    }
                    _ => {}
                }
            }
            crate::Result::Ok(())
        })?;

        let pool =
            commands::SingleUseCommandPool::new(device.clone(), device.main_queue().clone())?;

        let resources = &self.resources;
        let cmds = topo
            .iter()
            .rev()
            .map(|(passes, accesses)| {
                let passes = passes
                    .into_iter()
                    .map(|i| core::mem::take(&mut self.pass_descs[i.index()]))
                    .collect::<Vec<_>>();
                (passes, accesses)
            })
            .map({
                |(passes, accesses)| {
                    let cmd = pool.alloc()?;
                    // transitions
                    for (&id, &(from, to)) in accesses.iter() {
                        let buffer = unsafe { cmd.buffer() };
                        Self::transition_resource(
                            &resources[id.0 as usize],
                            device.dev(),
                            &buffer,
                            from,
                            to,
                        );
                    }

                    let ctx = RenderContext {
                        device: device.clone(),
                        cmd,
                        resources,
                        framebuffer: self.framebuffer,
                    };

                    for pass in passes {
                        if let Some(record) = pass.record {
                            (record)(&ctx)?;
                        }
                    }

                    ctx.cmd.end()?;
                    crate::Result::Ok(ctx.cmd)
                }
            })
            .collect::<crate::Result<Vec<_>>>()?;

        let cmd_list = commands::CommandList(cmds);

        Ok(WithLifetime::new(cmd_list))
    }

    pub fn get_outputs(&mut self) -> BTreeMap<GraphResourceId, GraphResource> {
        let outputs = self
            .outputs
            .iter()
            .map(|(id, _)| (*id, core::mem::take(&mut self.resources[id.0 as usize])))
            .collect::<BTreeMap<_, _>>();

        outputs
    }

    pub fn transition_resource(
        res: &GraphResource,
        dev: &ash::Device,
        cmd: &vk::CommandBuffer,
        from: Access,
        to: Access,
    ) {
        let barrier: Barrier = match res {
            GraphResource::Framebuffer(arc) => {
                image_barrier(arc.raw(), arc.format(), from, to, None).into()
            }
            GraphResource::ImportedImage(arc) => {
                image_barrier(arc.raw(), arc.format(), from, to, None).into()
            }
            GraphResource::ImportedBuffer(arc) => {
                buffer_barrier(arc.raw(), 0, arc.len(), from, to, None).into()
            }
            GraphResource::Image(image) => {
                image_barrier(image.raw(), image.format(), from, to, None).into()
            }
            GraphResource::Buffer(buffer) => {
                buffer_barrier(buffer.raw(), 0, buffer.len(), from, to, None).into()
            }
            _ => {
                unreachable!()
            }
        };

        unsafe {
            dev.cmd_pipeline_barrier2(*cmd, &((&barrier).into()));
        }
    }

    fn transition_resource_to(
        accesses: &mut BTreeMap<GraphResourceId, Access>,
        resources: &BTreeMap<GraphResourceId, GraphResource>,
        dev: &ash::Device,
        cmd: &vk::CommandBuffer,
        id: GraphResourceId,
        to: Access,
    ) {
        let old_access = accesses.get(&id);
        let res = resources.get(&id);

        if let (Some(&old_access), Some(res)) = (old_access, res) {
            Self::transition_resource(res, dev, cmd, old_access, to);
            accesses.insert(id, to);
        }
    }
}

mod graph_resolver {
    use std::collections::{BTreeMap, BTreeSet};
    use std::fmt::Display;

    use ash::vk;
    use petgraph::visit::EdgeRef;

    use crate::render_graph::*;

    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
    #[repr(transparent)]
    pub struct PassNode(u16);

    impl PassNode {
        pub fn index(&self) -> usize {
            self.0 as usize
        }
        pub fn as_u32(&self) -> u32 {
            self.0 as u32
        }
        pub fn pass(i: usize) -> Self {
            Self(i as u16)
        }
    }

    #[derive(Debug, Clone, Copy)]
    pub enum Barrier {
        Logical,
        Execution {
            src: vk::PipelineStageFlags2,
            dst: vk::PipelineStageFlags2,
        },
        LayoutTransition {
            src: (vk::PipelineStageFlags2, vk::ImageLayout),
            dst: (vk::PipelineStageFlags2, vk::ImageLayout),
        },

        MakeAvailable {
            src: (vk::PipelineStageFlags2, vk::AccessFlags2),
            dst: vk::PipelineStageFlags2,
        },
        MakeVisible {
            src: vk::PipelineStageFlags2,
            dst: (vk::PipelineStageFlags2, vk::AccessFlags2),
        },
        MemoryBarrier {
            src: (vk::PipelineStageFlags2, vk::AccessFlags2),
            dst: (vk::PipelineStageFlags2, vk::AccessFlags2),
        },
    }

    impl Display for Barrier {
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
            match self {
                Barrier::Logical => write!(f, "Logical"),
                Barrier::Execution { .. } => write!(f, "Execution"),
                Barrier::LayoutTransition { .. } => write!(f, "Layout"),
                Barrier::MakeAvailable { .. } => write!(f, "MakeAvailable"),
                Barrier::MakeVisible { .. } => write!(f, "MakeVisible"),
                Barrier::MemoryBarrier { .. } => write!(f, "MemoryBarrier"),
            }
        }
    }

    pub struct NodeRefsMap {
        num_resources: usize,
        num_passes: usize,
        // bitmap of passes referencing rid
        references: Vec<u64>,

        // range into ref_accesses*: start, end, index
        ref_ranges: Vec<(u32, u32, u32)>,
        ref_accesses: Vec<(Option<Access>, Option<Access>)>,
        ref_access_passid: Vec<PassNode>,
    }

    impl NodeRefsMap {
        pub fn new(num_resources: usize, num_passes: usize) -> Self {
            Self {
                num_resources,
                num_passes,
                references: vec![0; ((num_passes) * num_resources).div_ceil(64) as usize],
                ref_ranges: Vec::new(),
                ref_accesses: Vec::new(),
                ref_access_passid: Vec::new(),
            }
        }

        pub fn allocate_ref_ranges(&mut self, passes: &[PassDesc]) {
            let mut rid_passcount = vec![0; self.num_resources];

            for pass in passes.iter() {
                for rid in pass
                    .reads
                    .iter()
                    .chain(pass.writes.iter())
                    .map(|id| id.0)
                    .collect::<BTreeSet<_>>()
                {
                    rid_passcount[rid.0 as usize] += 1;
                }
            }

            let mut total = 0;
            for num_passes in rid_passcount {
                self.ref_ranges.push((total, total + num_passes, 0));
                self.ref_accesses
                    .extend((0..num_passes).map(|_| (None, None)));
                self.ref_access_passid
                    .extend((0..num_passes).map(|_| PassNode(0)));
                total += num_passes;
            }
        }

        fn get_accesses_for_rid_pass_mut(
            &mut self,
            rid: GraphResourceId,
            pass: PassNode,
        ) -> &mut (Option<Access>, Option<Access>) {
            let (start, _, i) = self.ref_ranges[rid.0 as usize];

            let idx = self.ref_access_passid[start as usize..(start + i) as usize]
                .binary_search(&pass)
                .unwrap_or_else(|_| {
                    // increase counter
                    self.ref_ranges[rid.0 as usize].2 += 1;
                    i as usize
                })
                + start as usize;

            self.ref_access_passid[idx] = pass;

            &mut self.ref_accesses[idx]
        }

        fn get_reads_for_rid_pass_mut(
            &mut self,
            rid: GraphResourceId,
            pass: PassNode,
        ) -> &mut Option<Access> {
            &mut self.get_accesses_for_rid_pass_mut(rid, pass).0
        }
        fn get_writes_for_rid_pass_mut(
            &mut self,
            rid: GraphResourceId,
            pass: PassNode,
        ) -> &mut Option<Access> {
            &mut self.get_accesses_for_rid_pass_mut(rid, pass).1
        }

        fn get_accesses_for_rid_pass(
            &self,
            rid: GraphResourceId,
            pass: PassNode,
        ) -> (Option<Access>, Option<Access>) {
            let (start, _, i) = self.ref_ranges[rid.0 as usize];

            let Some(idx) = self.ref_access_passid[start as usize..(start + i) as usize]
                .binary_search(&pass)
                .ok()
            else {
                return (None, None);
            };
            let idx = idx + start as usize;

            self.ref_accesses[idx]
        }

        fn get_reads_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option<Access> {
            self.get_accesses_for_rid_pass(rid, pass).0
        }
        fn get_writes_for_rid_pass(&self, rid: GraphResourceId, pass: PassNode) -> Option<Access> {
            self.get_accesses_for_rid_pass(rid, pass).1
        }

        fn reference_rid_pass(&mut self, rid: GraphResourceId, pass: PassNode) {
            let bit_idx = rid.0 as usize * (self.num_passes) + pass.index();
            let word_idx = bit_idx / 64;
            let word_offset = bit_idx % 64;
            tracing::trace!(
                bit_idx,
                word_idx,
                word_offset,
                "pass: {pass:?} references rid: {rid:?} "
            );
            self.references[word_idx] |= 1 << word_offset;
        }

        pub fn ref_passes(&mut self, passes: &[PassDesc]) {
            for (i, pass) in passes.iter().enumerate() {
                let pass_id = PassNode::pass(i);

                for &(rid, access) in &pass.reads {
                    let read = self
                        .get_reads_for_rid_pass_mut(rid, pass_id)
                        .get_or_insert(Access::empty());
                    *read = *read | access;

                    // TODO: check for first pass as well
                    self.reference_rid_pass(rid, PassNode::pass(i));
                }

                for &(rid, access) in &pass.writes {
                    let write = self
                        .get_writes_for_rid_pass_mut(rid, pass_id)
                        .get_or_insert(Access::empty());
                    *write = *write | access;

                    // TODO: check for first pass as well
                    self.reference_rid_pass(rid, PassNode::pass(i));
                }
            }
        }

        pub fn build_dag(
            &self,
        ) -> petgraph::stable_graph::StableDiGraph<PassNode, (GraphResourceId, Barrier)> {
            struct Edge {
                from: PassNode,
                to: PassNode,
                rid: GraphResourceId,
                barrier: Barrier,
            }

            #[derive(Debug, Clone, Copy)]
            enum Ref {
                Write(PassNode, Access),
                Read(PassNode, Access),
            }

            impl Ref {
                fn node(&self) -> PassNode {
                    match self {
                        Ref::Write(node, _) | Ref::Read(node, _) => *node,
                    }
                }
                fn access(&self) -> Access {
                    match self {
                        Ref::Write(_, access) | Ref::Read(_, access) => *access,
                    }
                }
            }

            let mut edges = Vec::<Edge>::new();

            let bits =
                crate::util::BitIter::new(&self.references, self.num_resources * (self.num_passes))
                    .chunks(self.num_passes);

            tracing::trace!("building edges.");
            for (i, bits) in bits.enumerate() {
                let rid = GraphResourceId(i as u32);

                let mut to_make_available = AccessMask::empty();
                let mut made_available = AccessMask::empty();

                let mut last_ref = Option::<Ref>::None;
                let mut last_write = Option::<Ref>::None;

                for pass in bits {
                    let pass = PassNode::pass(pass);

                    if let Some(last_ref) = last_ref.as_ref() {
                        edges.push(Edge {
                            from: last_ref.node(),
                            to: pass,
                            rid,
                            barrier: Barrier::Logical,
                        });
                    }

                    let read = self.get_reads_for_rid_pass(rid, pass);
                    if let Some(read) = read {
                        tracing::trace!("read: {:?}", read);

                        let make_visible = read.into_access_mask() & !made_available;
                        if let Some(last_write) = last_write.as_ref() {
                            let from = last_write.node();
                            let to = pass;
                            let from_write = last_write.access();

                            // if last_write is some, make visible the writes
                            if !make_visible.is_empty() && !from_write.stage.is_empty() {
                                made_available = made_available | make_visible;

                                edges.push(Edge {
                                    from,
                                    to,
                                    rid,
                                    barrier: Barrier::MakeVisible {
                                        src: from_write.stage,
                                        dst: (make_visible.stage, make_visible.mask),
                                    },
                                });
                            }

                            // make available any changes
                            if !to_make_available.is_empty() {
                                edges.push(Edge {
                                    from,
                                    to,
                                    rid,
                                    barrier: Barrier::MakeAvailable {
                                        src: (to_make_available.stage, to_make_available.mask),
                                        dst: read.stage,
                                    },
                                });
                                to_make_available = AccessMask::empty();
                            }

                            if make_visible.is_empty() && !to_make_available.is_empty() {
                                // still require a-after-b
                                edges.push(Edge {
                                    from,
                                    to,
                                    rid,
                                    barrier: Barrier::Execution {
                                        src: from_write.stage,
                                        dst: read.stage,
                                    },
                                });
                            }
                        }

                        // layout transition from previous pass, either read or write
                        if let Some(last_ref) = last_ref.as_ref() {
                            if last_ref.access().layout != read.layout {
                                let from = last_ref.node();
                                let to = pass;
                                edges.push(Edge {
                                    from,
                                    to,
                                    rid,
                                    barrier: Barrier::LayoutTransition {
                                        src: (last_ref.access().stage, last_ref.access().layout),
                                        dst: (read.stage, read.layout),
                                    },
                                });
                            }
                        }
                    }

                    let write = self.get_writes_for_rid_pass(rid, pass);
                    if let Some(write) = write {
                        tracing::trace!("write: {:?}", write);

                        if let Some(last) = last_ref.as_ref()
                            && last.access().layout != write.layout
                        {
                            let before = last.access();
                            edges.push(Edge {
                                from: last.node(),
                                to: pass,
                                rid,
                                barrier: Barrier::LayoutTransition {
                                    src: (before.stage, before.layout),
                                    dst: (write.stage, write.layout),
                                },
                            });
                        }

                        match last_ref.as_ref() {
                            Some(Ref::Read(node, before)) => {
                                // execution barrier to ward against write-after-read

                                edges.push(Edge {
                                    from: *node,
                                    to: pass,
                                    rid,
                                    barrier: Barrier::Execution {
                                        src: before.stage,
                                        dst: write.stage,
                                    },
                                });
                            }
                            _ => {}
                        }
                    }

                    if let Some(read) = read {
                        last_ref = Some(Ref::Read(pass, read));
                    }
                    if let Some(write) = write {
                        last_write = Some(Ref::Write(pass, write));
                        last_ref = last_write;
                    }
                }
            }

            let mut dag =
                petgraph::stable_graph::StableDiGraph::<PassNode, (GraphResourceId, Barrier)>::new(
                );

            for i in 0..self.num_passes {
                dag.add_node(PassNode::pass(i));
            }

            // insert edges
            for edge in edges {
                let Edge {
                    from,
                    to,
                    rid,
                    barrier,
                } = edge;
                dag.add_edge(from.as_u32().into(), to.as_u32().into(), (rid, barrier));
            }

            // prune dead ends
            let mut sinks = dag
                .externals(petgraph::Direction::Outgoing)
                .filter(|idx| dag.node_weight(*idx) != Some(&PassNode::pass(self.num_passes - 1)))
                .collect::<Vec<_>>();

            while let Some(sink) = sinks.pop() {
                let mut neighbors = dag
                    .neighbors_directed(sink, petgraph::Direction::Incoming)
                    .detach();
                while let Some((edge, node)) = neighbors.next(&dag) {
                    dag.remove_edge(edge);

                    if dag
                        .neighbors_directed(node, petgraph::Direction::Outgoing)
                        .count()
                        == 0
                    {
                        sinks.push(node);
                    }
                }

                dag.remove_node(sink);
            }

            #[cfg(any(debug_assertions, test))]
            std::fs::write(
                "render_graph2.dot",
                &format!(
                    "{:?}",
                    petgraph::dot::Dot::with_attr_getters(
                        &dag,
                        &[],
                        &|_graph, edgeref| {
                            format!(
                                "label = \"{},{:#?}\"",
                                edgeref.weight().0,
                                edgeref.weight().1,
                            )
                        },
                        &|_graph, noderef| {
                            format!(
                                "label = \"Pass({:?})\"",
                                petgraph::visit::NodeRef::weight(&noderef)
                            )
                        }
                    )
                ),
            )
            .expect("writing render_graph repr");

            dag
        }

        pub fn toposort_dag(
            &self,
            mut dag: petgraph::stable_graph::StableDiGraph<PassNode, (GraphResourceId, Barrier)>,
        ) -> Vec<(Vec<PassNode>, BTreeMap<GraphResourceId, (Access, Access)>)> {
            let mut topomap = Vec::new();

            let mut sinks = dag
                .externals(petgraph::Direction::Outgoing)
                .collect::<BTreeSet<_>>();
            let mut next_sinks = BTreeSet::new();

            loop {
                tracing::trace!("sinks: {sinks:?}, next_sinks: {next_sinks:?}");

                if sinks.is_empty() {
                    break;
                }

                let mut passes = Vec::with_capacity(self.num_passes);
                let mut barriers = BTreeMap::new();
                for &sink in sinks.iter() {
                    for &(rid, barrier) in dag
                        .edges_directed(sink, petgraph::Direction::Incoming)
                        .map(|edge| edge.weight())
                    {
                        let before_and_after = match barrier {
                            Barrier::Logical => None,
                            Barrier::Execution { src, dst } => Some((
                                Access {
                                    stage: src,
                                    ..Access::empty()
                                },
                                Access {
                                    stage: dst,
                                    ..Access::empty()
                                },
                            )),
                            Barrier::LayoutTransition {
                                src: (src, from),
                                dst: (dst, to),
                            } => Some((
                                Access {
                                    stage: src,
                                    layout: from,
                                    ..Access::empty()
                                },
                                Access {
                                    stage: dst,
                                    layout: to,
                                    ..Access::empty()
                                },
                            )),
                            Barrier::MakeAvailable {
                                src: (stage, mask),
                                dst,
                            } => Some((
                                Access {
                                    stage,
                                    mask,
                                    ..Access::empty()
                                },
                                Access {
                                    stage: dst,
                                    ..Access::empty()
                                },
                            )),
                            Barrier::MakeVisible {
                                src,
                                dst: (stage, mask),
                            } => Some((
                                Access {
                                    stage: src,
                                    ..Access::empty()
                                },
                                Access {
                                    stage,
                                    mask,
                                    ..Access::empty()
                                },
                            )),
                            Barrier::MemoryBarrier {
                                src: (src_stage, src_mask),
                                dst: (dst_stage, dst_mask),
                            } => Some((
                                Access {
                                    stage: src_stage,
                                    mask: src_mask,
                                    ..Access::empty()
                                },
                                Access {
                                    stage: dst_stage,
                                    mask: dst_mask,
                                    ..Access::empty()
                                },
                            )),
                        };

                        if let Some((before, after)) = before_and_after {
                            // initial access is transitioned at the beginning
                            // this affects imported resources only.
                            barriers
                                .entry(rid)
                                .and_modify(|(from, to)| {
                                    *from = *from | before;
                                    *to = *to | after;
                                })
                                .or_insert((before, after));
                        }
                    }

                    dag.edges_directed(sink, petgraph::Direction::Incoming)
                        .for_each(|edge| {
                            let node = edge.source();
                            if dag
                                .neighbors_directed(node, petgraph::Direction::Outgoing)
                                .all(|node| node == sink)
                            {
                                next_sinks.insert(node);
                            }
                        });

                    passes.push(*dag.node_weight(sink).unwrap());
                    dag.remove_node(sink);
                }

                topomap.push((passes, barriers));
                sinks.clear();
                core::mem::swap(&mut sinks, &mut next_sinks);
            }

            topomap
        }
    }
}

pub enum Barrier {
    Image(vk::ImageMemoryBarrier2<'static>),
    Buffer(vk::BufferMemoryBarrier2<'static>),
}

impl<'a> From<&'a Barrier> for vk::DependencyInfo<'a> {
    fn from(value: &'a Barrier) -> Self {
        let info = vk::DependencyInfo::default();
        let info = match value {
            Barrier::Image(barrier) => info.image_memory_barriers(core::slice::from_ref(barrier)),
            Barrier::Buffer(barrier) => info.buffer_memory_barriers(core::slice::from_ref(barrier)),
        };

        info
    }
}

impl From<vk::ImageMemoryBarrier2<'static>> for Barrier {
    fn from(value: vk::ImageMemoryBarrier2<'static>) -> Self {
        Self::Image(value)
    }
}
impl From<vk::BufferMemoryBarrier2<'static>> for Barrier {
    fn from(value: vk::BufferMemoryBarrier2<'static>) -> Self {
        Self::Buffer(value)
    }
}

pub fn buffer_barrier(
    buffer: vk::Buffer,
    offset: u64,
    size: u64,
    before: Access,
    after: Access,
    queue_families: Option<(u32, u32)>,
) -> vk::BufferMemoryBarrier2<'static> {
    vk::BufferMemoryBarrier2::default()
        .buffer(buffer)
        .offset(offset)
        .size(size)
        .src_access_mask(before.mask)
        .src_stage_mask(before.stage)
        .dst_access_mask(after.mask)
        .dst_stage_mask(after.stage)
        .src_queue_family_index(
            queue_families
                .map(|(src, _)| src)
                .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
        )
        .dst_queue_family_index(
            queue_families
                .map(|(_, dst)| dst)
                .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
        )
}

pub fn image_barrier(
    image: vk::Image,
    format: vk::Format,
    before_access: Access,
    after_access: Access,
    queue_families: Option<(u32, u32)>,
) -> vk::ImageMemoryBarrier2<'static> {
    vk::ImageMemoryBarrier2::default()
        .src_access_mask(before_access.mask)
        .src_stage_mask(before_access.stage)
        .dst_access_mask(after_access.mask)
        .dst_stage_mask(after_access.stage)
        .image(image)
        .old_layout(before_access.layout)
        .new_layout(after_access.layout)
        .subresource_range(vk::ImageSubresourceRange {
            aspect_mask: util::image_aspect_from_format(format),
            ..images::SUBRESOURCERANGE_ALL
        })
        .src_queue_family_index(
            queue_families
                .map(|(src, _)| src)
                .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
        )
        .dst_queue_family_index(
            queue_families
                .map(|(_, dst)| dst)
                .unwrap_or(vk::QUEUE_FAMILY_IGNORED),
        )
}

pub fn clear_pass(rg: &mut RenderGraph, color: Rgba, target: GraphResourceId) {
    let reads = [(target, Access::transfer_write())].to_vec();
    let writes = [(target, Access::transfer_write())].to_vec();

    let record: Box<RecordFn> = Box::new({
        move |ctx| {
            let target = ctx.get_image(target).unwrap();
            let cmd = &ctx.cmd;

            cmd.clear_color_image(
                target.raw(),
                target.format(),
                vk::ImageLayout::TRANSFER_DST_OPTIMAL,
                color,
                &[images::SUBRESOURCERANGE_COLOR_ALL],
            );
            Ok(())
        }
    });
    rg.add_pass(PassDesc {
        reads,
        writes,
        record: Some(record),
    });
}

#[deprecated = "mark target as output with Access::present()."]
pub fn present_pass(rg: &mut RenderGraph, target: GraphResourceId) {
    let reads = vec![(target, Access::present())];
    let writes = vec![(target, Access::present())];
    rg.add_pass(PassDesc {
        reads,
        writes,
        record: None,
    });
}
