use indexed_valued_enums_derive::{EnumIndexed, EnumValued};
use crate as indexed_valued_enums;

/// Enum with every planet of the solar system.
///
/// We mark it with ``#[EnumIndex]`` so we can get the following methods:
///
/// - ``const Planet::variant_index(&self) -> usize``: Gets the index of a variant,
/// for example, [Planet::Earth] is the third variant, so it's index is 2.
///
/// - ``const Planet::variant_name(&self) -> &'static str``: Gets the name of a variant,
/// for example, [Planet::Earth] would return ``"Earth"``.
///
/// - ``const Planet::from_variant_index -> Option<Planet>``: Gets the variant matching
/// said index, for example, 2 would return ``Some(Planet::Earth)``.
///
/// - ``const Planet::from_variant_name -> Option<Planet>``: Gets the variant matching
/// said name, for example, "Earth" would return ``Some(Planet::Earth)``, but it is preferable
/// to use [Planet::from_variant_index] rather than [Planet::from_variant_name], as that one
/// is O(1) and this one is O(N).
#[EnumIndexed(FieldlessEnum)]
#[derive(PartialEq, Debug)]
pub enum Planet{
    Mercury,
    Venus,
    Earth,
    Mars,
    Jupiter,
    Saturn,
    Uranus,
    Neptune,
}

/// This function gravity returns the gravity of each planet, and by marking it with
/// EnumValued, it turns it into this two following methods:
///
/// - ``const Planet::gravity(&self) -> f32``: Gets the gravity of a planet, but
/// this function will always be O(1) rather than O(N), as this function is then turned
/// into an array where it gets the gravity of every planet.
///
/// - ``Planet::from_gravity(&f32) -> Option<Planet>``: Gets the first variant whose gravity
/// is the same as the planet's.
///
/// If you are curious of the expansion, it is similar (but not the same) to this:
///
/// ```rust
/// mod hide_gravities {
///     const GRAVITIES : [f32;8] = [Mercury => 3.8, Venus => 8.87, Earth => 9.8, Mars => 3.7,
///         Jupiter => 24.8, Saturn => 10.5, 8.7, Neptune => 11.1];
///
///     impl Planet{
///         pub const fn gravity(&self) -> f32 {
///             GRAVITIES[self.variant_index(self)]
///         }
///
///         pub fn from_gravity(value: &f32) -> Option<Self>
///         where f32: PartialEq, Self: indexed_valued_enums::IndexedWithVariant {
///             GRAVITIES.iter().enumerate()
///                 .filter(|(_,gravity)| value==*gravity )
///                 .next().map(|(index, _)| Self::from_variant_index(index))
///         }
///     }
/// }
/// ```
///
#[EnumValued]
pub const fn gravity(planet: &'static Planet) -> f32{
    match planet {
        Planet::Mercury => 3.8,
        Planet::Venus => 8.87,
        Planet::Earth => 9.8,
        Planet::Mars => 3.7,
        Planet::Jupiter => 24.8,
        Planet::Saturn => 10.5,
        Planet::Uranus => 8.7,
        Planet::Neptune => 11.1
    }
}

pub fn main() {
    // Iterate over every variant.
    Planet::VARIANTS.iter().for_each(|planet|{
        let name = planet.variant_name();
        let gravity = planet.gravity();
        println!("The planet {name} has a gravity of {gravity}");
    });

    // Get a variant from its name.
    let earth = Planet::from_variant_name("Earth").unwrap();
    let name = earth.variant_name();
    let gravity = earth.gravity();
    println!("The planet {name} is our home, and it has a gravity of {gravity}");

    // Get a variant's index.
    let earth_index_in_enum = Planet::Earth.variant_index();
    println!("Planet Earth has and index of {earth_index_in_enum}");

    // Get a variant's from its index.
    // In this case, the planet earth is the variant with index 2.
    let earth = Planet::from_variant_index(earth_index_in_enum).unwrap();
    let earth_from_raw_index = Planet::from_variant_index(2).unwrap();

    assert_eq!(earth, Planet::Earth);
    assert_eq!(earth_from_raw_index, Planet::Earth);

    // Get a planet out of its gravity.
    let planet_with_9_8_gravity = Planet::from_gravity(&9.8).unwrap();

    println!("The planet with 9.8 gravity is {}", planet_with_9_8_gravity.variant_name());
}