use crate::utility::list::List;
use crate::kernel::thread::Thread;
use crate::simulator::interrupt::InterruptStatus::InterruptOff;
use crate::simulator::machine::Machine;
use std::cell::RefCell;
use std::rc::Rc;


use super::scheduler::Scheduler;
use super::thread_manager::ThreadManager;

///     Structure of a Semaphore used for synchronisation
pub struct Semaphore {

    /// Counter of simultanous Semaphore
    counter:i32,
    /// QUeue of Semaphore waiting to be exucated
    waiting_queue:List<Rc<RefCell<Thread>>>,
    /// Thread manager which managing threads
    thread_manager: Rc<RefCell<ThreadManager>>

}

impl Semaphore {

    ///     Initializes a semaphore, so that it can be used for synchronization.
    ///
    ///  ### Parameters
    /// - *counter* initial value of counter
    /// - *thread_manager* Thread manager which managing threads
    pub fn new(counter: i32, thread_manager: Rc<RefCell<ThreadManager>>) -> Semaphore{
        Semaphore { counter, waiting_queue: List::new(), thread_manager}
    }

    ///     Decrement the value, and wait if it becomes < 0. Checking the
    ///	value and decrementing must be done atomically, so we
    ///	need to disable interrupts before checking the value.
    ///
    ///	Note that thread_manager::thread_sleep assumes that interrupts are disabled
    ///	when it is called.
    ///
    /// ### Parameters
    /// - *current_thread* the current thread
    /// - *machine* the machine where the threads are executed
    pub fn p(&mut self, current_thread: Rc<RefCell<Thread>>, machine: &mut Machine){
        let old_status = machine.interrupt.set_status(InterruptOff);
        self.counter -= 1;
        if self.counter < 0 {
            self.waiting_queue.push(Rc::clone(&current_thread));
            self.thread_manager.borrow_mut().thread_sleep(current_thread);
        }
        machine.interrupt.set_status(old_status);
    }

    /// Increment semaphore value, waking up a waiting thread if any.
    /// As with P(), this operation must be atomic, so we need to disable
    /// interrupts.
    ///
    /// scheduler::ready_to_run() assumes that interrupts
    /// are disabled when it is called.
    ///
    /// ### Parameters
    /// - **machine** the machine where the threads are executed
    /// - **scheduler** the scheduler which determine which thread to execute
    pub fn v(&mut self, machine: &mut Machine, scheduler: &mut Scheduler){
        let old_status = machine.interrupt.set_status(InterruptOff);
        self.counter += 1;
        if self.waiting_queue.peek() != None {
            scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
        }
        machine.interrupt.set_status(old_status);
    }
}

/// Lock used for synchronisation, can be interpreted has a Semaphore with a
/// counter of 1
/// It's used for critical parts
pub struct Lock{

    /// Thread owning the lock
    owner: Option<Rc<RefCell<Thread>>>,
    /// The queue of threads waiting for execution
    waiting_queue:List<Rc<RefCell<Thread>>>,
    /// Thread manager which managing threads
    thread_manager: Rc<RefCell<ThreadManager>>,
    /// A boolean definig if the lock is free or not
    free: bool

}

impl Lock {

    ///    Initialize a Lock, so that it can be used for synchronization.
    /// The lock is initialy free
    ///
    /// ### Parameters
    /// - **thread_manager** Thread manager which managing threads
    pub fn new(thread_manager: Rc<RefCell<ThreadManager>>) -> Lock {
        Lock { owner: None, waiting_queue: List::new(), thread_manager, free: true }
    }

    ///    Wait until the lock become free.  Checking the
    ///	state of the lock (free or busy) and modify it must be done
    ///	atomically, so we need to disable interrupts before checking
    ///	the value of free.
    ///
    ///	Note that thread_manager::thread_seep assumes that interrupts are disabled
    ///	when it is called.
    ///
    /// ### Parameters
    /// - **current_thread** the current thread
    /// - **machine** the machine where the threads are executed
    pub fn acquire(&mut self, current_thread: Option<Rc<RefCell<Thread>>>, machine: &mut Machine) {
        let old_status = machine.interrupt.set_status(InterruptOff);

        if self.free {
            self.free = false;
            self.owner = current_thread;
        } else {
            match current_thread {
                Some(x) => {
                    self.waiting_queue.push(Rc::clone(&x));
                    self.thread_manager.borrow_mut().thread_sleep(x)
                },
                None => ()
            }
        }

        machine.interrupt.set_status(old_status);
    }

