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

use crate::{simulator::{machine::{ExceptionType, Machine}, error::{MachineOk, MachineError}}};
use crate::kernel::synch::{Lock, Semaphore};

use super::{system::{System, self}, thread::Thread};

/// The halt system call. Stops Burritos.
pub const SC_SHUTDOWN: u8 =	0;
/// The exit system call
/// 
/// Ends the calling thread
pub const SC_EXIT: u8 =	1;
/// The exec system call
/// 
/// Creates a new process (thread+address space)
pub const SC_EXEC: u8 =	2;
/// The join system call
/// 
/// Wait for the thread idThread to finish
pub const SC_JOIN: u8 =	3;
/// The create system call
/// 
/// Create a new file in nachos file system
pub const SC_CREATE: u8 = 4;
/// The open system call
/// 
/// Opens a file and returns an openfile identifier
pub const SC_OPEN: u8 =	5;
/// The read system call
/// 
/// Read in a file or the console
pub const SC_READ: u8 =	6;
/// The write system call
/// 
/// Write in a file or at the console
pub const SC_WRITE: u8 = 7;
/// Seek to a given position in an opened file
pub const SC_SEEK: u8 = 8;
/// The close system call
/// 
/// Close a file
pub const SC_CLOSE: u8 = 9;
/// The newThread system call
///
///  Create a new thread in the same address space
pub const SC_NEW_THREAD: u8 = 10;
/// The Yield System call
/// 
/// Relinquish the CPU if any other thread is runnable
pub const SC_YIELD: u8 = 11;
/// the PError system call
/// 
/// print the last error message
pub const SC_PERROR: u8 = 12;
/// carry out P() on the semaphore
pub const SC_P: u8 = 13;
/// carry out V() on the semaphore 
pub const SC_V: u8 = 14;
/// create a semaphore and add it in g_objects_addrs
pub const SC_SEM_CREATE: u8 = 15;
/// destroy the semaphore corresponding to the id
pub const SC_SEM_DESTROY: u8 = 16;
/// create a lock and add it to g_object_addrs
pub const SC_LOCK_CREATE: u8 = 17;
/// destroy the lock corresponding to the id
pub const SC_LOCK_DESTROY: u8 = 18;
/// carry out acquire() on the lock
pub const SC_LOCK_ACQUIRE: u8 = 19;
/// carry out release() on the lock 
pub const SC_LOCK_RELEASE: u8 = 20;
/// create a condition variable and add it to g_object_addrs
pub const SC_COND_CREATE: u8 = 21;
/// destroy the condition variable corresponding to the id
pub const SC_COND_DESTROY: u8 = 22;
/// carry out wait() on the condition 
pub const SC_COND_WAIT: u8 = 23;
/// carry out signal() on the condition 
pub const SC_COND_SIGNAL: u8 = 24;
/// carry out broadcast() on the condition 
pub const SC_COND_BROADCAST: u8 = 25;
/// the TtySend system call	
/// 
/// Sends some char by the serial line emulated
pub const SC_TTY_SEND: u8 = 26;
/// the TtyReceive system call	
/// 
/// read some char on the serial line
pub const SC_TTY_RECEIVE: u8 = 27;
/// the Mkdir system call
/// 
/// make a new directory in the file system 
pub const SC_MKDIR: u8 = 28;
/// the Rmdir system call
/// 
/// remove a directory from the file system
pub const SC_RMDIR: u8 = 29;
/// The Remove system call
/// 
/// Remove a file from the file system
pub const SC_REMOVE: u8 = 30;
/// The FSList system call
/// 
/// Lists all the file and directories in the filesystem
pub const SC_FSLIST: u8 = 31;
// The systime system call. Gets the system time
pub const SC_SYS_TIME: u8 = 32;
/// Map a file in memory
pub const SC_MMAP: u8 = 33;
/// Behaviour undefined and currently unused
pub const SC_DEBUG: u8 = 34;

pub const CONSOLE_OUTPUT: u8 = 1;

