// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Implementation of the `declare_handle_map!` macro

#[macro_export]
/// ```ignore
/// declare_handle_map! {
///     mod $handle_module_name {
///         #[dimensions = $map_dimension_provider]
///         type $handle_type_name: HandleLike<Object = $wrapped_type>;
///     }
/// }
/// ```
///
/// Declares a new public module with name `handle_module_name` which includes a new type
/// `handle_type_name` which is `#[repr(C)]` and represents FFI-accessible handles
/// to values of type `wrapped_type`.
///
/// Internal to the generated module, a new static `SingletonHandleMap` is created, where the
/// maximum number of active handles and the number of shards are given by
/// the dimensions returned by evaluation of the `map_dimension_provider` expression.
///
/// Note: `map_dimension_provider` will be evaluated within the defined module's scope,
/// so you will likely need to use `super` to refer to definitions in the enclosing scope.
///
/// # Example
/// The following code defines an FFI-safe type `StringHandle` which references
/// the `String` data-type, and uses it to define a (contrived)
/// function `sample` which will print "Hello World".
///
/// ```
/// #[macro_use]///
/// extern crate lazy_static;
///
/// use core::ops::Deref;
/// use handle_map::{declare_handle_map, HandleMapDimensions, HandleLike};
///
/// fn get_string_handle_map_dimensions() -> HandleMapDimensions {
///     HandleMapDimensions {
///         num_shards: 8,
///         max_active_handles: 100,
///     }
///  }
///
///  declare_handle_map! {
///     mod string_handle {
///         #[dimensions = super::get_string_handle_map_dimensions()]
///         type StringHandle: HandleLike<Object = String>;
///     }
///  }
///
///  use string_handle::StringHandle;
///
///  fn main() {
///         // Note: this method could panic if there are
///         // more than 99 outstanding handles.
///
///         // Allocate a new string-handle pointing to the string "Hello"
///         let handle = StringHandle::allocate(|| { "Hello".to_string() }).unwrap();
///         {
///             // Obtain a write-guard on the contents of our handle
///             let mut handle_write_guard = handle.get_mut().unwrap();
///             handle_write_guard.push_str(" World");
///             // Write guard is auto-dropped at the end of this block.
///         }
///         {
///             // Obtain a read-guard on the contents of our handle.
///             // Note that we had to ensure that the write-guard was
///             // dropped prior to doing this, or else execution
///             // could potentially hang.
///             let handle_read_guard = handle.get().unwrap();
///             println!("{}", handle_read_guard.deref());
///         }
///         // Clean up the data behind the created handle
///         handle.deallocate().unwrap();
///  }
///
/// ```
macro_rules! declare_handle_map {
    (
        mod $handle_module_name:ident {
            #[dimensions = $map_dimension_provider:expr]
            type $handle_type_name:ident: HandleLike<Object = $wrapped_type:ty>;
        }
     ) => {
        #[doc = ::core::concat!(
                    "Macro-generated (via `handle_map::declare_handle_map!`) module which",
                    " defines the `", ::core::stringify!($handle_module_name), "::",
                    ::core::stringify!($handle_type_name), "` FFI-transmissible handle type ",
                    " which references values of type `", ::core::stringify!($wrapped_type), "`."
                )]
        pub mod $handle_module_name {

            lazy_static! {
                static ref GLOBAL_HANDLE_MAP: $crate::HandleMap<$wrapped_type> =
                $crate::HandleMap::with_dimensions($map_dimension_provider);
            }

            #[doc = ::core::concat!(
                        "A `#[repr(C)]` handle to a value of type `",
                        ::core::stringify!($wrapped_type), "`."
                    )]
            #[repr(C)]
            #[derive(Clone, Copy, PartialEq, Eq)]
            pub struct $handle_type_name {
                handle_id: u64,
            }

            impl $handle_type_name {
                /// Cast the given raw Handle to this HandleLike
                pub fn from_handle(handle: $crate::Handle) -> Self {
                    Self { handle_id: handle.get_id() }
                }

                /// Get this HandleLike as a raw Handle.
                pub fn get_as_handle(&self) -> $crate::Handle {
                    $crate::Handle::from_id(self.handle_id)
                }
            }
            impl $crate::HandleLike for $handle_type_name {
                type Object = $wrapped_type;
                fn try_allocate<E: core::fmt::Debug>(
                    initial_value_provider: impl FnOnce() -> Result<$wrapped_type, E>,
                ) -> Result<Self, $crate::HandleMapTryAllocateError<E>> {
                    GLOBAL_HANDLE_MAP
                        .try_allocate(initial_value_provider)
                        .map(|derived_handle| Self { handle_id: derived_handle.get_id() })
                }
                fn allocate(
                    initial_value_provider: impl FnOnce() -> $wrapped_type,
                ) -> Result<Self, $crate::HandleMapFullError> {
                    GLOBAL_HANDLE_MAP
                        .allocate(initial_value_provider)
                        .map(|derived_handle| Self { handle_id: derived_handle.get_id() })
                }
                fn get(
                    &self,
                ) -> Result<$crate::ObjectReadGuardImpl<$wrapped_type>, $crate::HandleNotPresentError>
                {
                    GLOBAL_HANDLE_MAP.get(self.get_as_handle())
                }
                fn get_mut(
                    &self,
                ) -> Result<
                    $crate::ObjectReadWriteGuardImpl<$wrapped_type>,
                    $crate::HandleNotPresentError,
                > {
                    GLOBAL_HANDLE_MAP.get_mut(self.get_as_handle())
                }
                fn deallocate(self) -> Result<$wrapped_type, $crate::HandleNotPresentError> {
                    GLOBAL_HANDLE_MAP.deallocate(self.get_as_handle())
                }
            }
        }
    };
}