    ///     Wake up a waiter if necessary, or release it if no thread is waiting.
    /// We check that the lock is held by the g_current_thread.
    ///	As with Acquire, this operation must be atomic, so we need to disable
    ///	interrupts.  scheduler::ready_to_run() assumes that threads
    ///	are disabled when it is called.
    ///
    /// ### Parameters
    /// - **machine** the machine where the code is executed
    /// - **scheduler** the scheduler which determine which thread to execute
    pub fn release(&mut self, machine: &mut Machine, scheduler: &mut Scheduler, current_thread: Rc<RefCell<Thread>>) {
        let old_status = machine.interrupt.set_status(InterruptOff);

        if self.held_by_current_thread(current_thread) {
            if self.waiting_queue.peek() != None {
                self.owner = Some(self.waiting_queue.pop().unwrap());
                match &self.owner {
                    Some(x) => scheduler.ready_to_run(Rc::clone(&x)),
                    None => ()
                }
            } else {
                self.free = true;
                self.owner = None;
            }
        }

        machine.interrupt.set_status(old_status);
    }

    pub fn held_by_current_thread(&mut self, current_thread: Rc<RefCell<Thread>>) -> bool {
        match &self.owner {
            Some(x) => Rc::ptr_eq(&x, &current_thread),
            None => false
        }
    }
}

/// Structure of a condition used for synchronisation
pub struct Condition{

    /// The queue of threads waiting for execution
    waiting_queue:List<Rc<RefCell<Thread>>>,
    /// Thread manager which managing threads
    thread_manager: Rc<RefCell<ThreadManager>>,
    
}

impl Condition {

    /// Initializes a Condition, so that it can be used for synchronization.
    ///
    /// ### Parameters
    /// - *thread_manager* Thread manager which managing threads
    pub fn new(thread_manager: Rc<RefCell<ThreadManager>>) -> Condition {
        Condition{ waiting_queue: List::new(), thread_manager }
    }

    ///  Block the calling thread (put it in the wait queue).
    ///  This operation must be atomic, so we need to disable interrupts.
    ///
    /// ### Parameters
    /// - **current_thread** the current thread
    /// - **machine** the machine where threads are executed
    pub fn wait(&mut self, current_thread: Rc<RefCell<Thread>>, machine: &mut Machine) {
        let old_status = machine.interrupt.set_status(InterruptOff);

        self.waiting_queue.push(Rc::clone(&current_thread));
        self.thread_manager.borrow_mut().thread_sleep(current_thread);

        machine.interrupt.set_status(old_status);
    }

    /// Wake up the first thread of the wait queue (if any).
    /// This operation must be atomic, so we need to disable interrupts.
    ///
    /// ### Parameters
    /// - **machine** the machine where the code is executed
    /// - **scheduler** the scheduler which determine which thread to execute
    pub fn signal(&mut self, machine: &mut Machine, scheduler: &mut Scheduler) {
        let old_status = machine.interrupt.set_status(InterruptOff);

        if self.waiting_queue.peek() != None {
            scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
        }

        machine.interrupt.set_status(old_status);

    }

    /// Wake up all threads waiting in the waitqueue of the condition
    /// This operation must be atomic, so we need to disable interrupts.
    ///
    /// ### Parameters
    /// - **machine** the machine where the code is executed
    /// - **scheduler** the scheduler which determine which thread to execute
    pub fn broadcast(&mut self, machine: &mut Machine, scheduler: &mut Scheduler) {
        let old_status = machine.interrupt.set_status(InterruptOff);

        while self.waiting_queue.peek() != None {
            scheduler.ready_to_run(self.waiting_queue.pop().unwrap());
        }
        machine.interrupt.set_status(old_status);

    }

}

#[test]
fn test_semaphore_single() {
    // Init
    let thread_manager = Rc::new(RefCell::new(ThreadManager::new()));
    let mut semaphore = Semaphore::new(1, thread_manager);
    let mut machine = Machine::init_machine();
    let mut scheduler = Scheduler::new();
    let thread = Rc::new(RefCell::new(Thread::new("test_semaphore")));
    // P
    semaphore.p(thread, &mut machine);
    assert_eq!(semaphore.counter, 0);
    assert!(semaphore.waiting_queue.is_empty());
    // V
    semaphore.v(&mut machine, &mut scheduler);
    assert_eq!(semaphore.counter, 1);
    assert!(semaphore.waiting_queue.is_empty());
}

