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

use crate::core::prelude::{DecrementMut, IncrementMut};
use crate::engine::{ComputationalEngine, WolframUTM};
use crate::types::{Rulespace, Snapshot};
use eryon::{Direction, Tail};
use rstmt::nrt::LPR;

impl Plant<WolframUTM> {
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip_all, level = "trace", target = "plant")
    )]
    /// execute the given program on the plant, returning the history of states and tapes
    pub fn run(
        &mut self,
        input: Vec<usize>,
    ) -> crate::Result<Vec<Snapshot<usize, [usize; 3], Vec<usize>>>> {
        self.run_for(input, usize::MAX)
    }
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip_all, level = "trace", target = "plant")
    )]
    /// execute the given program on the plant, returning the history of states and tapes
    pub fn run_for(
        &mut self,
        input: Vec<usize>,
        max: usize,
    ) -> crate::Result<Vec<Snapshot<usize, [usize; 3], Vec<usize>>>> {
        // Initialize the tape with the input
        self.engine_mut().set_tape(input);
        // Reset the position
        self.engine_mut().set_position(0);

        let mut history = Vec::new();
        // record initial state
        history.push(self.engine().snapshot().cloned());
        #[cfg(feature = "tracing")]
        tracing::info!("Initial configuration: {:?}", history.first().unwrap());
        // initialize the halt flag
        let mut halt = false;
        // execute the program until the machine halts
        while let Ok(true) = self.step().map(|b| !halt && b) {
            #[cfg(feature = "tracing")]
            tracing::info!("Current configuration: {}", self.engine());
            // Record each state after a step
            history.push(self.engine().snapshot().cloned());

            // prevent infinite loops
            if history.len() > max {
                halt = true;
            }
        }

        Ok(history)
    }
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip_all, level = "trace", target = "plant")
    )]
    /// execute the given program on the plant, returning the history of states and tapes
    pub fn run_while<F>(
        &mut self,
        input: Vec<usize>,
        f: F,
    ) -> crate::Result<Vec<Snapshot<usize, [usize; 3], Vec<usize>>>>
    where
        F: Fn(&Self) -> bool,
    {
        // re-initialize the engine's tape & reset the position to `0`
        self.engine_mut().reset_with_tape(input);

        let mut history = Vec::new();
        #[cfg(feature = "tracing")]
        tracing::info!("Initial configuration: {:?}", self.engine().snapshot());
        // Record initial state
        history.push(self.engine().snapshot().cloned());

        let mut halt = false;
        // Run until the machine halts (i.e., step() returns false)
        while let Ok(true) = self.step().map(|res| !halt && res) {
            #[cfg(feature = "tracing")]
            tracing::info!("Current configuration: {}", self.engine());
            // Record each state after a step
            history.push(self.engine().snapshot().cloned());

            // halt on
            if !f(self) {
                halt = true;
                // self.engine_mut().set_state(self.engine().state().halt());
            }
        }

        Ok(history)
    }
    #[cfg_attr(
        feature = "tracing",
        tracing::instrument(skip_all, level = "trace", target = "plant")
    )]
    /// execute a single step of the plant's UTM, returning true if the step was successful
    pub fn step(&mut self) -> crate::Result<bool> {
        let position = self.position();
        let head = self.head();

        // verify that the current symbol is within the plant's headspace
        if !self.contains(&head.symbol) {
            // todo: implement a more sophisticated motion planning algorithm
            for t in LPR::iter() {
                if self.headspace().transform(t).contains(head.symbol()) {
                    #[cfg(feature = "tracing")]
                    tracing::trace!("Transforming plant using {t:?}");
                    self.transform_mut(t);
                    break;
                }
            }
            // if the the symbol isn't found in one of the neighboring triads,
            // then try and use the pathfinding algorithm to find a path to the symbol
            if !self.contains(&head.symbol) {
                let mut paths = self
                    .headspace()
                    .path_finder()
                    .find_paths_to_target(head.symbol);
                if paths.is_empty() {
                    return Err(crate::ActorError::UnknownSymbol);
                }
                // sort the paths by length
                paths.sort_by_key(|a| a.path().len());
                // inplace, walk the plant to the another instance that includes the symbol
                self.walk_mut(paths[0].path());
            }
        }

        // Find the transition rule for the current state and symbol
        if let Some(&tail) = self.engine().ruleset().get(&head) {
            let Tail {
                state: new_state,
                symbol: new_symbol,
                direction,
            } = tail;
            // Update the tape
            if position >= self.engine.tape().len() {
                self.engine.tape_mut().resize(position + 1, 0);
            }
            self.engine.tape_mut()[position] = new_symbol;

            // Update state
            self.engine.set_state(new_state);

            // Move the head
            #[cfg(feature = "tracing")]
            tracing::trace!("Shifting the head {direction:?}");
            match direction {
                Direction::Left => {
                    if position > 0 {
                        self.engine.position_mut().decrement_mut();
                    } else {
                        self.tape_mut().insert(0, 0);
                    }
                }
                Direction::Right => {
                    self.engine.position_mut().increment_mut();
                    if self.position() >= self.tape().len() {
                        self.tape_mut().push(0);
                    }
                }
                Direction::Stay => {}
            }

            Ok(true) // Step successful
        } else {
            // No rule found - machine halts
            Ok(false)
        }
    }
    /// set the ruleset of the machine
    pub fn set_ruleset(&mut self, ruleset: Rulespace<usize, usize>) {
        self.engine_mut().set_ruleset(ruleset);
    }
    /// consumes this instance to create another with the given ruleset
    pub fn with_ruleset(self, ruleset: Rulespace<usize, usize>) -> Self {
        Self {
            engine: self.engine.with_ruleset(ruleset),
            ..self
        }
    }
}
