use crate::indexed_enum::Indexed;
use crate::valued_enum::Valued;
#[macro_export]
macro_rules! create_indexed_valued_enum {
        (
        $(#[doc = $doc:expr])?
        $(#[derive($($derives:ident),*)])?
        $(#[features($($features:tt),*)])?
        $visibility:vis enum $enum_name:ident valued as $value_type:ty;
        $($variants:ident, $values:expr),+ $(,)?
    ) => {
        $(#[derive($($derives),*)])?
        $(#[doc = $doc])?
        #[repr(usize)]
        $visibility enum $enum_name{
            $($variants),+
        }
        impl indexed_valued_enums::indexed_enum::Indexed for $enum_name {
            #[doc = concat!("Array storing all the variants of the [",stringify!($enum_name),"]\
            enum where each variant is stored in ordered by their discriminant")]
            const VARIANTS: &'static [ Self ] = &[$($enum_name::$variants),+];
        }
        impl indexed_valued_enums::valued_enum::Valued for $enum_name {
            type Value = $value_type;
            #[doc = concat!("Array storing all the variants values of the \
             [",stringify!($enum_name),"] enum, each value is stored in the same order as the \
            discriminant of the variant they belong to")]
            const VALUES: &'static [ Self::Value] = & [$($values),+];
        }
        $(create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($features)*] })?
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [Delegators $($other_features:tt)*]
    )=>{
        impl $enum_name {
            #[doc = concat!("Gets the discriminant of this",stringify!($enum_name),", this \
            operation is O(1)")]
            pub fn discriminant(&self) -> usize {
                indexed_valued_enums::indexed_enum::Indexed::discriminant(self)
            }
            #[doc = concat!("Gets the",stringify!($enum_name),"'s variant corresponding to said \
            discriminant, this operation is O(1) as it just gets the discriminant as a copy from \
            [indexed_valued_enums::indexed_enum::Indexed::VARIANTS], meaning this enum does not \
            need to implement [Clone]")]
            pub fn from_discriminant_opt(discriminant: usize) -> Option<Self> {
                indexed_valued_enums::indexed_enum::Indexed::from_discriminant_opt(discriminant)
            }
            #[doc = concat!("Gets the",stringify!($enum_name),"'s variant corresponding to said \
            discriminant, this operation is O(1) as it just gets the discriminant as a copy from \
            [indexed_valued_enums::indexed_enum::Indexed::VARIANTS], meaning this enum does not \
            need to implement [Clone]")]
            pub fn from_discriminant(discriminant: usize) -> Self {
                indexed_valued_enums::indexed_enum::Indexed::from_discriminant(discriminant)
            }
            #[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding \
            to this [", stringify!($enum_name),"] 's variant, this operation is O(1) as it just \
            gets the discriminant as a copy from \
            [indexed_valued_enums::valued_enum::Valued::VALUES] \
            <br><br>This always returns [Option::Some], so it's recommended to call\
            [",stringify!($enum_name),"::value] instead")]
            pub fn value_opt(&self) -> Option<$value_type> {
                indexed_valued_enums::valued_enum::Valued::value_opt(self)
            }
            #[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding \
            to this [", stringify!($enum_name),"] 's variant, this operation is O(1) as it just \
            gets the discriminant as a copy from \
            [indexed_valued_enums::valued_enum::Valued::VALUES]")]
            pub fn value(&self) -> $value_type {
                indexed_valued_enums::valued_enum::Valued::value(self)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
        (process features
        [$enum_name:ident, $value_type:ty],
        [ValueToVariantDelegators $($other_features:tt)*]
    )=>{
        impl $enum_name {
            #[doc = concat!("Gives [",stringify!($enum_name),"]'s variant corresponding to this \
            value <br><br> this is an O(n) operation as it does so by comparing every single value \
            contained in [indexed_valued_enums::valued_enum::Valued::VALUES]")]
            pub fn value_to_variant_opt(value: &$value_type) -> Option<Self> {
                indexed_valued_enums::valued_enum::Valued::value_to_variant_opt(value)
            }
            #[doc = concat!("Gives [",stringify!($enum_name),"]'s variant corresponding to this \
            value <br><br> this is an O(n) operation as it does so by comparing every single value \
            contained in [indexed_valued_enums::valued_enum::Valued::VALUES]")]
            pub fn value_to_variant(value: &$value_type) -> Self {
                indexed_valued_enums::valued_enum::Valued::value_to_variant(value)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [DerefToValue $($other_features:tt)*]
    )=>{
        impl core::ops::Deref for $enum_name{
            type Target = $value_type;
            #[doc = concat!("Gives the value of type [",stringify!($value_type),"] corresponding to \
            this [", stringify!($enum_name),"] 's variant <br><br>Since \
            [indexed_valued_enums::valued_enum::Valued::VALUES] is a constant array, the value will \
            be referenced for 'static")]
            fn deref(&self) -> &'static Self::Target {
                &<Self as indexed_valued_enums::valued_enum::Valued>::VALUES[self.discriminant()]
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [Clone $($other_features:tt)*]
    )=>{
        impl core::clone::Clone for $enum_name {
            #[doc = concat!("Clones this [",stringify!($enum_name),"]'s variant<br><br>This clone \
            is taken from the constant array of\
            [indexed_valued_enums::indexed_enum::Indexed::VARIANTS], meaning this is a copy of that \
            array, and therefore not causing a long macro expansion")]
            fn clone(&self) -> Self {
                let discriminant = indexed_valued_enums::indexed_enum::Indexed::discriminant(self);
                indexed_valued_enums::indexed_enum::Indexed::from_discriminant(discriminant)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [Serialize $($other_features:tt)*]
    )=>{
        impl serde::Serialize for $enum_name {
            #[doc = concat!("Serializes this [",stringify!($enum_name),"]'s variant as it's \
            discriminant, reducing its serializing complexity")]
            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
                serializer.serialize_u128(self.discriminant() as u128)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [Deserialize $($other_features:tt)*]
    )=>{
        impl<'de> serde::Deserialize<'de> for $enum_name {
            #[doc = concat!("Deserializes this [",stringify!($enum_name),"]'s variant from it's \
            discriminant, reducing its deserializing complexity")]
            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
                match deserializer.deserialize_u128(indexed_valued_enums::serde_compatibility::discriminant_visitor::DISCRIMINANT_VISITOR) {
                    Ok(value) => {
                        match $enum_name::from_discriminant_opt(value) {
                            Some(value) => { Ok(value) }
                            None => { Err(serde::de::Error::custom("Deserialized an discriminant that is bigger than the amount of variants")) }
                        }
                    }
                    Err(error) => { Err(error) }
                }
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [NanoSerBin $($other_features:tt)*]
    )=>{
        impl nanoserde::SerBin for $enum_name {
            #[doc = concat!("Serializes this [",stringify!($enum_name),"]'s variant as it's \
            discriminant, reducing its serializing complexity")]
            fn ser_bin(&self, output: &mut Vec<u8>) {
                self.discriminant().ser_bin(output)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [NanoDeBin $($other_features:tt)*]
    )=>{
        impl nanoserde::DeBin for $enum_name {
            #[doc = concat!("Deserializes this [",stringify!($enum_name),"]'s variant from it's \
            discriminant, reducing its deserializing complexity")]
            fn de_bin(offset: &mut usize, bytes: &[u8]) -> core::result::Result<Self, nanoserde::DeBinErr> {
                core::result::Result::Ok(
                    $enum_name::from_discriminant_opt(nanoserde::DeBin::de_bin(offset, bytes)?)
                        .ok_or_else(|| nanoserde::DeBinErr {
                            o: *offset,
                            l: core::mem::size_of::<usize>(),
                            s: bytes.len(),
                        })?)
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [NanoSerJson $($other_features:tt)*]
    )=>{
        impl nanoserde::SerJson for $enum_name {
            #[doc = concat!("Serializes this [",stringify!($enum_name),"]'s variant as it's \
            discriminant, reducing its serializing complexity")]
            fn ser_json(&self, _d: usize, state: &mut nanoserde::SerJsonState) {
                state.out.push_str(&self.discriminant().to_string());
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features
        [$enum_name:ident, $value_type:ty],
        [NanoDeJson $($other_features:tt)*]
    )=>{
        impl nanoserde::DeJson for $enum_name {
            #[doc = concat!("Deserializes this [",stringify!($enum_name),"]'s variant from it's \
            discriminant, reducing its deserializing complexity")]
            fn de_json(state: &mut nanoserde::DeJsonState, input: &mut core::str::Chars) -> Result<Self, nanoserde::DeJsonErr> {
                let val = state.u64_range(core::u64::MAX as u64)?;
                state.next_tok(input)?;
                let discriminant = val as usize;
                let variant = $enum_name::from_discriminant_opt(discriminant)
                    .ok_or_else(|| nanoserde::DeJsonErr{
                        msg: "Indicated discriminant doesn't not correspond to any variant of this enum".to_string(),
                        line: 0,
                        col: 0,
                    })?;
                return Ok(variant);
            }
        }
        create_indexed_valued_enum !{process features [$enum_name, $value_type], [$($other_features)*]}
    };
    (process features [$enum_name:ident, $value_type:ty], [])=>{};
}