//! [![crates.io](https://img.shields.io/crates/v/indexed_valued_enums.svg)](https://crates.io/crates/indexed_valued_enums)
//! [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/JorgeRicoVivas/indexed_valued_enums/rust.yml)](https://github.com/JorgeRicoVivas/indexed_valued_enums/actions)
//! [![docs.rs](https://img.shields.io/docsrs/indexed_valued_enums)](https://docs.rs/indexed_valued_enums/latest/indexed_valued_enums/)
//! [![GitHub License](https://img.shields.io/github/license/JorgeRicoVivas/indexed_valued_enums)](https://github.com/JorgeRicoVivas/indexed_valued_enums?tab=CC0-1.0-1-ov-file)
//!
//! # indexed_valued_enums
//!
//! This crate makes easier to associate values to variant's of an enum while not incurring a slowdown in runtime, and
//! also to get the variant back from the value.
//!
//! This crate gives you the following derive macros for this:
//! - [EnumIndexed]: This gives you functions for getting the index and name of every variant, and get the variant
//! back from either the index or the name. This is done by implementing the [Indexed], [IndexedWithVariant], and
//! [Fieldless] traits, and it gives you the following functions:
//!   - ``const fn variant_index(&self) -> usize``, this is O(1).
//!   - ``const fn from_variant_index(index: usize) -> Option<Self>``, this is O(1).
//!   - ``const fn variant_name(&self) -> &'static str``, this is O(1).
//!   - ``const fn from_variant_name(search: &str) -> Option<Self>``, this is **O(N)**.
//!
//! - [EnumValued]: This macro turns functions with the signature ``fn *function_name*(&self) -> *TypeOfValue*`` into
//! O(1) functions. It also gives you a ``fn from_*name of function*(value: &*associated type*) -> Option<Self>``
//! function, which is O(N).
//!
//! - [EnumValuedFromCSV]: Similar to [EnumValued], but you can specify the values of the variants in a CSV file.
//!
//! You can find a full description and examples inside the documentation of each of these macros.
//! 
//! Note for users of ``1.X.X``: The ``2.X.X`` version offers no backward compatibility with ``1.X.X``, as ``1.X.X`` was 
//! arduous to use for end-users.


#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "parameter_types", feature(freeze))]
#![cfg_attr(feature = "unstable_docs", feature(doc_cfg))]

pub mod indexed_enum;

pub mod mem;


pub use crate::indexed_enum::{Indexed, IndexedWithVariant, Fieldless};

pub(crate) mod common_utils;

pub use const_default::ConstDefault;

#[cfg(not(feature = "std"))]
#[cfg_attr(feature = "unstable_docs", doc(cfg(not(feature = "std"))))]
pub mod core_lazy;

#[cfg(not(feature = "std"))]
pub type Lazy<T, F = fn() -> T> = core_lazy::Lazy<T,F>;

#[cfg(feature = "std")]
pub type Lazy<T, F = fn() -> T> = std::sync::LazyLock<T, F>;

/// Re-export of the [core::marker::Freeze] trait, once said trait is stabilized,
/// the feature ``parameter_types`` will be stabilized, and therefore, won't be required and would
/// be removed.
#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "parameter_types")))]
#[cfg(feature = "parameter_types")]
pub trait Freeze : core::marker::Freeze{}

#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "parameter_types")))]
#[cfg(feature = "parameter_types")]
impl <T> Freeze for T where T : core::marker::Freeze{}



#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "docs")))]
#[cfg(feature = "docs")]
/// Documented examples on how to use this crate.
pub mod examples;


#[cfg_attr(feature = "unstable_docs", doc(cfg(any(feature = "derive", feature = "legacy_derive"))))]
#[cfg(feature = "derive")]
extern crate indexed_valued_enums_derive;

#[cfg_attr(feature = "unstable_docs", doc(cfg(feature = "derive")))]
#[cfg(feature = "derive")]
pub use indexed_valued_enums_derive::{
    EnumIndexed, EnumValued, IndexedHelper, EnumValuedFromCSV, DefineEnumValuedFromCSV
};

#[macro_export]
macro_rules! do_nothing{
    ($($t:tt)*)=>{}
}

