use std::{rc::Rc};

use crate::configotron::fsm::{builder::elimate_unused_states, masking::{MaskPresets, TransitionMask}, new_state_ref, transition::FSMTranistion, FSMState, FSMStateAction, FSMStateRef};

use super::super::FSM;

fn number_of_digits(i: i32) -> u32 {
    if i == 0 {
        1
    } else {
        let mut n = i.abs();
        let mut digits = 0;
        while n > 0 {  // Yes, with floating point math it does not work, sorry ;(
            n /= 10;
            digits += 1;
        }
        digits
    }
}

fn nth_decimal_digit(num: u32, n: u32) -> u32 {
    let num_digits = number_of_digits(num as i32);
    if n >= num_digits {
        return 0;
    }
    let divisor = 10u32.pow(num_digits - n - 1);
    (num / divisor) % 10
}

fn create_fsm_number_upperbound(max: u32) -> FSM {
    let length_of_number = number_of_digits(max as i32);
    
    // Creating All states
    let error_state = new_state_ref(FSMState::new("e".to_string(), FSMStateAction::Error));
    let goal_state = new_state_ref(FSMState::new("g".to_string(), FSMStateAction::Terminal));

    let mut main_line: Vec<FSMStateRef> = Vec::new();
    let mut side_line: Vec<FSMStateRef> = Vec::new();

    // Create and Link Main to side
    for i in 0..length_of_number {
        let digit = nth_decimal_digit(max, i);
        
        let new_main_state = new_state_ref(FSMState::new(
            format!("m{}", i), FSMStateAction::Terminal));
        let new_side_state = new_state_ref(FSMState::new(
            format!("s{}", i), FSMStateAction::Terminal));
        
        let mut main_state_transition = FSMTranistion::new(Rc::downgrade(&error_state));

        if digit > 0 {
            main_state_transition.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(digit as u8 - 1))), Rc::downgrade(&new_side_state));
        }

        new_main_state.borrow_mut().set_transitions(
            main_state_transition
        );

        new_side_state.borrow_mut().set_transitions(
            FSMTranistion::new(Rc::downgrade(&error_state))
        );

        main_line.push(new_main_state.clone());
        side_line.push(new_side_state.clone());
    };

    // Link Main to main, side to side
    for (i, 
        (
            (prev_main, next_main), 
            (prev_side, next_side)
        )
    ) in
        main_line.iter().zip(main_line.iter().skip(1))
            .zip(side_line.iter().zip(side_line.iter().skip(1)))
            .enumerate() {
        let digit = nth_decimal_digit(max, i as u32);
        
        match &mut prev_main.borrow_mut().transition {
            None => {},
            Some(t) => {

                if digit < 9 {
                    t.apply(
                        TransitionMask::from(MaskPresets::NumberRange((digit as u8 + 1)..=9)),
                        Rc::downgrade(next_side)
                    );
                }

                t.apply(
                    TransitionMask::from(MaskPresets::Char(std::char::from_digit(digit, 10).unwrap())),
                    Rc::downgrade(next_main)
                );
            }
        }

        match &mut prev_side.borrow_mut().transition {
            None => {},
            Some(t) => {
                t.apply(
                    TransitionMask::from(MaskPresets::Numbers),
                    Rc::downgrade(next_side)
                );
            }
        }        
    }

    // Link last main to side, goal
    match &mut main_line.last() {
        Some(main_last) => {
            let last_digit = max % 10;
            match &mut main_last.borrow_mut().transition {
                Some(t) => {
                    t.apply(TransitionMask::from(MaskPresets::Char(
                        std::char::from_digit(last_digit, 10).unwrap()
                    )), Rc::downgrade(&goal_state));

                    t.apply(TransitionMask::from(MaskPresets::NumberRange(0..=(last_digit as u8))),
                        Rc::downgrade(&goal_state)
                    );
                },
                _ => {}
            }
        },
        _ => {}
    }

    // Link goal to error
    goal_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
    
    // Linking error to error
    error_state.borrow_mut().set_transitions(FSMTranistion::new(Rc::downgrade(&error_state)));
    
    // Combinding in one array
    let mut states: Vec<Rc<std::cell::RefCell<FSMState>>> = vec![error_state, goal_state];
    let first_state: Option<FSMStateRef> = match main_line.first() {
        None => None,
        Some(first) => {
            Some(first.clone())
        }
    };
    states.append(&mut main_line);
    states.append(&mut side_line);

    let mut fsm = FSM {
        name: format!("FSM_UpperBound_{}", max),
        states: states,
        start: first_state
    };

    elimate_unused_states(&mut fsm);

    fsm
}


#[cfg(test)]
mod test {
    use graphviz_rust::cmd::Format;

    use crate::configotron::fsm::{builder::numbers::{create_fsm_number_upperbound, number_of_digits}, run::run_fsm, test::debug_file_dump, FSMStateAction};

    
    #[test]
    fn build_upper_bound() {

        for max in vec![4026, 124, 9999, 1000, 0, 42398] {
            let fsm = create_fsm_number_upperbound(max);
            let data = fsm.render_graph(Format::Svg).unwrap();
            println!("Testing Lower Bound FSM {}", max);

            for i in 0..=(if max == 0 {10} else {max * 10} ) {
    
                assert_eq!(run_fsm(&i.to_string(), &fsm), if i <= max {
                    FSMStateAction::Terminal
                } else {
                    FSMStateAction::Error
                });
            }

            // Testing against refernce machine
            let file_name = format!("build_number_upper_bound_{}.svg", max);
            debug_file_dump(data.clone(), format!("/tmp/{}", file_name).to_string());

        }
    }

    #[test]
    fn number_of_digits_test() {
        assert_eq!(number_of_digits(0), 1);
        assert_eq!(number_of_digits(9), 1);
        assert_eq!(number_of_digits(10), 2);
        assert_eq!(number_of_digits(99), 2);
        assert_eq!(number_of_digits(100), 3);
        assert_eq!(number_of_digits(-1), 1);
        assert_eq!(number_of_digits(-9), 1);
        assert_eq!(number_of_digits(-10), 2);
        assert_eq!(number_of_digits(-99), 2);
        assert_eq!(number_of_digits(-100), 3);
        assert_eq!(number_of_digits(123456), 6);
        assert_eq!(number_of_digits(-123456), 6);
    }
}