use objc2::{
    extern_class, extern_conformance, extern_methods, msg_send,
    rc::{Allocated, Retained},
    runtime::ProtocolObject,
};
use objc2_foundation::{CopyingHelper, NSArray, NSCopying, NSDictionary, NSObject, NSObjectProtocol, NSString};

use crate::*;

extern_class!(
    /// Groups together properties to drive a static linking process.
    ///
    /// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4staticlinkingdescriptor?language=objc)
    #[unsafe(super(NSObject))]
    #[derive(Debug, PartialEq, Eq, Hash)]
    pub struct MTL4StaticLinkingDescriptor;
);

extern_conformance!(
    unsafe impl NSCopying for MTL4StaticLinkingDescriptor {}
);

unsafe impl CopyingHelper for MTL4StaticLinkingDescriptor {
    type Result = Self;
}

extern_conformance!(
    unsafe impl NSObjectProtocol for MTL4StaticLinkingDescriptor {}
);

impl MTL4StaticLinkingDescriptor {
    extern_methods!();

    /// Provides an array of functions to link at the Metal IR level.
    pub fn function_descriptors(&self) -> Option<Box<[Retained<MTL4FunctionDescriptor>]>> {
        let function_descriptors: Option<Retained<NSArray<MTL4FunctionDescriptor>>> =
            unsafe { msg_send![self, functionDescriptors] };
        function_descriptors.map(|descriptors| descriptors.to_vec().into_boxed_slice())
    }

    pub fn set_function_descriptors(
        &self,
        function_descriptors: Option<&[&MTL4FunctionDescriptor]>,
    ) {
        let function_descriptors = function_descriptors.map(NSArray::from_slice);
        unsafe {
            let _: () = msg_send![self, setFunctionDescriptors: function_descriptors.as_deref()];
        }
    }

    /// Provides an array of private functions to link at the Metal IR level.
    ///
    /// You specify private functions to link separately from ``functionDescriptors`` because pipelines don't export private functions as ``MTLFunctionHandle`` instances.
    /// - Note: You can link private functions even when your ``MTLDevice`` doesn't support function pointers.
    pub fn private_function_descriptors(&self) -> Option<Box<[Retained<MTL4FunctionDescriptor>]>> {
        let private_function_descriptors: Option<Retained<NSArray<MTL4FunctionDescriptor>>> =
            unsafe { msg_send![self, privateFunctionDescriptors] };
        private_function_descriptors.map(|descriptors| descriptors.to_vec().into_boxed_slice())
    }

    pub fn set_private_function_descriptors(
        &self,
        private_function_descriptors: Option<&[&MTL4FunctionDescriptor]>,
    ) {
        let private_function_descriptors = private_function_descriptors.map(NSArray::from_slice);
        unsafe {
            let _: () = msg_send![
                self,
                setPrivateFunctionDescriptors: private_function_descriptors.as_deref()
            ];
        }
    }

    /// Assigns groups of functions to match call-site attributes in shader code.
    ///
    /// Function groups help the compiler reduce the number of candidate functions it needs to evaluate for shader function calls, potentially increasing runtime performance.
    pub fn groups(&self) -> Option<Box<[(String, Box<[Retained<MTL4FunctionDescriptor>]>)]>> {
        let groups: Option<Retained<NSDictionary<NSString, NSArray<MTL4FunctionDescriptor>>>> =
            unsafe { msg_send![self, groups] };
        groups.map(|groups| {
            let (group_names, group_functions) = groups.to_vecs();
            group_names
                .into_iter()
                .zip(group_functions)
                .map(|(group_name, functions)| (group_name.to_string(), functions.to_vec().into_boxed_slice()))
                .collect::<Vec<_>>()
                .into_boxed_slice()
        })
    }

