mod instruction;
mod output;
mod turing;
mod warnings;
use std::{borrow::Cow, collections::HashMap};
pub use instruction::TuringInstruction;
pub use output::TuringOutput;
use pest::Parser;
use serde::{Deserialize, Serialize};
pub use turing::{Rule, TuringMachine, TuringParser};
pub use warnings::{CompilerError, CompilerWarning, ErrorPosition};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Library {
    pub name: Cow<'static, str>,
    pub description: Cow<'static, str>,
    pub initial_state: Cow<'static, str>,
    pub final_state: Cow<'static, str>,
    pub used_states: Cow<'static, [Cow<'static, str>]>,
    pub code: Cow<'static, str>,
}
impl Library {
    pub fn get_instructions(
        &self,
    ) -> Result<HashMap<(String, bool), TuringInstruction>, CompilerError> {
        let mut instructions: HashMap<(String, bool), TuringInstruction> = HashMap::new();
        let file = match TuringParser::parse(Rule::instructions, self.code.as_ref()) {
            Ok(mut f) => f.next().unwrap(),
            Err(e) => panic!("{}", e),
        };
        for record in file.into_inner() {
            let tmp = match TuringInstruction::from(record.into_inner()) {
                Ok(i) => i,
                Err(e) => return Err(e),
            };
            instructions.insert(
                (tmp.from_state.clone(), tmp.from_value.clone()),
                tmp.clone(),
            );
        }
        Ok(instructions)
    }
}
pub const LIBRARIES: [Library; 5] = [
    Library {
        name: Cow::Borrowed("sum"),
        description: Cow::Borrowed("x + y"),
        initial_state: Cow::Borrowed("q0"),
        final_state: Cow::Borrowed("q2"),
        used_states: Cow::Borrowed(&[
            Cow::Borrowed("q0"),
            Cow::Borrowed("q1"),
            Cow::Borrowed("q2"),
        ]),
        code: Cow::Borrowed(include_str!("./composition/sum.tm")),
    },
    Library {
        name: Cow::Borrowed("x2"),
        description: Cow::Borrowed("x * 2"),
        initial_state: Cow::Borrowed("q0"),
        final_state: Cow::Borrowed("qf"),
        used_states: Cow::Borrowed(&[
            Cow::Borrowed("q0"),
            Cow::Borrowed("q1"),
            Cow::Borrowed("q2"),
            Cow::Borrowed("q3"),
            Cow::Borrowed("q4"),
            Cow::Borrowed("q5"),
            Cow::Borrowed("qf"),
        ]),
        code: Cow::Borrowed(include_str!("./composition/duplicate.tm")),
    },
    Library {
        name: Cow::Borrowed("mod"),
        description: Cow::Borrowed("x mod y"),
        initial_state: Cow::Borrowed("q0"),
        final_state: Cow::Borrowed("qf"),
        used_states: Cow::Borrowed(&[
            Cow::Borrowed("q0"),
            Cow::Borrowed("q1"),
            Cow::Borrowed("q2"),
            Cow::Borrowed("q2"),
            Cow::Borrowed("q4"),
            Cow::Borrowed("q5"),
            Cow::Borrowed("q5"),
            Cow::Borrowed("q6"),
            Cow::Borrowed("q7"),
            Cow::Borrowed("q8"),
            Cow::Borrowed("q9"),
            Cow::Borrowed("q10"),
            Cow::Borrowed("q11"),
            Cow::Borrowed("qf"),
        ]),
        code: Cow::Borrowed(include_str!("./composition/mod.tm")),
    },
    Library {
        name: Cow::Borrowed("div2"),
        description: Cow::Borrowed("x div 2"),
        initial_state: Cow::Borrowed("q0"),
        final_state: Cow::Borrowed("qf"),
        used_states: Cow::Borrowed(&[
            Cow::Borrowed("q0"),
            Cow::Borrowed("q1"),
            Cow::Borrowed("q2"),
            Cow::Borrowed("qf"),
        ]),
        code: Cow::Borrowed(include_str!("./composition/div2.tm")),
    },
    Library {
        name: Cow::Borrowed("bound_diff"),
        description: Cow::Borrowed("x ∸ y"),
        initial_state: Cow::Borrowed("q0"),
        final_state: Cow::Borrowed("qf"),
        used_states: Cow::Borrowed(&[
            Cow::Borrowed("q0"),
            Cow::Borrowed("q1"),
            Cow::Borrowed("q2"),
            Cow::Borrowed("q3"),
            Cow::Borrowed("q4"),
            Cow::Borrowed("q5"),
            Cow::Borrowed("q6"),
            Cow::Borrowed("qf"),
        ]),
        code: Cow::Borrowed(include_str!("./composition/bound_diff.tm")),
    },
];
#[cfg(test)]
mod test_parsing {
    use std::fs;
    use crate::warnings::ErrorPosition;
    use crate::CompilerError;
    use crate::Rule;
    use crate::TuringMachine;
    use crate::TuringParser;
    use pest::{consumes_to, parses_to};
    #[test]
    fn parse_description() {
        let test = "/// a + b\r\n";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::description,
            tokens: [
                description(0, 11),
            ]
        }
    }
    #[test]
    fn parse_tape_valid() {
        let test = "{111011};";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::tape,
            tokens: [
                tape(0, 9, [
                    value(1, 2),
                    value(2, 3),
                    value(3, 4),
                    value(4, 5),
                    value(5, 6),
                    value(6, 7),
                ]),
            ]
        }
    }
    #[test]
    fn parse_tape_zeros() {
        let test = "
        {000};
        I = {q0};
        F = {q2};
        
        (q0, 1, 0, R, q1);
        
        (q1, 1, 1, R, q1);
        (q1, 0, 0, R, q2);
        
        (q2, 1, 0, H, q2);
        (q2, 0, 0, H, q2);
        ";
        let tm_error = TuringMachine::new(test);
        let expected: CompilerError = CompilerError::SyntaxError {
            position: ErrorPosition::new((1, 9), None), message: String::from("Expected at least a 1 in the tape"),
            code: String::from("000"),
            expected: Rule::tape,
            found: None,
        };
        assert_eq!(tm_error.unwrap_err(), expected);
    }
    #[test]
    fn parse_initial_state() {
        let test = "I = {q0};";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::initial_state,
            tokens: [
                initial_state(0, 9, [
                    state(5, 7)
                ])
            ]
        }
    }
    #[test]
    fn parse_final_state() {
        let test = "F = {q2};";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::final_state,
            tokens: [
                final_state(0, 9, [
                    state(5, 7)
                ])
            ]
        }
    }
    #[test]
    fn parse_instruction() {
        let test = "(q0, 1, 0, R, q1);";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::instruction,
            tokens: [
                instruction(0, 18, [
                    state(1, 3),
                    value(5, 6),
                    value(8, 9),
                    movement(11, 12),
                    state(14, 16)
                ]),
            ]
        }
    }
    #[test]
    fn parse_file() {
        let unparsed_file = fs::read_to_string("Examples/Example1.tm").expect("cannot read file");
        let (tm, _) = match TuringMachine::new(&unparsed_file) {
            Ok(t) => t,
            Err(e) => {
                TuringMachine::handle_error(e);
                std::process::exit(1);
            }
        };
        assert_eq!(
            tm.to_string(),
            "0 0 0 1 1 1 1 1 0 1 1 \n      ^               "
        )
    }
}
#[cfg(test)]
mod test_composition {
    use crate::Rule;
    use crate::TuringMachine;
    use crate::TuringOutput;
    use crate::TuringParser;
    use crate::LIBRARIES;
    use pest::{consumes_to, parses_to};
    #[test]
    fn parse_composition_function_name_valid() {
        let test = "sum_test";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::function_name,
            tokens: [
                function_name(0, 8)
            ]
        }
    }
    #[test]
    fn parse_composition_valid() {
        let test = "compose = { sum_test };";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::composition,
            tokens: [
                composition(0, 23, [
                    function_name(12, 20)
                ])
            ]
        }
    }
    #[test]
    fn parse_multiple_compositions() {
        let test = "compose = {sum, diff};";
        parses_to! {
            parser: TuringParser,
            input: test,
            rule: Rule::composition,
            tokens: [
                composition(0, 22, [
                    function_name(11, 14),
                    function_name(16, 20)
                ])
            ]
        }
    }
    #[test]
    fn libraries() {
        for lib in LIBRARIES {
            let _ = lib.get_instructions();
        }
    }
    #[test]
    fn composition() {
        let test = "
        compose = {sum};
        
        F = {q2};
        {111011};
        I = {q0};
        ";
        let mut tm = match TuringMachine::new(test) {
            Ok(t) => t.0,
            Err(e) => {
                println!("{:?}", e);
                std::process::exit(1);
            }
        };
        assert_eq!(tm.final_result(), TuringOutput::Defined((6, 3)));
        assert_eq!(
            tm.to_string(),
            "0 0 0 0 1 1 0 0 1 0 0 \n              ^       "
        );
    }
}