// Copyright 2023 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use super::{BindType, ShaderInfo};
use crate::types::msl::BindingIndex;
use naga::back::msl as naga_msl;

pub fn translate(shader: &ShaderInfo) -> Result<String, naga_msl::Error> {
    let mut map = naga_msl::EntryPointResourceMap::default();
    let mut idx_iter = BindingIndexIterator::default();
    let mut binding_map = naga_msl::BindingMap::default();
    for resource in &shader.bindings {
        let binding = naga::ResourceBinding {
            group: resource.location.0,
            binding: resource.location.1,
        };
        let mut target = naga_msl::BindTarget::default();
        match idx_iter.next(resource.ty) {
            BindingIndex::Buffer(idx) => {
                target.buffer = Some(idx);
            }
            BindingIndex::Texture(idx) => {
                target.texture = Some(idx);
            }
        }
        target.mutable = resource.ty.is_mutable();
        binding_map.insert(binding, target);
    }
    map.insert(
        "main".to_string(),
        naga_msl::EntryPointResources {
            resources: binding_map,
            immediates_buffer: None,
            sizes_buffer: Some(30),
        },
    );
    let options = naga_msl::Options {
        lang_version: (2, 0),
        per_entry_point_map: map,
        inline_samplers: vec![],
        spirv_cross_compatibility: false,
        fake_missing_bindings: false,
        bounds_check_policies: naga::proc::BoundsCheckPolicies::default(),
        zero_initialize_workgroup_memory: true,
        force_loop_bounding: true,
    };
    let (source, _) = naga_msl::write_string(
        &shader.module,
        &shader.module_info,
        &options,
        &naga_msl::PipelineOptions::default(),
    )?;
    Ok(source)
}

#[derive(Default)]
pub struct BindingIndexIterator {
    buffer_idx: u8,
    tex_idx: u8,
}

impl BindingIndexIterator {
    pub fn next(&mut self, ty: BindType) -> BindingIndex {
        match ty {
            BindType::Buffer | BindType::BufReadOnly | BindType::Uniform => {
                let idx = self.buffer_idx;
                self.buffer_idx += 1;
                assert!(self.buffer_idx > 0);
                BindingIndex::Buffer(idx)
            }
            BindType::Image | BindType::ImageRead => {
                let idx = self.tex_idx;
                self.tex_idx += 1;
                assert!(self.tex_idx > 0);
                BindingIndex::Texture(idx)
            }
        }
    }
}
