use crate::common_utils::split_usize_to_isizes;

/// Allows getting an index from an enum's variant to a usize, and also get the same variant
/// from said index, for example, having the following implementation:
///
/// ```rust
/// #[repr(usize)]
/// enum Number{ First, Second, Third }
///
/// impl indexed_valued_enums::indexed_enum::Indexed for Number{
///     const VARIANTS_LEN: usize = 3;
///     const VARIANTS_NAMES: &'static [&'static str] = &["First", "Second", "Third"];
/// }
///
/// impl indexed_valued_enums::indexed_enum::IndexedWithVariant for Number{
///     const VARIANTS: &'static [Self] = &[Number::First, Number::Second, Number::Third];
/// }
///
/// ```
/// Calling [variant_index] on every enum produces [First->0, Second->1, Third->2].
///
/// Calling on [from_variant_index] over the enums would produce [0->First, 1->Second, 2->Third].
///
/// Note this documentation it's solely informational, it is dis-recommended to implement this trait
/// manually, but using the derive macro [crate::legacy::valued_enum::Valued] or the declarative macro
/// [crate::create_indexed_valued_enum] instead.
pub trait Indexed {
    /// Number of variants.
    ///
    /// [IndexedWithVariant::VARIANTS] and [Self::VARIANTS_NAMES] should have the same length as this constant.
    const VARIANTS_LEN: usize;

    /// Array with the name of every variant
    const VARIANTS_NAMES: &'static [&'static str];
}

/// Similar to [Indexed], but allows getting a constant of every variant for this enum.
pub trait IndexedWithVariant: Sized where Self: 'static {

    /// Array storing all the variants of the enum ordered by index.
    const VARIANTS: &'static [Self];
}

/// Tells a target type this enum resolves to, where this target should be another enum just
/// like the implementor, but where every variant has no field, being unit like.
pub trait Fieldless : Indexed{
    /// The target that should be an enum where every field is unit like.
    type Target;
}

/// Gets the index for a variant of an enum marked with ``#[repr(usize)]``.
///
/// This operation is O(1).

// DO NOT REMOVE INLINE, it will cause the variant_name function to crash.
#[inline]
pub const fn variant_index<T>(variant: &T) -> usize {
    unsafe { *(variant as *const T).cast::<usize>() }
}

/// Gets the variant corresponding to said index, this operation is O(1) as it just gets
/// the index as a read-copy from [IndexedWithVariant::VARIANTS].
///
/// The enum bust be marked with ``#[repr(usize)]`` and it 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).
pub const fn from_variant_index<TIndexed: IndexedWithVariant>(index: usize) -> Option<TIndexed> {
    if index >= TIndexed::VARIANTS.len() {
        return None;
    }
    let (first_offset, second_offset, third_offset) = split_usize_to_isizes(index);
    Some(unsafe {
        TIndexed::VARIANTS
            .as_ptr()
            .offset(first_offset)
            .offset(second_offset)
            .offset(third_offset)
            .read()
    })
}

/// Gets the variant corresponding to said index as a static reference, this operation is O(1) as it
/// just gets the index as a read-copy from [IndexedWithVariant::VARIANTS].
///
/// The enum bust be marked with ``#[repr(usize)]`` and it 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).
pub const fn ref_from_variant_index<TIndexed: IndexedWithVariant>(index: usize) -> Option<&'static TIndexed> {
    if index >= TIndexed::VARIANTS.len() {
        return None;
    }
    let (first_offset, second_offset, third_offset) = split_usize_to_isizes(index);
    unsafe {
        TIndexed::VARIANTS
            .as_ptr()
            .offset(first_offset)
            .offset(second_offset)
            .offset(third_offset)
            .as_ref()
    }
}

/// Gets the name for a variant of an enum marked with ``#[repr(usize)]``.
///
/// This operation is O(1).
pub const fn variant_name<TIndexed: Indexed>(variant: &TIndexed) -> &'static str {
    <TIndexed as Indexed>::VARIANTS_NAMES[variant_index(variant)]
}

/// Gets the variant corresponding to said name.
///
/// The enum bust be marked with ``#[repr(usize)]`` and it 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(N), but since it is const, be inserted as const.
pub const fn from_variant_name<TIndexed: Indexed + IndexedWithVariant>(search: &str) -> Option<TIndexed> {
    let mut index = 0;
    while index< <TIndexed as Indexed>::VARIANTS_LEN{
        if compare_strings(search, <TIndexed as Indexed>::VARIANTS_NAMES[index]) {
            return from_variant_index(index);
        }
        index+=1;
    }
    None
}

const fn compare_strings(string_1:&str, string_2:&str) -> bool {
    if string_1.len()!=string_2.len(){
        return false;
    }
    let bytes_1 = string_1.as_bytes();
    let bytes_2 = string_2.as_bytes();
    let mut index = 0;
    while index<bytes_1.len(){
        if bytes_1[index]!=bytes_2[index]{
            return false;
        }
        index+=1;
    }
    true
}

/// Searches for a variant verifying ``VARIANT::value == search_value``.
///
/// This operation has a worst case scenario of O(N), where N is the length of the array, this
/// happens when either the value cannot be found, or the value found is the last of the array.
pub fn search_value<'value, ENUM, VALUE, VALUES>(search_value: &VALUE, values: VALUES) -> Option<ENUM>
where
    ENUM: IndexedWithVariant,
    VALUE: PartialEq + 'value,
    VALUES: Iterator<Item= &'value VALUE>
{
    values
        .enumerate()
        .filter(|(_, value)| search_value.eq(value))
        .next()
        .map(|(index, _)| from_variant_index(index))
        .flatten()
}