#[macro_export]
macro_rules! create_uninit_array {
    (len: {$len:expr}, on index named $index_name:ident {$on_index:expr} ) => {{
        let mut res = [const { core::mem::MaybeUninit::uninit() }; $len];
        let mut $index_name = 0;
        while $index_name < $len {
            res[$index_name] = core::mem::MaybeUninit::new($on_index);
            $index_name += 1;
        }
        res
    }};
}

#[macro_export]
macro_rules! impl_indexed {
    (
        impl Indexed,
        enum: $enum:ident,
        $(
            {
                enum for fields: $enum_for_variants_names:ident,
                variants names: {$($variants_names_in_enum_for_variants_names:ident)*},
                $(
                    generics{
                        common target: $_:ident,
                        generics <params>: {$($generic_params_for_variants_names:tt)*},
                        generics where: {$($generics_where_clause_contents_for_variants_names:tt)*},
                        lifetimes names: {$($generics_lifetimes_names_for_variants_names:lifetime)*},
                        consts names and types: {$($generic_consts_names_for_variants_names:ident : $_unused_gfv1:ty)*},
                        generic types names: {$($generic_types_names_for_variants_names:ident)*} $(,)?

                        lifetimes as statics: {$($_unused_gfv2:lifetime)*},
                        consts as static value: { $({$_unused_gfv3:expr} : $_unused_gfv4:ty)*},
                        generic as static type: {$($_unused_gfv5:ty)*},
                    }
                )?
            }
        )?
        $(
            self_is_fieldless: {$($self_is_fieldless:tt)*},
        )?
        $(
            generics{
                common target: $enum_common_type_name:ident,
                generics <params>: {$($generic_params:tt)*},
                generics where: {$($generics_where_clause_contents:tt)*},
                lifetimes names: {$($generics_lifetimes_names:lifetime)*},
                consts names and types: {$($generic_consts_names:ident : $generic_consts_types:ty)*},
                generic types names: {$($generic_types_names:ident)*} $(,)?

                lifetimes as statics: {$($generics_lifetimes_as_statics:lifetime)*},
                consts as static value: { $({$generic_consts_as_common_value:expr} : $generic_consts_types_as_common_value_type:ty)*},
                generic as static type: {$($generic_types_as_common_type:ty)*},
            }
        )?

        doc variant: {$($doc_variant_index:literal $doc_variant:ident)?},
        field types: {$($field_type:ty)*},
        variants len: $len:literal,
        variants names: {$($variants_names:ident)*},
        methods vis: $vis:vis,
        vars: { $({$value:expr})* },
    ) => {
        $(
        /// Default used in [enum_value] macros to resolve values.
        ///
        /// This serves as a place holder.
        pub type $enum_common_type_name = $enum
            <
            $($generics_lifetimes_as_statics,)*
            $({$generic_consts_as_common_value},)*
            $($generic_types_as_common_type,)*
            >;
        )?


        impl $(<$($generic_params)*>)? indexed_valued_enums::indexed_enum::Indexed
        for $enum $(< $($generics_lifetimes_names,)* $($generic_consts_names,)* $($generic_types_names,)*  >)?
        $(where $($generics_where_clause_contents)*)?
        {
            #[doc = concat!("Amount of variants the enum [",stringify!($enum),"], has.")]
            const VARIANTS_LEN: usize = $len;

            #[doc = concat!("Names of every variant in this enum [",stringify!($enum),"].")]
            const VARIANTS_NAMES: &'static [&'static str] = &[$( stringify!($variants_names), )*];
        }

        impl $(<$($generic_params)*>)?
        $enum $(< $($generics_lifetimes_names,)* $($generic_consts_names,)* $($generic_types_names,)*  >)?
        $(where $($generics_where_clause_contents)*)?
        {
            #[doc = concat!("Amount of variants the enum [",stringify!($enum),"], has.")]
            $vis const VARIANTS_LEN: usize = <Self as indexed_valued_enums::Indexed>::VARIANTS_LEN;

            #[doc = concat!("Names of every variant in this enum [",stringify!($enum),"].")]
            $vis const VARIANTS_NAMES: &'static [&'static str] = <Self as indexed_valued_enums::Indexed>::VARIANTS_NAMES;

            /// Gets the index of this variant.
            ///
            /// This operation is O(1).
            $(
                #[doc = "```rust"]
                #[doc = concat!("assert_eq!(",stringify!($doc_variant_index), ", ", stringify!($enum), "::",stringify!($doc_variant),".variant_index())" )]
                #[doc = "```"]
            )?
            $vis const fn variant_index(&self) -> usize {
                indexed_valued_enums::indexed_enum::variant_index(self)
            }

            /// Gets the name / ident of this variant.
            ///
            /// This operation is O(1).
            $(
                #[doc = "```rust"]
                #[doc = concat!("assert_eq!(\"",stringify!($doc_variant), "\", ", stringify!($enum), "::",stringify!($doc_variant),".variant_name())" )]
                #[doc = "```"]
            )?
            $vis const fn variant_name(&self) -> &'static str {
                indexed_valued_enums::indexed_enum::variant_name(self)
            }
        }

        $(
            indexed_valued_enums::do_nothing!($($self_is_fieldless)*);

            impl indexed_valued_enums::indexed_enum::Fieldless
            for $enum
            {
                #[doc = concat!("The fieldless version of [",stringify!($enum),"] is [",stringify!($enum),"].")]
                type Target = Self;
            }
        )?

        $(
            #[doc = concat!("Enum like [",stringify!($enum),"], but every variant is fieldless, meaning they are unit like.")]
            #[indexed_valued_enums::EnumIndexed]
            $vis enum $enum_for_variants_names{
                $($variants_names_in_enum_for_variants_names,)*
            }

            impl $(<$($generic_params_for_variants_names)*>)? indexed_valued_enums::indexed_enum::Fieldless
            for $enum $(< $($generics_lifetimes_names_for_variants_names,)* $($generic_consts_names_for_variants_names,)* $($generic_types_names_for_variants_names,)*  >)?
            $(where $($generics_where_clause_contents_for_variants_names)*)?
            {
                #[doc = concat!("The fieldless version of [",stringify!($enum),"] is [",stringify!($enum_for_variants_names),"].")]
                type Target = $enum_for_variants_names;
            }

        )?
    };
    (
        impl IndexedWithVariant,
                enum: $enum:ident,
        $(
            {
                enum for fields: $enum_for_variants_names:ident,
                variants names: {$($variants_names_in_enum_for_variants_names:ident)*},
                $(
                    generics{
                        common target: $_:ident,
                        generics <params>: {$($generic_params_for_variants_names:tt)*},
                        generics where: {$($generics_where_clause_contents_for_variants_names:tt)*},
                        lifetimes names: {$($generics_lifetimes_names_for_variants_names:lifetime)*},
                        consts names and types: {$($generic_consts_names_for_variants_names:ident : $_unused_gfv1:ty)*},
                        generic types names: {$($generic_types_names_for_variants_names:ident)*} $(,)?

                        lifetimes as statics: {$($_unused_gfv2:lifetime)*},
                        consts as static value: { $({$_unused_gfv3:expr} : $_unused_gfv4:ty)*},
                        generic as static type: {$($_unused_gfv5:ty)*},
                    }
                )?
            }
        )?
        $(
            self_is_fieldless: {$($self_is_fieldless:tt)*},
        )?
        $(
            generics{
                common target: $enum_common_type_name:ident,
                generics <params>: {$($generic_params:tt)*},
                generics where: {$($generics_where_clause_contents:tt)*},
                lifetimes names: {$($generics_lifetimes_names:lifetime)*},
                consts names and types: {$($generic_consts_names:ident : $generic_consts_types:ty)*},
                generic types names: {$($generic_types_names:ident)*} $(,)?

                lifetimes as statics: {$($generics_lifetimes_as_statics:lifetime)*},
                consts as static value: { $({$generic_consts_as_common_value:expr} : $generic_consts_types_as_common_value_type:ty)*},
                generic as static type: {$($generic_types_as_common_type:ty)*},
            }
        )?

        doc variant: {$($doc_variant_index:literal $doc_variant:ident)?},
        field types: {$($field_type:ty)*},
        variants len: $len:literal,
        variants names: {$($variants_names:ident)*},
        methods vis: $vis:vis,
        vars: { $({$value:expr})* },
    ) => {

        impl $(<$($generic_params)*>)? indexed_valued_enums::indexed_enum::IndexedWithVariant
        for $enum $(< $($generics_lifetimes_names,)* $($generic_consts_names,)* $($generic_types_names,)*  >)?
        where
        $($field_type: 'static + indexed_valued_enums::ConstDefault + indexed_valued_enums::Freeze, )*
        $(
            $($generics_lifetimes_names: 'static,)*
            $($generic_types_names: 'static + indexed_valued_enums::ConstDefault + indexed_valued_enums::Freeze,)*
            $($generics_where_clause_contents)*
        )?
        {
            #[doc = concat!("A constant of every variant the enum [",stringify!($enum),"], has.")]
            const VARIANTS: &'static [Self] = &[$(const {$value},)+];
        }

        impl $(<$($generic_params)*>)? $enum $(< $($generics_lifetimes_names,)* $($generic_consts_names,)* $($generic_types_names,)*  >)?
        where
        $($field_type: 'static + indexed_valued_enums::ConstDefault + indexed_valued_enums::Freeze, )*
        $(
            $($generics_lifetimes_names: 'static,)*
            $($generic_types_names: 'static + indexed_valued_enums::ConstDefault + indexed_valued_enums::Freeze,)*
            $($generics_where_clause_contents)*
        )?
        {
            #[doc = concat!("A constant of every variant the enum [",stringify!($enum),"], has.")]
            $vis const VARIANTS: &'static [Self] = <Self as indexed_valued_enums::IndexedWithVariant>::VARIANTS;

            /// Gets the variant corresponding to said index, and it will return None if the index
            /// asked is a number bigger than the number of variants.
            ///
            /// This enum doesn't need to implement the [Clone] trait as the array is treated as a raw
            /// pointer whose value is read without cloning through [core::ptr::read].
            ///
            /// This operation is O(1) as it just gets the variant as a read-copy from
            /// [indexed_valued_enums::IndexedWithVariant::VARIANTS].
            $(
                #[doc = "```rust"]
                #[doc = concat!("assert_eq!(Some(", stringify!($enum), "::",stringify!($doc_variant),"), ",stringify!($enum),"::from_variant_index(",stringify!($doc_variant_index),"))" )]
                #[doc = "```"]
            )?
            $vis const fn from_variant_index(index: usize) -> Option<Self> {
                indexed_valued_enums::indexed_enum::from_variant_index(index)
            }

            /// Gets the variant corresponding to said name.
            ///
            /// This operation is O(N), but since it is const, be inserted as const when the string
            /// to search is also const.
            $(
                #[doc = "```rust"]
                #[doc = concat!("assert_eq!(Some(", stringify!($enum), "::",stringify!($doc_variant),"), ",stringify!($enum),"::from_variant_name(\"",stringify!($doc_variant),"\"))" )]
                #[doc = "```"]
            )?
            $vis const fn from_variant_name(search: &str) -> Option<Self>{
                indexed_valued_enums::indexed_enum::from_variant_name(search)
            }
        }
    };
}

#[macro_export]
macro_rules! enum_value {
    (
        resolve common target,
        common target: $enum_common_type_name:ident,
        is downcasted to fieldless: $is_downcasted_to_fieldless:literal,
    )=>{
        <$enum_common_type_name as indexed_valued_enums::indexed_enum::Fieldless>::Target
    };
    (
        resolve common target,
        common target: $enum_common_type_name:ident,
    )=>{
        $enum_common_type_name
    };
    (
        visibility: $vis:vis,
        enum: $enum:ident,
        common target: $enum_common_type_name:ident,
        $(is downcasted to fieldless: $is_downcasted_to_fieldless:literal,)?
        $(
            generics{
                generics <params>: {$($generic_params:tt)*} $(,)?
                generics where: {$($generics_where_clause_contents:tt)*} $(,)?
                lifetimes names: {$($generics_lifetimes_names:lifetime)*} $(,)?
                consts names and types: {$($generic_consts_names:ident : $generic_consts_types:ty)*} $(,)?
                generic types names: {$($generic_types_names:ident)*} $(,)?
            }
        )?

        value type: $value_type:ty,
        hidder module: $hidder_module:ident,
        function name: $function_name:ident,
        from function name: $from_function_name:ident,
        static or const: {$($static_or_const:tt)*},
        function attributes: {$($function_attributes:tt)*},
        function: {$function:item},
    ) =>
    {
        mod $hidder_module {
            use super::*;

            $function

            /// Type used to resolve the values.
            ///
            /// Only one value is required, meaning there is no necessity to have different arrays
            /// for every single type when using parameter types.
            type CommonTarget = indexed_valued_enums::enum_value!{
                resolve common target,
                common target: $enum_common_type_name,
                $(is downcasted to fieldless: $is_downcasted_to_fieldless,)?
            };

            /// Amount of variants
            const LEN:usize = <CommonTarget as indexed_valued_enums::indexed_enum::Indexed>::VARIANTS_LEN;

            indexed_valued_enums::enum_value!(
                create array {$($static_or_const)*},
                function name: $function_name,
                value type: $value_type,
            );

            impl $(<$($generic_params)*>)? $enum
            $(< $($generics_lifetimes_names,)* $($generic_consts_names,)* $($generic_types_names,)* >)?

            $(where $($generics_where_clause_contents)*)? {

                indexed_valued_enums::enum_value!(
                    impl getter function {$($static_or_const)*},
                    visibility: $vis,
                    function name: $function_name,
                    value type: $value_type,
                    function attributes: {$($function_attributes)*},
                );

                #[doc = concat!("Invert function of [",stringify!($enum),"::",stringify!($function_name),"].")]
                ///
                /// This operation has a worst case scenario of O(N), being N the number of
                /// variants, and is not const.
                $vis fn $from_function_name(value: &$value_type) -> Option<Self>
                where
                $value_type: PartialEq,
                Self: indexed_valued_enums::IndexedWithVariant,
                {
                    indexed_valued_enums::indexed_enum::search_value(
                        value,
                        VALUES.iter(),
                    )
                }
            }
        }
    };

    (
        create array {static},
        function name: $function_name:ident,
        value type: $value_type:ty,
    )=>
    {
        /// Values where every index matches an enum variant.
        ///
        /// This is lazy, meaning the value is calculated at runtime, but just once.
        static VALUES: indexed_valued_enums::Lazy<[$value_type;LEN]> = {
            indexed_valued_enums::Lazy::new(||{
                let values = indexed_valued_enums::create_uninit_array!(
                len: {LEN},
                on index named index {
                    {
                        let variant : &CommonTarget = indexed_valued_enums::indexed_enum::ref_from_variant_index(index).unwrap();
                        $function_name(variant)
                    }
                });
                indexed_valued_enums::mem::unwrap_maybe_uninit_array(values)
            })
        };
    };

    (
        create array {const},
        function name: $function_name:ident,
        value type: $value_type:ty,
    )=>{
        /// Values where every index matches an enum variant.
        ///
        /// This is const, allowing to inline values at compile time.
        const VALUES: [$value_type; LEN] = {
                let values = indexed_valued_enums::create_uninit_array!(
                len: {LEN},
                on index named index {
                    {
                        let variant : &CommonTarget = indexed_valued_enums::indexed_enum::ref_from_variant_index(index).unwrap();
                        $function_name(variant)
                    }
                });
                indexed_valued_enums::mem::unwrap_maybe_uninit_array(values)
            };
    };

    (
        impl getter function {static},
        visibility: $vis:vis,
        function name: $function_name:ident,
        value type: $value_type:ty,
        function attributes: {$($function_attributes:tt)*},
    )=>{
        $($function_attributes)*
        ///
        /// This is operation is O(1) and lazy, meaning the value is calculated at runtime, but just once.
        $vis fn $function_name(&self) -> &'static $value_type {
            &VALUES[indexed_valued_enums::indexed_enum::variant_index(self)]
        }
    };

    (
        impl getter function {const},
        visibility: $vis:vis,
        function name: $function_name:ident,
        value type: $value_type:ty,
        function attributes: {$($function_attributes:tt)*},
    )=>{
        $($function_attributes)*
        ///
        /// This is operation is O(1) and const, allowing to inline values at compile time.
        $vis const fn $function_name(&self) -> $value_type {
            VALUES[indexed_valued_enums::indexed_enum::variant_index(self)]
        }
    };
}




