pub mod masking;
pub mod transition;
pub mod builder;
pub mod run;

use graphviz_rust::{
    cmd::Layout, dot_structures::{Attribute, Edge, EdgeTy, Graph, Id, NodeId, Stmt, Vertex}, exec, printer::PrinterContext
};
use transition::FSMTranistion;
use std::{cell::RefCell, rc::Rc};

pub type FSMStateRef = Rc<RefCell<FSMState>>;

pub fn new_state_ref(s: FSMState) -> FSMStateRef {
    Rc::new(RefCell::new(s))
}

#[derive(Debug, Clone)]
pub struct FSM {
    name: String,
    states: Vec<FSMStateRef>,
    start: Option<FSMStateRef>
}

impl FSM {
    pub fn new(state: Vec<FSMStateRef>, name: &str) -> Self {
        FSM {
            states: state,
            name: name.to_string(),
            start: None
        }
    }

    fn render_stmts(&self) -> Vec<Stmt> {
        let mut stmts: Vec<Stmt> = vec![Stmt::Attribute(Attribute(
            Id::Plain("rankdir".to_string()),
            Id::Plain("LR".to_string()),
        ))];
        for s in &self.states {
            let s_borrowed = s.borrow();

            stmts.push(Stmt::Node(graphviz_rust::dot_structures::Node {
                id: NodeId(Id::Plain(s_borrowed.to_graph_label()), None),
                attributes: {
                    let mut res = vec![];
                    let label = s_borrowed.to_graph_label();

                    match s_borrowed.action {
                        FSMStateAction::None => {
                            res.push(Attribute(
                                Id::Plain("shape".to_string()),
                                Id::Plain("circle".to_string()),
                            ));
                        }
                        FSMStateAction::Error => {
                            res.push(Attribute(
                                Id::Plain("shape".to_string()),
                                Id::Plain("circle".to_string()),
                            ));
                        }
                        FSMStateAction::Reset => {
                            res.push(Attribute(
                                Id::Plain("shape".to_string()),
                                Id::Plain("circle".to_string()),
                            ));
                        }
                        FSMStateAction::Terminal => {
                            res.push(Attribute(
                                Id::Plain("shape".to_string()),
                                Id::Plain("doublecircle".to_string()),
                            ));
                        }
                    }

                    res.push(                        
                        Attribute(
                            Id::Plain("label".to_string()),
                            Id::Plain(label),
                        )
                    );

                    res
                },
            }));

            match &s_borrowed.transition {
                None => {}
                Some(t) => {
                    for (t, range) in t.to_targets_vec() {
                        match t.upgrade() {
                            None => {}
                            Some(target) => {
                                stmts.push(Stmt::Edge(Edge {
                                    ty: EdgeTy::Pair(
                                        Vertex::N(NodeId(
                                            Id::Plain(s_borrowed.to_graph_label()),
                                            None,
                                        )),
                                        Vertex::N(NodeId(
                                            Id::Plain(target.borrow().to_graph_label()),
                                            None,
                                        )),
                                    ),
                                    attributes: vec![Attribute(
                                        Id::Plain("label".to_string()),
                                        Id::Plain(format!("\"{}\"", range.to_string())),
                                    )],
                                }));
                            }
                        }
                    }
                }
            }
        }

        stmts
    }

    fn render_graph(&self, format: graphviz_rust::cmd::Format) -> Result<Vec<u8>, std::io::Error> {
        let g = Graph::DiGraph {
            id: Id::Plain("root".to_string()),
            strict: false,
            stmts: self.render_stmts(),
        };

        let mut ctx: graphviz_rust::printer::PrinterContext = PrinterContext::default();

        println!("{}", graphviz_rust::print(g.clone(), &mut ctx));

        let data = exec(g, &mut ctx, vec![
            format.into(),
            graphviz_rust::cmd::CommandArg::Layout(Layout::Dot)
        ])?;

        Ok(data)
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FSMStateAction {
    None,
    Terminal,
    Error,
    Reset
}

#[derive(Debug, Clone)]
pub struct FSMState {
    name: String,
    action: FSMStateAction,
    transition: Option<FSMTranistion>,
}

impl FSMState {
    pub fn new(name: String, action: FSMStateAction) -> Self {
        FSMState {
            name: name,
            action: action,
            transition: None,
        }
    }

    pub fn set_transitions(&mut self, t: FSMTranistion) {
        self.transition = Some(t);
    }

    pub fn to_graph_label(&self) -> String {
        return format!("\"{}\"", self.name);
    }

    pub fn to_graph_hash(&self) -> String {
        self.to_graph_label()
    }
}


#[cfg(test)]
mod test {
    use graphviz_rust::cmd::Format;
    use std::{cell::RefCell, io::Write, rc::Rc};
    use super::{FSMState, FSMStateAction, FSM};
    use super::transition::FSMTranistion;
    use super::masking::{MaskPresets, TransitionMask};

    pub fn debug_file_dump(d: Vec<u8>, filepath: String) {
        let mut output_file = std::fs::File::create(filepath).unwrap();
        output_file.write_all(&d).unwrap();
    }

    #[test]
    fn simple_fsm_render() {
        let z0 = Rc::new(RefCell::new(FSMState::new(
            "z0".to_string(),
            FSMStateAction::None,
        )));
        let z1 = Rc::new(RefCell::new(FSMState::new(
            "z1".to_string(),
            FSMStateAction::None,
        )));
        let z2 = Rc::new(RefCell::new(FSMState::new(
            "z2".to_string(),
            FSMStateAction::Terminal,
        )));
        let z3 = Rc::new(RefCell::new(FSMState::new(
            "z3".to_string(),
            FSMStateAction::None,
        )));

        let mut z0_transisions = FSMTranistion::new_from_mask(
            TransitionMask::from(MaskPresets::Char('a')),
            Rc::downgrade(&z1),
            Rc::downgrade(&z3),
        );
        z0_transisions.apply(
            TransitionMask::from(MaskPresets::Char('b')),
            Rc::downgrade(&z2),
        );

        let mut z1_transisions = FSMTranistion::new_from_mask(
            TransitionMask::from(MaskPresets::Char('a')),
            Rc::downgrade(&z1),
            Rc::downgrade(&z3),
        );
        z1_transisions.apply(
            TransitionMask::from(MaskPresets::Char('b')),
            Rc::downgrade(&z2),
        );

        let z2_transisions = FSMTranistion::new_from_mask(
            TransitionMask::from(MaskPresets::Char('a')),
            Rc::downgrade(&z2),
            Rc::downgrade(&z3),
        );

        let z3_transisions = FSMTranistion::new(Rc::downgrade(&z3));

        z0.borrow_mut().set_transitions(z0_transisions);
        z1.borrow_mut().set_transitions(z1_transisions);
        z2.borrow_mut().set_transitions(z2_transisions);
        z3.borrow_mut().set_transitions(z3_transisions);

        let fsm = FSM::new(vec![z0, z1, z2, z3], "Test FSM #1");

        let data = fsm.render_graph(Format::Svg).unwrap();

        let mut output_file = std::fs::File::create("/tmp/debug-simle_fsm_building.svg").unwrap();
        output_file.write_all(&data).unwrap();


        let svg_content = std::fs::read_to_string("/tmp/debug-simle_fsm_building.svg").unwrap();
        assert_eq!(svg_content, String::from_utf8(data).unwrap());
    }
}
