ks_constants_rs 0.1.2

Constants for use in Kerrigan Survival projects
Documentation
use std::collections::HashMap;
use std::ops::Index;

use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;


use crate::devs::Developer;
use crate::roletypes::{RoleType, Team};
use crate::locales::Language;

#[pyclass(frozen)]
#[derive(Clone, PartialEq, Debug)]
pub struct Role {
    _id: usize,
    pub name: String,
    role_type: Option<RoleType>,
    localized_name: HashMap<Language, String>,
    original_author: Developer,
    current_author: Developer,
    available: bool
}

impl Role {
    pub fn new(
        id: usize, 
        role_name: &str, 
        role_type: Option<RoleType>, 
        local_names: HashMap<Language, String>, 
        orig_author: Developer, 
        current_author: Option<Developer>, 
        available: bool
    ) -> Self{
        Role{
            _id: id, 
            name: role_name.to_string(), 
            role_type, 
            localized_name: local_names, 
            original_author: orig_author, 
            current_author: if current_author.is_none() {orig_author} else {current_author.unwrap()},
            available: available
        }
    }

    pub fn get_index(&self) -> usize {
        return self._id
    }

    pub fn get_team(&self) -> Team {
        if self.name != "Random" {
            return self.role_type.unwrap().get_team().clone();
        } else {
            panic!("Random does not have a team");
        }
    }

    pub fn get_role_type(&self) -> Option<RoleType> {
        return self.role_type
    }

    pub fn get_name(&self, locale: Language) -> String {
        return self.localized_name[&locale].clone();
    }

    pub fn get_english_name(&self) -> String {
        return self.localized_name[&Language::English].clone();
    }

    pub fn get_original_author(&self) -> Developer {
        return self.original_author;
    }

    pub fn get_current_author(&self) -> Developer {
        return self.current_author;
    }

    pub fn is_available(&self) -> bool {
        return self.available;
    }

    
}

macro_rules! define_roles {
    ($($key:ident => $id:expr),* $(,)?) => {
        #[pyclass(frozen)]
        #[derive(Clone)]
        pub struct Roles {
            $(#[pyo3(get)]
            pub $key: Role,)*
            _roles: HashMap<String, Role>,
            _raw_list: Vec<Role>
        }

        impl Roles {
            pub fn new() -> Self {
                let mut map = HashMap::new();
                let mut list = Vec::new();
                $(
                    let role = $id;
                    map.insert(stringify!($key).to_string(), role.clone());
                    list.push(role);

                )*
                Self { $($key: map[stringify!($key)].clone(),)* _roles: map, _raw_list: list }
            }
            pub fn from_index(&self, index: usize) -> &Role{
                &self._raw_list[index]
            }
        }

        #[pymethods]
        impl Roles {
            #[new]
            pub fn py_new() -> Self {
                Roles::new()
            }

            fn __iter__(slf: PyRef<Self>) -> PyRolesIter {
                PyRolesIter {
                    inner: slf.clone().into_iter(),
                }
            }
        }
    }
}

