/*
    Appellation: impl_plant <module>
    Contrib: @FL03
*/
use super::Plant;

use crate::drivers::{Driver, RawDriver, TriadDriver};
use crate::engine::{ComputationalEngine, Engine, RawEngine};
use crate::traits::RawStore;
use eryon::{Head, State};
use rstmt::nrt::{LPR, Triad};

impl<E> Plant<E>
where
    E: ComputationalEngine<usize, [usize; 3]>,
    E::Store: RawStore<Elem = usize>,
{
    /// returns a copy of the plant's current head
    pub fn head(&self) -> Head<usize, usize> {
        self.engine.head().copied()
    }
    /// returns the current position of the plant's UTM
    #[inline]
    pub fn position(&self) -> usize {
        self.engine.position()
    }
    /// returns a copy of the plant's current state
    #[inline]
    pub fn state(&self) -> State<usize> {
        self.engine.state().copied()
    }
    /// returns a mutable reference to the plant's state
    #[inline]
    pub fn state_mut(&mut self) -> State<&mut usize> {
        self.engine.state_mut()
    }
    /// returns an immutable reference to the plant's tape
    #[inline]
    pub fn tape(&self) -> &<E as RawEngine>::Store {
        self.engine.tape()
    }
    /// returns a mutable reference to the plant's tape
    #[inline]
    pub fn tape_mut(&mut self) -> &mut <E as RawEngine>::Store {
        self.engine.tape_mut()
    }
    /// applies the given transformation to the plant's headspace, modifying the utm accordingly
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip(self), name = "step", target = "plant")
    )]
    pub fn transform(&self, transform: LPR) -> Self
    where
        E: Clone,
    {
        let mut engine = self.engine.clone();
        // transform the triad
        let triad = self.headspace().transform(transform);
        // update the utm's alphabet
        engine.set_alphabet(triad.notes());
        Plant {
            headspace: triad,
            engine,
        }
    }
    /// mutably applies the given transformation to the plant's headspace, modifying the utm
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip(self), level = "trace", name = "transform_mut", target = "plant")
    )]
    pub fn transform_mut(&mut self, transform: LPR) -> &mut Self {
        self.headspace_mut().transform_inplace(transform);
        self.sync_headspace()
    }
    /// mutably walk the plant's headspace along the given path
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip(self, path), level = "trace", name = "walk_mut" target = "plant")
    )]
    pub fn walk_mut<'a, I>(&mut self, path: I) -> &mut Self
    where
        I: IntoIterator<Item = &'a LPR>,
    {
        self.headspace_mut().walk_inplace(path.into_iter().copied());
        self.sync_headspace()
    }
    /// synchronize the plant's headspace with the engine; typically used to update the plant
    /// after a transformation has been applied
    pub fn sync_headspace(&mut self) -> &mut Self {
        let actual = self.headspace().notes();
        self.engine_mut().set_alphabet(actual);
        self
    }
}

impl<E> RawDriver<Triad> for Plant<E>
where
    E: RawEngine,
{
    type Engine = E;

    seal!();

    fn engine(&self) -> &E {
        &self.engine
    }

    fn engine_mut(&mut self) -> &mut E {
        &mut self.engine
    }

    fn headspace(&self) -> &Triad {
        &self.headspace
    }

    fn headspace_mut(&mut self) -> &mut Triad {
        &mut self.headspace
    }
}

impl<E> Driver<Triad> for Plant<E>
where
    E: ComputationalEngine<usize, [usize; 3]> + Engine,
    E::Store: RawStore<Elem = usize>,
{
    fn from_headspace(headspace: Triad) -> Self {
        Plant::from_headspace(headspace)
    }
}

impl<E> TriadDriver for Plant<E>
where
    E: ComputationalEngine<usize, [usize; 3]> + Engine,
    E::Store: RawStore<Elem = usize>,
{
    fn transform(&mut self, transform: LPR) -> Result<(), crate::ActorError> {
        self.transform_mut(transform);
        Ok(())
    }

    fn walk<'a, I>(&mut self, path: I) -> Result<(), crate::ActorError>
    where
        I: IntoIterator<Item = &'a LPR>,
    {
        self.walk_mut(path);
        Ok(())
    }
}