    pub fn set_groups(
        &self,
        groups: Option<&[(&str, &[&MTL4FunctionDescriptor])]>,
    ) {
        let groups = groups.map(|groups| {
            let group_names: Vec<Retained<NSString>> =
                groups.iter().map(|(group_name, _)| NSString::from_str(group_name)).collect();
            let group_name_refs: Vec<&NSString> = group_names.iter().map(|name| &**name).collect();
            let group_functions: Vec<Retained<NSArray<MTL4FunctionDescriptor>>> =
                groups.iter().map(|(_, functions)| NSArray::from_slice(functions)).collect();
            let group_function_refs: Vec<&NSArray<MTL4FunctionDescriptor>> =
                group_functions.iter().map(|functions| &**functions).collect();
            NSDictionary::from_slices(&group_name_refs, &group_function_refs)
        });
        unsafe {
            let _: () = msg_send![self, setGroups: groups.as_deref()];
        }
    }
}

/// Methods declared on superclass `NSObject`.
impl MTL4StaticLinkingDescriptor {
    extern_methods!(
        #[unsafe(method(init))]
        #[unsafe(method_family = init)]
        pub fn init(this: Allocated<Self>) -> Retained<Self>;

        #[unsafe(method(new))]
        #[unsafe(method_family = new)]
        pub fn new() -> Retained<Self>;
    );
}

extern_class!(
    /// Groups together properties to drive the dynamic linking process of a pipeline stage.
    ///
    /// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4pipelinestagedynamiclinkingdescriptor?language=objc)
    #[unsafe(super(NSObject))]
    #[derive(Debug, PartialEq, Eq, Hash)]
    pub struct MTL4PipelineStageDynamicLinkingDescriptor;
);

extern_conformance!(
    unsafe impl NSCopying for MTL4PipelineStageDynamicLinkingDescriptor {}
);

unsafe impl CopyingHelper for MTL4PipelineStageDynamicLinkingDescriptor {
    type Result = Self;
}

extern_conformance!(
    unsafe impl NSObjectProtocol for MTL4PipelineStageDynamicLinkingDescriptor {}
);

impl MTL4PipelineStageDynamicLinkingDescriptor {
    extern_methods!(
        /// Limits the maximum depth of the call stack for indirect function calls in the pipeline stage function.
        #[unsafe(method(maxCallStackDepth))]
        #[unsafe(method_family = none)]
        pub fn max_call_stack_depth(&self) -> usize;

        /// Setter for [`maxCallStackDepth`][Self::maxCallStackDepth].
        #[unsafe(method(setMaxCallStackDepth:))]
        #[unsafe(method_family = none)]
        pub fn set_max_call_stack_depth(
            &self,
            max_call_stack_depth: usize,
        );
    );

    /// Provides the array of binary functions to link.
    ///
    /// Binary functions are shader functions that you compile from Metal IR to machine code ahead of time
    /// using instances of ``MTL4Compiler``.
    pub fn binary_linked_functions(&self) -> Option<Box<[Retained<ProtocolObject<dyn MTL4BinaryFunction>>]>> {
        let binary_linked_functions: Option<Retained<NSArray<ProtocolObject<dyn MTL4BinaryFunction>>>> =
            unsafe { msg_send![self, binaryLinkedFunctions] };
        binary_linked_functions.map(|functions| functions.to_vec().into_boxed_slice())
    }

    pub fn set_binary_linked_functions(
        &self,
        binary_linked_functions: Option<&[&ProtocolObject<dyn MTL4BinaryFunction>]>,
    ) {
        let binary_linked_functions = binary_linked_functions.map(NSArray::from_slice);
        unsafe {
            let _: () = msg_send![
                self,
                setBinaryLinkedFunctions: binary_linked_functions.as_deref()
            ];
        }
    }