define_roles!(
    Kerrigan => Role::new(0, "Kerrigan", Some(RoleType::Hunter), HashMap::from([(Language::English, "Kerrigan".to_string())]), Developer::Geo, Some(Developer::Luminous), true),
    Scientist => Role::new(1, "Scientist", Some(RoleType::Builder), HashMap::from([(Language::English, "Scientist".to_string())]), Developer::Geo, Some(Developer::Luminous), true),
    Dark_Templar => Role::new(2, "Dark_Templar", Some(RoleType::Builder), HashMap::from([(Language::English, "Dark Templar".to_string())]), Developer::Geo, Some(Developer::Luminous), true),
    Ascendant => Role::new(3, "Ascendant", Some(RoleType::Builder), HashMap::from([(Language::English, "Ascendant".to_string())]), Developer::Luminous, None, true),
    Spirit => Role::new(4, "Spirit", Some(RoleType::Builder), HashMap::from([(Language::English, "Spirit".to_string())]), Developer::Luminous, None, true),
    Ares => Role::new(5, "Ares", Some(RoleType::Builder), HashMap::from([(Language::English, "Ares".to_string())]), Developer::Luminous, None, true),
    Prophet => Role::new(6, "Prophet", Some(RoleType::Support), HashMap::from([(Language::English, "Prophet".to_string())]), Developer::Luminous, None, true),
    Stukov => Role::new(7, "Stukov", Some(RoleType::Builder), HashMap::from([(Language::English, "Stukov".to_string())]), Developer::Luminous, None, true),
    Artanis => Role::new(8, "Artanis", Some(RoleType::Builder), HashMap::from([(Language::English, "Artanis".to_string())]), Developer::Luminous, None, true),
    Zagara => Role::new(9, "Zagara", Some(RoleType::Defender), HashMap::from([(Language::English, "Zagara".to_string())]), Developer::Luminous, None, true),
    Engineer => Role::new(10, "Engineer", Some(RoleType::Builder), HashMap::from([(Language::English, "Engineer".to_string())]), Developer::Luminous, None, true),
    Team_Nova => Role::new(11, "Team_Nova", Some(RoleType::Support), HashMap::from([(Language::English, "Team Nova".to_string())]), Developer::Luminous, None, true),
    Nomad => Role::new(12, "Nomad", Some(RoleType::Builder), HashMap::from([(Language::English, "Nomad".to_string())]), Developer::Luminous, None, true),
    Dehaka => Role::new(13, "Dehaka", Some(RoleType::Hunter), HashMap::from([(Language::English, "Dehaka".to_string())]), Developer::Luminous, None, true),
    Helios => Role::new(14, "Helios", Some(RoleType::Builder), HashMap::from([(Language::English, "Helios".to_string())]), Developer::Luminous, None, true),
    Random => Role::new(15, "Random", None, HashMap::from([(Language::English, "Random".to_string())]), Developer::Luminous, None, true),
    Thakras => Role::new(16, "Thakras", Some(RoleType::Hunter), HashMap::from([(Language::English, "Thakras".to_string())]), Developer::Luminous, None, true),
    Swann => Role::new(17, "Swann", Some(RoleType::Builder), HashMap::from([(Language::English, "Swann".to_string())]), Developer::Luminous, None, true),
    Warden => Role::new(18, "Warden", Some(RoleType::Support), HashMap::from([(Language::English, "Warden".to_string())]), Developer::Luminous, None, true),
    Selendis => Role::new(19, "Selendis", Some(RoleType::Builder), HashMap::from([(Language::English, "Selendis".to_string())]), Developer::Hex, None, true),
    Niadra => Role::new(20, "Niadra", Some(RoleType::Defender), HashMap::from([(Language::English, "Niadra".to_string())]), Developer::Luminous, None, true),
    Mira => Role::new(21, "Mira", Some(RoleType::Builder), HashMap::from([(Language::English, "Mira".to_string())]), Developer::Luminous, None, true),
    Scion => Role::new(22, "Scion", Some(RoleType::Support), HashMap::from([(Language::English, "Scion".to_string())]), Developer::Luminous, None, true),
    Technician => Role::new(23, "Technician", Some(RoleType::Builder), HashMap::from([(Language::English, "Technician".to_string())]), Developer::Fatline, Some(Developer::Gcloud), true),
    Warfield => Role::new(24, "Warfield", Some(RoleType::Builder), HashMap::from([(Language::English, "Warfield".to_string())]), Developer::Fatline, Some(Developer::Understudy), true),
    Champion => Role::new(25, "Champion", Some(RoleType::Builder), HashMap::from([(Language::English, "Champion".to_string())]), Developer::Luminous, None, true),
    Elementalist => Role::new(26, "Elementalist", Some(RoleType::Support), HashMap::from([(Language::English, "Elementalist".to_string())]), Developer::Fatline, Some(Developer::Sox), true),
    Brakk => Role::new(27, "Brakk", Some(RoleType::Hunter), HashMap::from([(Language::English, "Brakk".to_string())]), Developer::Fatline, Some(Developer::Gcloud), true),
    Glevig => Role::new(28, "Glevig", Some(RoleType::Defender), HashMap::from([(Language::English, "Glevig".to_string())]), Developer::Fatline, Some(Developer::Understudy), true),
    Delta_Squad => Role::new(29, "Delta_Squad", Some(RoleType::Support), HashMap::from([(Language::English, "Delta Squad".to_string())]), Developer::Luminous, None, true),
    Phaegore => Role::new(30, "Phaegore", Some(RoleType::Defender), HashMap::from([(Language::English, "Phaegore".to_string())]), Developer::Templar, Some(Developer::Andrene), true),
    Alarak => Role::new(31, "Alarak", Some(RoleType::Builder), HashMap::from([(Language::English, "Alarak".to_string())]), Developer::Luminous, None, true),
    Izsha => Role::new(32, "Izsha", Some(RoleType::Defender), HashMap::from([(Language::English, "Izsha".to_string())]), Developer::Susu, None, true),
    Malus => Role::new(33, "Malus", Some(RoleType::Hunter), HashMap::from([(Language::English, "Malus".to_string())]), Developer::Susu, None, true),
    Kraith => Role::new(34, "Kraith", Some(RoleType::Hunter), HashMap::from([(Language::English, "Kraith".to_string())]), Developer::Templar, None, true),
    Energizer => Role::new(35, "Energizer", Some(RoleType::Builder), HashMap::from([(Language::English, "Energizer".to_string())]), Developer::Fatline, Some(Developer::Sox), true),
    Andor => Role::new(36, "Andor", Some(RoleType::Builder), HashMap::from([(Language::English, "Andor".to_string())]), Developer::Korneel, None, true),
    DJ => Role::new(37, "DJ", Some(RoleType::Builder), HashMap::from([(Language::English, "DJ".to_string())]), Developer::Sox, None, true),
    Rattlesnake => Role::new(38, "Rattlesnake", Some(RoleType::Support), HashMap::from([(Language::English, "Rattlesnake".to_string())]), Developer::Legacy, Some(Developer::Andrene), true),
    SgtHammer => Role::new(39, "SgtHammer", Some(RoleType::Builder), HashMap::from([(Language::English, "SgtHammer".to_string())]), Developer::Archlei, Some(Developer::Hex), true),
    Chew => Role::new(40, "Chew", Some(RoleType::Support), HashMap::from([(Language::English, "Chew".to_string())]), Developer::Sox, None, true),
    Aewyn => Role::new(41, "Aewyn", Some(RoleType::Builder), HashMap::from([(Language::English, "Aewyn".to_string())]), Developer::Luminous, None, true),
    Critter_Lord => Role::new(42, "Critter_Lord", Some(RoleType::Builder), HashMap::from([(Language::English, "Critter Lord".to_string())]), Developer::Korneel, None, true),
    Nightingale => Role::new(43, "Nightingale", Some(RoleType::Support), HashMap::from([(Language::English, "Nightingale".to_string())]), Developer::Luminous, None, true),
    Sjlerk => Role::new(44, "Sjlerk", Some(RoleType::Support), HashMap::from([(Language::English, "Sjlerk".to_string())]), Developer::Korneel, None, true),
    Sophia => Role::new(45, "Sophia", Some(RoleType::Builder), HashMap::from([(Language::English, "Sophia".to_string())]), Developer::Korneel, Some(Developer::TheePotatoLord), true),
    Jinara => Role::new(46, "Jinara", Some(RoleType::Builder), HashMap::from([(Language::English, "Jinara".to_string())]), Developer::Fern, None, true),
    Sir_Roachington => Role::new(47, "Sir_Roachington", Some(RoleType::Defender), HashMap::from([(Language::English, "Sir Roachington".to_string())]), Developer::TheePotatoLord, None, true),
    Raszagal => Role::new(48, "Raszagal", Some(RoleType::Builder), HashMap::from([(Language::English, "Raszagal".to_string())]), Developer::Understudy, None, false),
);

