enneagram 1.3.0

A digital enneagram
use core::{any::Any, fmt::Debug};

use crate::{
    config::{TriadConfig, TriadsConfig, Fallback, Property},
    enneatype::Enneatype
};

moddef::moddef!(
    flat(pub) mod {
        fault,
        frame,
        need,
        means
    }
);

#[cfg(test)]
pub fn triangulate(edges: &[Enneatype; 3]) -> Box<dyn Triad>
{
    let [triad] = core::iter::empty()
        .chain(
            Fault::all()
                .into_iter()
                .filter(|triad| triad.edges() == edges)
                .map(|triad| Box::new(triad) as Box<dyn Triad>)
        )
        .chain(
            Frame::all()
                .into_iter()
                .filter(|triad| triad.edges() == edges)
                .map(|triad| Box::new(triad) as Box<dyn Triad>)
        )
        .chain(
            Need::all()
                .into_iter()
                .filter(|triad| triad.edges() == edges)
                .map(|triad| Box::new(triad) as Box<dyn Triad>)
        )
        .chain(
            Means::all()
                .into_iter()
                .filter(|triad| triad.edges() == edges)
                .map(|triad| Box::new(triad) as Box<dyn Triad>)
        )
        .collect::<Vec<_>>()
        .try_into()
        .expect("Exactly one triad should match the set of three edges.");
    triad
}

pub fn all() -> [Box<dyn Triad>; 4 * 3]
{
    core::iter::empty()
        .chain(Fault::all().into_iter().map(|triad| Box::new(triad) as Box<dyn Triad>))
        .chain(Frame::all().into_iter().map(|triad| Box::new(triad) as Box<dyn Triad>))
        .chain(Need::all().into_iter().map(|triad| Box::new(triad) as Box<dyn Triad>))
        .chain(Means::all().into_iter().map(|triad| Box::new(triad) as Box<dyn Triad>))
        .collect::<Vec<_>>()
        .try_into()
        .expect("The enneagram is defined by 4 triads each consisting of 3 states, 12 in total. Wrong number of states!")
}

pub trait Triad: Debug + Any
{
    fn as_any(&self) -> &dyn Any;
    fn equals(&self, other: &dyn Triad) -> bool;

    fn edges(&self) -> &'static [Enneatype; 3];
    fn config<'a>(&self, config: &'a dyn Property<TriadsConfig>, fallback: &'a Fallback) -> TriadConfig<'a>;
    fn kind<'a>(&self, config: &'a dyn Property<TriadsConfig>, fallback: &'a Fallback) -> &'a str;

    fn lines(&self) -> [[Enneatype; 2]; 3]
    {
        let edges @ &[.., mut prev] = self.edges();
        edges.map(|edge| [core::mem::replace(&mut prev, edge), edge])
    }
}

#[cfg(test)]
mod test
{
    use crate::{
        config::{Config, Fallback},
        triad::{Fault, Frame, Means, Need, Triad}
    };

    #[test]
    fn test_frame()
    {
        test_triads(&Frame::all());
    }
    #[test]
    fn test_strategy()
    {
        test_triads(&Means::all());
    }
    #[test]
    fn test_fault()
    {
        test_triads(&Fault::all());
    }
    #[test]
    fn test_need()
    {
        test_triads(&Need::all());
    }

    #[test]
    fn test_all()
    {
        for triad in &crate::triad::all()
        {
            test_triad(&**triad);
        }
    }

    fn test_triad<T>(triad: &T)
    where
        T: Triad + ?Sized
    {
        let config = Config::default();
        let fallback = Fallback::default();
        let conf = triad.config(&config, &fallback);
        println!("Q: {}\nA: {}\n", conf.expression, conf.reflection);
        let edges = triad.edges();
        let reconstruction = crate::triad::triangulate(edges);
        assert!(triad.equals(&*reconstruction), "Triad must match its own reconstruction!")
    }

    fn test_triads<'a, T>(triads: impl IntoIterator<Item = &'a T>)
    where
        T: Triad
    {
        for triad in triads
        {
            test_triad(triad);
        }
    }
}