#[test]
fn test_semaphore_multiple() {
    // Init
    let thread_manager = Rc::new(RefCell::new(ThreadManager::new()));
    let mut semaphore = Semaphore::new(2, Rc::clone(&thread_manager));
    let mut machine = Machine::init_machine();
    let mut scheduler = Scheduler::new();
    let thread1 = Rc::new(RefCell::new(Thread::new("test_semaphore_1")));
    let thread2 = Rc::new(RefCell::new(Thread::new("test_semaphore_2")));
    let thread3 = Rc::new(RefCell::new(Thread::new("test_semaphore_3")));
    // P
    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread1)));
    semaphore.p(thread1, &mut machine);
    assert_eq!(semaphore.counter, 1);
    assert!(semaphore.waiting_queue.is_empty());
    
    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread2)));
    semaphore.p(thread2, &mut machine);
    assert_eq!(semaphore.counter, 0);
    assert!(semaphore.waiting_queue.is_empty());

    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread3)));
    semaphore.p(thread3, &mut machine);
    assert_eq!(semaphore.counter, -1);
    assert!(semaphore.waiting_queue.iter().count() == 1);

    // V
    semaphore.v(&mut machine, &mut scheduler);
    assert_eq!(semaphore.counter, 0);
    assert!(semaphore.waiting_queue.is_empty());

    semaphore.v(&mut machine, &mut scheduler);
    assert_eq!(semaphore.counter, 1);
    assert!(semaphore.waiting_queue.is_empty());

    semaphore.v(&mut machine, &mut scheduler);
    assert_eq!(semaphore.counter, 2);
    assert!(semaphore.waiting_queue.is_empty());
}



#[test]
fn test_lock_simple() {
    let thread_manager = Rc::new(RefCell::new(ThreadManager::new()));
    let mut machine = Machine::init_machine();
    let mut scheduler = Scheduler::new();
    let thread = Rc::new(RefCell::new(Thread::new("test_lock")));
    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread)));
    let mut lock = Lock::new(Rc::clone(&thread_manager));

    assert!(lock.free);
    lock.acquire(Some(Rc::clone(&thread)), &mut machine);
    assert!(lock.held_by_current_thread(Rc::clone(&thread)));

    assert!(!lock.free);
    lock.release(&mut machine, &mut scheduler, Rc::clone(&thread));
    assert!(!lock.held_by_current_thread(thread));
    assert!(lock.free);
}

#[test]
fn test_lock_multiple() {
    let thread_manager = Rc::new(RefCell::new(ThreadManager::new()));
    let mut machine = Machine::init_machine();
    let mut scheduler = Scheduler::new();
    let thread1 = Rc::new(RefCell::new(Thread::new("test_lock1")));
    let thread2 = Rc::new(RefCell::new(Thread::new("test_lock2")));
    let thread3 = Rc::new(RefCell::new(Thread::new("test_lock3")));

    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread1)));
    let mut lock = Lock::new(Rc::clone(&thread_manager));

    assert!(lock.free);
    lock.acquire(Some(Rc::clone(&thread1)), &mut machine);
    assert!(lock.held_by_current_thread(Rc::clone(&thread1)));
    assert!(!lock.free);

    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread2)));
    lock.acquire(Some(Rc::clone(&thread2)), &mut machine);
    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread1)));

    
    assert!(lock.held_by_current_thread(Rc::clone(&thread1)));
    assert!(lock.waiting_queue.iter().count() == 1);
    assert!(!lock.free);

    lock.release(&mut machine, &mut scheduler, Rc::clone(&thread1));
    assert!(!lock.held_by_current_thread(thread1));
    assert!(lock.held_by_current_thread(Rc::clone(&thread2)));
    assert!(!lock.free);

    thread_manager.borrow_mut().set_g_current_thread(Some(Rc::clone(&thread2)));


    lock.release(&mut machine, &mut scheduler, Rc::clone(&thread2));
    assert!(!lock.held_by_current_thread(thread2));
    assert!(lock.free);
}