// todo : returns new types, not just machine errors and machine ok
pub fn call(exception: &ExceptionType, machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {

    match exception {
        ExceptionType::NoException => Err("No Exception no yet implemented")?,
        ExceptionType::SyscallException => syscall(machine, system),
        ExceptionType::PagefaultException => Err("Page Fault Exception not yet implemented")?,
        ExceptionType::ReadOnlyException => Err("Read Only Exception not yet implemented")?,
        ExceptionType::BusErrorException => Err("Bus Error Exception not yet implemented")?,
        ExceptionType::AddressErrorException => Err("AddressErrorException not yet implemented")?,
        ExceptionType::OverflowException => Err("OverflowException not yet implemented")?,
        ExceptionType::IllegalInstrException => Err("IllegalInstrException not yet implemented")?,
        ExceptionType::NumExceptionTypes => Err("NumExceptionTypes not yet implemented")?,
    }
}

fn syscall(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let call_type = machine.read_int_register(17) as u8;

    match call_type {
        SC_SHUTDOWN => Ok(MachineOk::Shutdown),
        SC_EXIT => {
            let th = match &system.get_thread_manager().g_current_thread {
                Some(th) => th.clone(),
                None => Err("Current thread is None")?
            };
            system.get_thread_manager().thread_finish(machine, th);
            Ok(MachineOk::Ok)
        },
        SC_EXEC => todo!(),
        SC_JOIN => sc_join(machine, system),
        SC_CREATE => todo!(),
        SC_OPEN => todo!(),
        SC_READ => todo!(),
        SC_WRITE => {

            let address = machine.read_int_register(10);
            let size = machine.read_int_register(11);
            // openfileid or 1 (console)
            let f = machine.read_int_register(12);

            // load buffer
            let mut buffer = String::new();
            let mut val: [u8; 4] = [0; 4];
            for (j, elem) in val.iter_mut().enumerate() {
                *elem = machine.read_memory(1, address as usize + j) as u8;
            }
            for i in 0..size {
                buffer.push((machine.read_memory(1, (address + i) as usize)) as u8 as char);
            }

            if f as u8 == CONSOLE_OUTPUT {
                print!("{}", buffer); // todo replace with console driver in the future
                Ok(MachineOk::Ok)
            } else {
                Err("SC_WRITE to file is not yet implemented")?
            }
        },
        SC_SEEK => todo!(),
        SC_CLOSE => todo!(),
        SC_NEW_THREAD => sc_new_thread(machine, system),
        SC_YIELD => todo!(),
        SC_PERROR => todo!(),
        SC_P => sc_p(machine, system),
        SC_V => sc_v(machine, system),
        SC_SEM_CREATE => sc_sem_create(machine, system),
        SC_SEM_DESTROY => sc_sem_remove(machine, system),
        SC_LOCK_CREATE => sc_lock_create(machine, system),
        SC_LOCK_DESTROY => sc_lock_destroy(machine, system),
        SC_LOCK_ACQUIRE => sc_lock_acquire(machine, system),
        SC_LOCK_RELEASE => todo!(),
        SC_COND_CREATE => todo!(),
        SC_COND_DESTROY => todo!(),
        SC_COND_WAIT => todo!(),
        SC_COND_SIGNAL => todo!(),
        SC_COND_BROADCAST => todo!(),
        SC_TTY_SEND => todo!(),
        SC_TTY_RECEIVE => todo!(),
        SC_MKDIR => todo!(),
        SC_RMDIR => todo!(),
        SC_REMOVE => todo!(),
        SC_FSLIST => todo!(),
        SC_SYS_TIME => todo!(),
        SC_MMAP => todo!(),
        SC_DEBUG => todo!(),
        _ => todo!()
    }
}

fn sc_lock_release(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError>{
    let id = machine.read_int_register(10) as i32;
    system.get_thread_manager().lock_release(id, machine)
}

fn sc_lock_acquire(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let id = machine.read_int_register(10) as i32;
    system.get_thread_manager().lock_acquire(id, machine)
}

fn sc_lock_create(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let addr_name = machine.read_int_register(10) as usize;
    let size = get_length_param(addr_name, machine);
    let _name = get_string_param(addr_name, size, machine);
    let lock = Lock::new();
    let id = system.get_thread_manager().get_obj_addrs().add_lock(lock);
    machine.write_int_register(10, id as i64);
    Ok(MachineOk::Ok)
}

fn sc_lock_destroy(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let id = machine.read_int_register(10) as i32;
    system.get_thread_manager().get_obj_addrs().remove_lock(id);
    Ok(MachineOk::Ok)
}

fn sc_p(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let id_sema = machine.int_reg.get_reg(10);
    system.get_thread_manager().sem_p(id_sema as i32, machine)
}

fn sc_v(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let id_sema = machine.int_reg.get_reg(10);
    system.get_thread_manager().sem_v(id_sema as i32, machine)
}

fn sc_sem_create(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let addr_name = machine.read_int_register(10) as usize;
    let initial_count = machine.read_int_register(11) as i32;
    let size = get_length_param(addr_name, machine);
    let _name = get_string_param(addr_name, size, machine);
    match initial_count < 0 {
        true => Err(format!("Initial_count < 0"))?,
        false => {
            let id = system.get_thread_manager().get_obj_addrs().add_semaphore(Semaphore::new(initial_count));
            machine.write_int_register(10, id as i64);
            Ok(MachineOk::Ok)
        }
    }
}

fn sc_sem_remove(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError>{
    let id = machine.read_int_register(10) as i32;
    system.get_thread_manager().get_obj_addrs().remove_semaphore(id);
    Ok(MachineOk::Ok)
}

fn sc_new_thread(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    // Get the address of the string for the name of the thread
    let name_addr = machine.read_int_register(10) as usize;
    // Get the pointer of the function to be executed in the new thread
    let func = machine.read_int_register(11);
    // Get function parameters
    let args = machine.read_int_register(12);
    // get string name
    let name_size = get_length_param(name_addr, machine);
    let thread_name: String = get_string_param(name_addr, name_size, machine).into_iter().collect();

    let n_thread = Thread::new(thread_name.as_str());
    let n_thread = Rc::new(RefCell::new(n_thread));
    let tid = system.get_thread_manager().get_obj_addrs().add_thread(Rc::clone(&n_thread));
    let current_thread = match system.get_thread_manager().get_g_current_thread() {
        Some(th) => {
            Rc::clone(th)
        },
        None => {
            return Err("Current thread is none")?;
        }
    };
    let current_thread = current_thread.borrow_mut();
    if let Some(process) = current_thread.get_process_owner() {
        system.get_thread_manager().start_thread(n_thread, Rc::clone(&process), func as u64, current_thread.thread_context.int_registers[2] as u64 + machine.page_size, args);
        // TODO changé la valeur de sp quand on supportera les addresses virtuels
        machine.write_int_register(10, tid as i64);
        Ok(MachineOk::Ok)
    } else {
        return Err("Process owner of current thread is none")?;
    }
}

fn sc_join(machine: &mut Machine, system: &mut System) -> Result<MachineOk, MachineError> {
    let tid = machine.read_int_register(10);
    let p_thread = system.get_thread_manager().get_obj_addrs().search_thread(tid as i32);
    match p_thread {
        Some(waiting_for) => {
            let rc_waiting_for = Rc::clone(waiting_for);
            if let Some(current_thread) = system.get_thread_manager().get_g_current_thread() {
                let rc_curr = Rc::clone(current_thread);
                system.get_thread_manager().thread_join(machine, rc_curr, rc_waiting_for);

                Ok(MachineOk::Ok)
            } else {
                Err("Current should not be None")?
            }
        },
        None => {
            // Thread already terminated (type set to INVALID_TYPE) or call on an object
	        // that is not a thread
	        // Exit with no error code since we cannot separate the two cases
            Ok(MachineOk::Ok)
        }
    }
}

fn get_length_param(addr: usize, machine: & Machine) -> usize {
    let mut i = 0;
    let mut c = 1;
    while c != 0 {
        c = machine.read_memory(1, addr + i);
        i += 1;

    }
    i + 1
}

fn get_string_param(addr: usize, maxlen: usize, machine: &Machine) -> Vec<char> {
    let mut dest = Vec::with_capacity(maxlen);

    let mut i: usize = 0;
    let mut c = 1;

    while c != 0 && i < maxlen {
        c = machine.read_memory(1, addr + i) as u8;
        dest.push(c as char);
        i += 1;
    }

    dest
}

#[cfg(test)]
mod test {
    use crate::kernel::exception::{SC_SHUTDOWN, SC_WRITE};
    use crate::kernel::system::System;
    use crate::simulator::machine::Machine;
    use crate::utility::cfg::get_debug_configuration;

    #[test]
    fn test_sc_shutdown() {
        let mut machine = Machine::new(true, get_debug_configuration());
        machine.write_int_register(17, SC_SHUTDOWN as i64); // Set type to shutdown
        // let ecall = Instruction::new(0b000000000000_00000_000_00000_1110011);

        machine.write_memory(4, 0, 0b000000000000_00000_000_00000_1110011); // ecall
        machine.write_memory(4, 4, 0b000000001010_00000_000_00001_0010011); // r1 <- 10
        let mut system = System::new(true);
        machine.run(&mut system);
        // If the machine was stopped with no error, the shutdown worked
        assert_ne!(machine.read_int_register(1), 10); // Check if the next instruction was executed
    }

    // This test print HELLO in the console
    #[test]
    #[ignore]
    fn test_sc_print() {
        let mut machine = Machine::new(true, get_debug_configuration());

        let _address = machine.read_int_register(10);
        // Write string 'HELLO' in memory
        machine.write_memory(1, 4000, 72);
        machine.write_memory(1, 4001, 69);
        machine.write_memory(1, 4002, 76);
        machine.write_memory(1, 4003, 76);
        machine.write_memory(1, 4004, 79);


        machine.write_int_register(10, 4000); // String address
        machine.write_int_register(11, 5); // String size
        machine.write_int_register(12, 1); // Console output

        machine.write_memory(4, 0, 0b000000000000_00000_000_00000_1110011); // ecall
        machine.write_int_register(17, SC_WRITE as i64); // Set type to write

        machine.write_memory(4, 4, 0b000000000000_00000_000_10001_0010011); // r17 <- SC_SHUTDOWN
        machine.write_memory(4, 8, 0b000000000000_00000_000_00000_1110011); // ecall

        let mut system = System::new(true);
        machine.run(&mut system);
    }

}