use std::fmt;

use crate::instruction::Head;
use crate::program::Program;
use crate::state::Configuration;
use crate::{Symbol, TuringMachine, With};

/// [`Classic`] is a common [`TuringMachine`] realization that can be used
/// freely for program execution.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Classic<S: Symbol> {
    default: S,
    program: Program<S>,
}

impl<S: Symbol> Classic<S> {
    /// Constructs a new [`Classic`] Turing machine from the program
    /// [`Program`] and the default symbol [`Symbol`].
    ///
    /// Returns [`Ok(Classic)`] when the default symbol is in the program
    /// alphabet otherwise [`Err(String)`] with diagnostic information.
    ///
    /// # Examples
    /// Trying to return the new machine with a mismatched default symbol:
    /// ```rust
    /// use turing_machine_rs::instruction::State;
    /// use turing_machine_rs::machines::Classic;
    /// use turing_machine_rs::program::Program;
    ///
    /// let program = Program::new(vec!['0', '1'], State(1));
    /// let machine = Classic::new(program, '!');
    ///
    /// assert!(machine.is_err());
    /// ```
    ///
    /// Successful creation:
    /// ```rust
    /// use turing_machine_rs::instruction::State;
    /// use turing_machine_rs::machines::Classic;
    /// use turing_machine_rs::program::Program;
    ///
    /// let program = Program::new(vec!['0', '1'], State(1));
    /// let machine = Classic::new(program, '0');
    ///
    /// assert!(machine.is_ok());
    /// ```
    pub fn new(program: Program<S>, default: S) -> Result<Self, String> {
        match program.alphabet().contains(&default) {
            true => Ok(Classic { program, default }),
            false => Err(format!(
                "new error: default symbol {} is not in alphabet {:?}",
                default,
                program.alphabet()
            )),
        }
    }
}

impl<S: Symbol> TuringMachine<S> for Classic<S> {
    /// Executes [`Configuration`] once by mutation.
    ///
    /// Returns [`Ok(Configuration)`] when an [`crate::instruction::Instruction`]
    /// exists for the current [`Configuration`] symbol and state.
    /// And otherwise returns [`Err(String)`] with diagnostic information.
    fn execute_once(&self, mut conf: Configuration<S>) -> Result<Configuration<S>, String> {
        let head = Head::new(conf.state, conf.get_symbol().clone());
        let inst = match self.program.get(&head)? {
            Some(inst) => inst,
            None => {
                return Err(format!(
                    "uncovered case: have no tail for head ({}) in program",
                    head
                ))
            }
        };
        conf.state = inst.tail.state;
        conf.set_symbol(inst.tail.symbol.clone());
        conf.shift(inst.tail.movement, self.default.clone());
        Ok(conf)
    }

    /// Executes [`Configuration`] until predicate is `false` by mutation.
    ///
    /// Returns [`Ok(Configuration)`] when an [`crate::instruction::Instruction`]
    /// exists for the current [`Configuration`] symbol and state.
    /// And otherwise returns [`Err(String)`] with diagnostic information.
    fn execute_until(
        &self,
        mut conf: Configuration<S>,
        until: impl Fn(&Configuration<S>) -> bool,
    ) -> Result<Configuration<S>, String> {
        while !until(&conf) {
            let head = Head::new(conf.state, conf.get_symbol().clone());
            let inst = match self.program.get(&head)? {
                Some(inst) => inst,
                None => {
                    return Err(format!(
                        "uncovered case: have no tail for head ({}) in program",
                        head
                    ))
                }
            };
            conf.state = inst.tail.state;
            conf.set_symbol(inst.tail.symbol.clone());
            conf.shift(inst.tail.movement, self.default.clone());
        }
        Ok(conf)
    }
}

impl<S: Symbol> With<Classic<S>> for Classic<S> {
    type Output = Result<Classic<S>, String>;

    /// Makes superposition with two or more [`Classic`] machines by chain.
    /// This method accept only [`Classic`] struct and can be used only for
    /// another [`Classic`] machine.
    ///
    /// Returns a new [`Ok(Classic)`] when machines can be concatenated
    /// and [`Err(String)`] with diagnostic information when machines
    /// have different alphabets or default symbols.
    fn with(&self, other: &Classic<S>) -> Self::Output {
        if self.default != other.default {
            return Err(format!(
                "with error: classic machines have different default symbols: {} and {}",
                self.default, other.default,
            ));
        }
        // `Program::with` implementation guarantees that program can
        // be concatenated only with the same alphabet
        let program = self.program.with(&other.program)?;
        Classic::new(program, self.default.clone())
    }
}

impl<S: Symbol> With<Classic<S>> for Result<Classic<S>, String> {
    type Output = Result<Classic<S>, String>;

    /// Makes superposition with two or more [`Classic`] machines by chain.
    /// This method accept only [`Classic`] struct and can be used only for
    /// [`Result<Classic, String>`].
    ///
    /// Returns a new [`Ok(Classic)`] when `self` is [`Result::Ok`] and machines
    /// can be concatenated and [`Err(String)`] when `self` is [`Result::Ok`]
    /// but machines have different alphabets or default symbols.
    ///
    /// And Returns a copy of [`Err(String)`] when `self` is [`Result::Err`].
    fn with(&self, other: &Classic<S>) -> Self::Output {
        match self {
            Ok(machine) => machine.with(other),
            Err(msg) => Err(msg.clone()),
        }
    }
}

impl<S: Symbol> fmt::Display for Classic<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        use std::any::type_name;

        write!(
            f,
            "Classic<{}> {{ program: {} }}",
            type_name::<S>(),
            self.program
        )
    }
}