use core::borrow::Borrow;

use crate::{config::TriadsConfig, triad::{Fault, Frame, Means, Need, Triad}};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Personality
{
    pub frame: Frame,
    pub strategy: Means,
    pub fault: Fault,
    pub need: Need
}

impl Personality
{
    pub fn affirmation(&self, f: &mut core::fmt::Formatter, config: &dyn Borrow<TriadsConfig>) -> core::fmt::Result
    {
        let frame = self.frame.config(config);
        let frame = frame.affirmation.as_ref();

        let fault = self.fault.config(config);
        let fault = fault.affirmation.as_ref();

        let strategy = self.strategy.config(config);
        let strategy = strategy.affirmation.as_ref();

        write!(f, "i will {frame}, {fault}, and {strategy}.")
    }

    pub fn from_triads(triads: [&dyn Triad; 4]) -> Self
    {
        let (frame, strategy, fault, need) = triads.into_iter()
            .map(|triad| {
                let any = triad.as_any();
                any.downcast_ref()
                    .map(|frame| (Some(frame), None, None, None))
                    .or_else(|| any.downcast_ref()
                        .map(|strategy| (None, Some(strategy), None, None))
                    ).or_else(|| any.downcast_ref()
                        .map(|fault| (None, None, Some(fault), None))
                    ).or_else(|| any.downcast_ref()
                        .map(|need| (None, None, None, Some(need)))
                    ).expect("The triad type is not r{ecognized. Something must have gone horribly wrong!")
            }).reduce(|lhs, rhs| (
                lhs.0.map(Some).xor(rhs.0.map(Some)).map(|inner| inner.expect("Unsound personality. Conflicting personality frame")),
                lhs.1.map(Some).xor(rhs.1.map(Some)).map(|inner| inner.expect("Unsound personality. Conflicting personality strategy")),
                lhs.2.map(Some).xor(rhs.2.map(Some)).map(|inner| inner.expect("Unsound personality. Conflicting personality fault")),
                lhs.3.map(Some).xor(rhs.3.map(Some)).map(|inner| inner.expect("Unsound personality. Conflicting personality need")),
            )).expect("Unsound personality, No triads given. Impossible!");
        Personality {
            frame: *frame.expect("Unsound personality. Undefined personality frame"),
            strategy: *strategy.expect("Unsound personality. Undefined personality strategy"),
            fault: *fault.expect("Unsound personality. Undefined personality fault"),
            need: *need.expect("Unsound personality. Undefined personality need")
        }
    }
}