use std::{rc::Rc, cell::RefCell};

use super::{process::Process, thread_manager::ThreadRef};
use crate::{simulator::machine::{NUM_INT_REGS, NUM_FP_REGS, STACK_REG}, utility::list::List};

const STACK_FENCEPOST: u32 = 0xdeadbeef;

/// Polymorphic macro to get thread without passing a name by default
#[macro_export]
macro_rules! get_new_thread {
    () => { Thread::new(DEFAULT_THREAD_NAME) };
    ($a:literal) => {
        Thread::new(&$a.to_string())
    };
}

#[derive(PartialEq, Debug)]
pub struct ThreadContext {
    pub int_registers: [i64; NUM_INT_REGS],
    pub float_registers: [f32; NUM_FP_REGS],
    pub pc: u64,
}

#[derive(PartialEq, Debug)]
pub struct Thread {
    name: String,
    pub process: Option<Rc<RefCell<Process>>>,
    pub thread_context: ThreadContext,
    pub join_thread: List<ThreadRef>,
}

impl Thread {

    /// Thread constructor
    pub fn new(name: &str) -> Self {
        Self {
            name: String::from(name),
            process: None,
            // simulation_context: UContextT::new(),
            thread_context: ThreadContext { 
                int_registers: [0; NUM_INT_REGS], 
                float_registers: [0f32; NUM_FP_REGS], 
                pc: 0,
            },
            join_thread: List::default(),
        }
    }

    pub fn init_thread_context(&mut self, initial_pc_reg: u64, initial_sp: u64, arg: i64) {
        self.thread_context.pc = initial_pc_reg;
        self.thread_context.int_registers[10] = arg;
        self.thread_context.int_registers[STACK_REG] = initial_sp as i64;
    }
    
    /// Check if a thread has overflowed its stack
    /// 
    /// This assertion doesn't catch all stack overflow conditions and your program may still crash because of an overflow.
    /// 
    pub fn check_overflow(&self) {
        // if self.simulator_context.stackBottom != STACK_FENCEPOST {
        //     panic!("thread {} has overflowed", self.get_name())
        // }
    }

    pub fn get_name(&self) -> String {
        self.name.clone()
    }

    /// Return reference to an optional Process
    /// can be None if Thread hasn't been initialize
    pub fn get_process_owner(&self) -> &Option<Rc<RefCell<Process>>> {
        &self.process
    }

}

#[cfg(test)]
mod test {

    use super::{Thread, ThreadContext, NUM_INT_REGS, NUM_FP_REGS};
    const DEFAULT_THREAD_NAME: &str = "test_thread";

    /// This macro allows for getting a Thread for which we've ensured proper initial state
    /// in case a commit further down the line changes the initial state of threads generated
    /// from Thread::new
    macro_rules! expected_initial_state {
        () => { expected_initial_state!(DEFAULT_THREAD_NAME) };
        ($a:expr) => { {
            let mut x = Thread::new($a);
            x.name = $a.to_string();
            x.process = Option::None;
            x.thread_context = ThreadContext { 
                int_registers: [0; NUM_INT_REGS], 
                float_registers: [0f32; NUM_FP_REGS], 
                pc: 0 
            };
            x }
        };
    }

    #[test]
    fn test_macro() {
        let t = get_new_thread!("hello");
        assert_eq!(t.get_name(), "hello");
        let t = get_new_thread!(1);
        assert_eq!(t.get_name(), "1");
    }

    #[test]
    fn check_init() {
        let t = get_new_thread!();
        let expected_state = expected_initial_state!();
        assert_eq!(t, expected_state)
    }

}