    /// Provides an array of dynamic libraries the compiler loads when it builds the pipeline.
    pub fn preloaded_libraries(&self) -> Box<[Retained<ProtocolObject<dyn MTLDynamicLibrary>>]> {
        let preloaded_libraries: Retained<NSArray<ProtocolObject<dyn MTLDynamicLibrary>>> =
            unsafe { msg_send![self, preloadedLibraries] };
        preloaded_libraries.to_vec().into_boxed_slice()
    }

    pub fn set_preloaded_libraries(
        &self,
        preloaded_libraries: &[&ProtocolObject<dyn MTLDynamicLibrary>],
    ) {
        let preloaded_libraries = NSArray::from_slice(preloaded_libraries);
        unsafe {
            let _: () = msg_send![self, setPreloadedLibraries: &*preloaded_libraries];
        }
    }
}

/// Methods declared on superclass `NSObject`.
impl MTL4PipelineStageDynamicLinkingDescriptor {
    extern_methods!(
        #[unsafe(method(init))]
        #[unsafe(method_family = init)]
        pub fn init(this: Allocated<Self>) -> Retained<Self>;

        #[unsafe(method(new))]
        #[unsafe(method_family = new)]
        pub fn new() -> Retained<Self>;
    );
}

extern_class!(
    /// Groups together properties that provide linking properties for render pipelines.
    ///
    /// See also [Apple's documentation](https://developer.apple.com/documentation/metal/mtl4renderpipelinedynamiclinkingdescriptor?language=objc)
    #[unsafe(super(NSObject))]
    #[derive(Debug, PartialEq, Eq, Hash)]
    pub struct MTL4RenderPipelineDynamicLinkingDescriptor;
);

extern_conformance!(
    unsafe impl NSCopying for MTL4RenderPipelineDynamicLinkingDescriptor {}
);

unsafe impl CopyingHelper for MTL4RenderPipelineDynamicLinkingDescriptor {
    type Result = Self;
}

extern_conformance!(
    unsafe impl NSObjectProtocol for MTL4RenderPipelineDynamicLinkingDescriptor {}
);

impl MTL4RenderPipelineDynamicLinkingDescriptor {
    extern_methods!(
        /// Controls properties for linking the vertex stage of the render pipeline.
        #[unsafe(method(vertexLinkingDescriptor))]
        #[unsafe(method_family = none)]
        pub fn vertex_linking_descriptor(&self) -> Retained<MTL4PipelineStageDynamicLinkingDescriptor>;

        /// Controls properties for linking the fragment stage of the render pipeline.
        #[unsafe(method(fragmentLinkingDescriptor))]
        #[unsafe(method_family = none)]
        pub fn fragment_linking_descriptor(&self) -> Retained<MTL4PipelineStageDynamicLinkingDescriptor>;

        /// Controls properties for linking the tile stage of the render pipeline.
        #[unsafe(method(tileLinkingDescriptor))]
        #[unsafe(method_family = none)]
        pub fn tile_linking_descriptor(&self) -> Retained<MTL4PipelineStageDynamicLinkingDescriptor>;

        /// Controls properties for link the object stage of the render pipeline.
        #[unsafe(method(objectLinkingDescriptor))]
        #[unsafe(method_family = none)]
        pub fn object_linking_descriptor(&self) -> Retained<MTL4PipelineStageDynamicLinkingDescriptor>;

        /// Controls properties for linking the mesh stage of the render pipeline.
        #[unsafe(method(meshLinkingDescriptor))]
        #[unsafe(method_family = none)]
        pub fn mesh_linking_descriptor(&self) -> Retained<MTL4PipelineStageDynamicLinkingDescriptor>;
    );
}

/// Methods declared on superclass `NSObject`.
impl MTL4RenderPipelineDynamicLinkingDescriptor {
    extern_methods!(
        #[unsafe(method(init))]
        #[unsafe(method_family = init)]
        pub fn init(this: Allocated<Self>) -> Retained<Self>;

        #[unsafe(method(new))]
        #[unsafe(method_family = new)]
        pub fn new() -> Retained<Self>;
    );
}