impl Index<&str> for Roles {
    type Output = Role;

    fn index(&self, index: &str) -> &Self::Output {
        &self._roles[index]
    }
}


impl IntoIterator for Roles {
    type IntoIter = RolesIntoIter;
    type Item = Role;
    
    fn into_iter(self) -> Self::IntoIter {
        RolesIntoIter {
            roles: self,
            index: 0
        }
    }
    
}

pub struct RolesIntoIter {
    roles: Roles,
    index: usize
}

impl Iterator for RolesIntoIter {
    type Item = Role;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index == self.roles._raw_list.len() {
            return None;
        }
        let res = self.roles._raw_list[self.index].clone();
        self.index +=1;
        Some(res)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_iter_order() {
        let roles = Roles::new();
        let roles2 = Roles::new();
        let mut i = 0;
        for role in roles {
            assert!(role == roles2._raw_list[i]);
            i += 1;
        }
    }

    #[test]
    fn select_role_by_name_validate_order() {
        let roles = Roles::new();
        let roles2 = Roles::new();
        for role in roles {
            assert!(&roles2[&role.name] == roles2.from_index(role._id));
        }
    }

    #[test]
    fn select_role_direct() {
        let roles = Roles::new();
        assert!(&roles.Scientist == &roles["Scientist"]);
        assert!(&roles.Warfield == &roles["Warfield"]);
    }
}

#[pyclass]
struct PyRolesIter {
    inner: RolesIntoIter,
}

#[pymethods]
impl PyRolesIter {
    fn __iter__(slf: PyRef<Self>) -> PyRef<Self> {
        slf
    }

    fn __next__(mut slf: PyRefMut<Self>) -> Option<Role> {
        slf.inner.next()
    }
}

#[pymethods]
impl Role {
    #[pyo3(name = "get_index")]
    pub fn py_get_index(&self) -> PyResult<usize> {
        Ok(self.get_index())
    }

    #[pyo3(name = "get_team")]
    pub fn py_get_team(&self) -> PyResult<Team> {
        if self.name != "Random" {
            Ok(self.get_team())
        } else {
            Err(PyErr::new::<PyTypeError, _>("Role Random does not have a team."))
        }
    }

    #[pyo3(name = "get_role_type")]
    pub fn py_get_role_type(&self) -> PyResult<Option<RoleType>> {
        Ok(self.get_role_type())
    }

    #[pyo3(name = "get_name", signature = (language=None))]
    pub fn py_get_name(&self, language: Option<Language>) -> PyResult<String> {
        match language {
            Some(lang) => Ok(self.get_name(lang)),
            None => Ok(self.get_english_name()),
        }
    }

    #[pyo3(name = "get_original_author")]
    pub fn py_get_original_author(&self) -> PyResult<Developer> {
        Ok(self.get_original_author())
    }

    #[pyo3(name = "get_current_author")]
    pub fn py_get_current_author(&self) -> PyResult<Developer> {
        Ok(self.get_current_author())
    }

    #[pyo3(name = "is_available")]
    pub fn py_is_available(&self) -> PyResult<bool> {
        Ok(self.is_available())
    }
}