//! # Machine
//! 
//! This module contains a RISC-V simulator.
//! It supports the base instruction set along
//! with 32bit floating point operations.
//! 
//! Basic usage:
//! 
//! ```
//! let mut machine = Machine::init_machine();
//! machine.run();
//! ```

use std::{
	io::Write,
	fs::File
};
use crate::simulator::{
	print,
	error::MachineError,
	decode::*,
	interrupt::Interrupt,
	global::*,
	register::*
};

/// # Exceptions
/// 
/// Textual names of the exceptions that can be generated by user program
/// execution, for debugging purpose.
/// todo: is this really supposed to stand in machine.rs?
pub enum ExceptionType {
	/// Everything ok
	NoException,
	/// A program executed a system call
	SyscallException,
	/// Page fault exception
	PagefaultException,
	/// Write attempted to a page marked "read-only"
	ReadOnlyException,
	/// Translation resulted in an invalid physical	address (mis-aligned or	out-of-bounds)
	BusErrorException,
	/// Reference which was not mapped in the address space
	AddressErrorException,
	/// Integer overflow in add or sub
	OverflowException,
	/// Unimplemented or reserved instruction
	IllegalInstrException,
	NumExceptionTypes
}

/// ID of the stack register
pub const STACK_REG: usize = 2;
/// Number of available Integer registers
pub const NUM_INT_REGS: usize = 32;
/// Number of available Floating Point registers
pub const NUM_FP_REGS: usize = 32;
/// max number of physical pages
pub const NUM_PHY_PAGE : u64 = 400;
/// Must be 2^x
pub const PAGE_SIZE : u64 = 128;
/// Must be a multiple of PAGE_SIZE
pub const MEM_SIZE : usize = (PAGE_SIZE*NUM_PHY_PAGE*100) as usize;

/// RISC-V Simulator
pub struct Machine {
	/// Program counter
	pub pc : u64,
	/// Stack pointer
	pub sp: usize,
	/// Integer register
	pub int_reg : Register<i64>,
	/// Floating point register
	pub fp_reg : Register<f32>,
	/// Heap memory
	pub main_memory : Vec<u8>,
	/// Shiftmask
	pub shiftmask : [u64 ; 64],
	/// Debug data
	pub registers_trace : String, // for tests
	/// todo: document Interrupts
	pub interrupt: Interrupt
	// futur taille à calculer int memSize = g_cfg->NumPhysPages * g_cfg->PageSize;
	//creer une struct cfg(configuration) qui s'initialise avec valeur dans un fichier cfg
}


impl Machine {

	/// Machine constructor
	pub fn init_machine() -> Machine {	
		let mut shiftmask : [u64 ; 64] = [0 ; 64];
		let mut value : u64 = 0xffffffff;

		value = (value << 32) + value;
		for item in &mut shiftmask {
			*item = value;
			value >>= 1;
		}

		Machine {
			pc : 0,
			sp: 0,
			int_reg : { let mut r = Register::<i64>::init(); r.set_reg(10, -1); r },
			fp_reg : Register::<f32>::init(),
			main_memory : vec![0_u8; MEM_SIZE],
			shiftmask,
			interrupt: Interrupt::new(),
			registers_trace : String::from("")
		}
	}

	/// Read from main memory of the machine
	///
	/// `panic!` when size is not 1, 2, 4 or 8
	/// 
	/// ### Parameters
	///
	/// - **machine** which contains the main memory
	/// - **size** the number of bytes to read (1, 2, 4, 8)
	/// - **address** in the memory to read
	pub fn read_memory(&self, size : i32, address : usize) -> u64 {
		if ![1, 2, 4, 8].contains(&size) {
			panic!("ERROR read_memory : wrong size parameter {size}, must be (1, 2, 4 or 8)");
		}

		let mut ret: u64 = 0;
		for i in 0..size {
			ret <<= 8;
			ret += self.main_memory[address + i as usize] as u64;
		}
		ret
	}

	/// Write to the main memory of the machine
	/// 
	/// `panic!` when size is not 1, 2, 4 or 8
	/// 
	/// ### Parameters
	/// 
	/// - **machine** contains the memory
	/// - **size** the number of bytes to write (1, 2, 4 or 8)
	/// - **address** the address to write to
	/// - **value** data to be written
	pub fn write_memory(&mut self, size: i32, address: usize, value: u64) {
		if ![1, 2, 4, 8].contains(&size) {
			panic!("ERROR write_memory: WRONG `size` PARAMETER ({size}), must be 1, 2, 4 or 8")
		}
		for i in 0..size as usize {
			let inv_i = size as usize - i - 1;
			self.main_memory[address + i] = ((value & 0xff << (8 * inv_i)) >> (inv_i * 8)) as u8;
		}
	}

	/// Write the contains of the main memory of the machine
	/// in a file called burritos_memory.txt
	/// 
	/// ### Parameters
	/// 
	///  - **machine** contains the memory
	pub fn _extract_memory(&mut self){
		let file_path = "burritos_memory.txt";
		let write_to_file = |path| -> std::io::Result<File> {
			let mut file = File::create(path)?;
			file.write_all(&self.main_memory)?;
			Ok(file)
		};
		match write_to_file(file_path) {
			Err(e) => eprintln!("Failed to write memory to file: {}", e),
			Ok(_) => println!("Memory extracted to {}", file_path)
		};
	}

	/// Print the status of the machine to the standard output
	/// 
	/// ### Parameters
	/// 
	///  - **machine** the machine to get the status from
	pub fn print_status(&self) {
		println!("######### Machine status #########");
		for i in (0..32).step_by(3) {
			print!(">{0: <4} : {1:<16x} ", print::REG_X[i], self.int_reg.get_reg(i as u8));
			print!(">{0: <4} : {1:<16x} ", print::REG_X[i+1], self.int_reg.get_reg((i+1) as u8));
			if i+2 < 32 {
				print!(">{0: <4} : {1:<16x} ", print::REG_X[i+2], self.int_reg.get_reg((i+2) as u8));
			}
			println!();
		}
		println!("________________SP________________");
		let sp_index = self.int_reg.get_reg(2);
		/* for i in 0..5 {
			println!("SP+{:<2} : {:16x}", i*8, self.read_memory(8, (sp_index + i*8) as usize));
		} */		
		println!("##################################");
	}

	/// Get the state of the registers as a string
	/// 
	/// ### Parameters
	/// 
	///  - **machine** the machine to read the registers from
	pub fn string_registers(&self) -> String {
		let mut s = String::from("");
		for i in 0..32 {
			s.push_str(format!("{} ", self.int_reg.get_reg(i)).as_str());
		}
		s
	}

	/// Execute the instructions table of a machine putted in param
	/// 
	/// ### Parameters
	/// 
	/// - **machine** which contains a table of instructions
	pub fn run(&mut self) {
		loop {
			match self.one_instruction() {
				Ok(_) => println!("hello"),
				Err(e) => { if e.to_string().contains("System") { break; } panic!("FATAL at pc {} -> {}", self.pc, e) }
			}
			self.write_int_register(0, 0); // In case an instruction write on register 0
		}
	}

	/// Execute the current instruction
	/// 
	/// ### Parameters
	/// 
	/// - **machine** which contains a table of instructions and a pc to the actual instruction
	pub fn one_instruction(&mut self) -> Result<(), MachineError> {
	
		if self.main_memory.len() <= self.pc as usize {
			panic!("ERROR : number max of instructions rushed");
		}
		let mut val: [u8; 4] = [0; 4];
		for (i, elem) in val.iter_mut().enumerate() {
			*elem = self.main_memory[self.pc as usize + i];
		}
		
		let val = u32::from_be_bytes(val) as u64;
		let inst : Instruction = decode(val);
		self.print_status();
		println!("executing instruction : {:016x} at pc 0x{:x}", val, self.pc);
		println!("{}", print::print(decode(val), self.pc as i32));
		let trace = Self::string_registers(self);
		self.registers_trace.push_str(format!("{}\n", trace).as_str());
		
		self.pc += 4;
		
		match inst.opcode {
			// Treatment for: LOAD UPPER IMMEDIATE INSTRUCTION
			RISCV_LUI => {
				self.int_reg.set_reg(inst.rd, inst.imm31_12 as i64);
				Ok(())
			},

			// Treatment for: ADD UPPER IMMEDIATE TO PC INSTRUCTION
			RISCV_AUIPC => {
				self.int_reg.set_reg(inst.rd, self.pc as i64 - 4 + inst.imm31_12 as i64);
				Ok(())
			},

			// Treatement for: JUMP AND LINK INSTRUCTIONS (direct jump)
			RISCV_JAL => {
				self.int_reg.set_reg(inst.rd, self.pc as i64);
				self.pc = (self.pc as i64 + inst.imm21_1_signed as i64 - 4) as u64;
				Ok(())
			},

			// Treatment for: JUMP AND LINK REGISTER INSTRUCTIONS (indirect jump)
			RISCV_JALR => {
				let tmp = self.pc;
				self.pc = (self.int_reg.get_reg(inst.rs1) + inst.imm12_I_signed as i64) as u64 & 0xfffffffe;
				self.int_reg.set_reg(inst.rd, tmp as i64);
				Ok(())
			},

  			// Treatment for: BRANCH INSTRUCTIONS
			RISCV_BR => 	self.branch_instruction(inst),

    		// Treatment for: LOAD INSTRUCTIONS
			RISCV_LD => 	self.load_instruction(inst),

			// Treatment for: STORE INSTRUCTIONS
			RISCV_ST => 	self.store_instruction(inst),

    		// Treatment for: OPI INSTRUCTIONS
			RISCV_OPI =>	self.opi_instruction(inst),

			// Treatment for: OP INSTRUCTIONS
			RISCV_OP => 	self.op_instruction(inst),

			// Treatment for OPIW INSTRUCTIONS
			RISCV_OPIW =>	self.opiw_instruction(inst),

    		// Treatment for: OPW INSTRUCTIONS
			RISCV_OPW =>	self.opw_instruction(inst),

    		// Treatment for: FLOATING POINT INSTRUCTIONS
			RISCV_FP =>		self.fp_instruction(inst),

			// Treatment for: SYSTEM CALLS
			RISCV_SYSTEM => Err(format!("{:x}: System opcode\npc: {:x}", inst.opcode, self.pc))?,

			// Default case
			_ => Err(format!("{:x}: Unknown opcode\npc: {:x}", inst.opcode, self.pc))?
		}
	}

	/// Treatement for Branch instructions
	fn branch_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let op = match inst.funct3 {
			RISCV_BR_BEQ => |a, b| a == b,
			RISCV_BR_BNE => |a, b| a != b,
			RISCV_BR_BLT => |a, b| a < b,
			RISCV_BR_BGE => |a, b| a >= b,
			RISCV_BR_BLTU => |a, b| a < b,
			RISCV_BR_BGEU => |a, b| a >= b,
			_ => unreachable!()
		};
		let rs1 = self.int_reg.get_reg(inst.rs1);
		let rs2 = self.int_reg.get_reg(inst.rs2);
		if op(rs1, rs2) {
			self.pc = (self.pc as i64 + inst.imm13_signed as i64 - 4) as u64;
		}
		Ok(())
	}

	/// Executes RISC-V Load Instructions on the machine
	fn load_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let mut set_reg = |rd, size| {
			let val = self.read_memory(size, (self.int_reg.get_reg(inst.rs1) + inst.imm12_I_signed as i64) as usize) as i64;
			self.int_reg.set_reg(rd, val);
			Ok(())
		};
		match inst.funct3 {
			RISCV_LD_LB | RISCV_LD_LBU =>	set_reg(inst.rd, 1),
			RISCV_LD_LH | RISCV_LD_LHU =>	set_reg(inst.rd, 2),
			RISCV_LD_LW | RISCV_LD_LWU =>	set_reg(inst.rd, 4),
			RISCV_LD_LD =>					set_reg(inst.rd, 8),
			_ => 							Err(format!("In LD switch case, this should never happen... Instr was {}", inst.value).as_str())?
		}
	}

	/// Executes RISC-V Store Instructions on the machine
	fn store_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let mut store = |size| {
			self.write_memory(
				size,
				(self.int_reg.get_reg(inst.rs1) + inst.imm12_S_signed as i64) as usize,
				self.int_reg.get_reg(inst.rs2) as u64
			);
			Ok(())
		};
		match inst.funct3 {
			RISCV_ST_STB => store(1),
			RISCV_ST_STH => store(2),
			RISCV_ST_STW => store(4),
			RISCV_ST_STD => store(8),
			_ => 			Err(format!("In ST switch case, this should never happen... Instr was {}", inst.value).as_str())?
		}
	}

	/// Executes RISC-V Integer Register-Immediate Instructions on the machine
	fn opi_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let rs1 = self.int_reg.get_reg(inst.rs1);
		let imm12 = inst.imm12_I_signed as i64;
		let shamt = inst.shamt as i64;
		let mut compute = |operation: &dyn Fn (i64, i64) -> i64, a, b| {
			self.int_reg.set_reg(inst.rd, operation(a, b));
			Ok(())
		};
		match inst.funct3 {
			RISCV_OPI_ADDI =>	compute(&std::ops::Add::add, rs1, imm12),
			RISCV_OPI_SLTI =>	compute(&|a, b| (a < b) as i64, rs1, imm12),
			RISCV_OPI_XORI =>	compute(&core::ops::BitXor::bitxor, rs1, imm12),
			RISCV_OPI_ORI =>	compute(&core::ops::BitOr::bitor, rs1, imm12),
			RISCV_OPI_ANDI =>	compute(&core::ops::BitAnd::bitand, rs1, imm12),
			RISCV_OPI_SLLI =>	compute(&core::ops::Shl::shl, rs1, imm12),
			RISCV_OPI_SRI =>	if inst.funct7_smaller == RISCV_OPI_SRI_SRLI {
									compute(&|a, b| { (a >> b) & self.shiftmask[inst.shamt as usize] as i64 }, rs1, shamt)
								} else {
									compute(&core::ops::Shr::shr, rs1, shamt)
								}
			_ =>				Err(format!("In OPI switch case, this should never happen... Instr was %x\n {}", inst.value))?
		}
	}

	/// Executes simple RISC-V mathematical operations on the machine
	fn op_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let long_result: i128;
		let unsigned_reg1: u64;
		let unsigned_reg2: u64;
		if inst.funct7 == 1 {
			match inst.funct3 {
				RISCV_OP_M_MUL =>		{
											long_result = (self.int_reg.get_reg(inst.rs1) * self.int_reg.get_reg(inst.rs2)) as i128;
											self.int_reg.set_reg(inst.rd, (long_result & 0xffffffffffffffff) as i64)
										},
				RISCV_OP_M_MULH => 		{
											long_result = (self.int_reg.get_reg(inst.rs1) * self.int_reg.get_reg(inst.rs2)) as i128;
											self.int_reg.set_reg(inst.rd, ((long_result >> 64) & 0xffffffffffffffff) as i64)
										},
				RISCV_OP_M_MULHSU =>	{
											unsigned_reg2 = self.int_reg.get_reg(inst.rs2) as u64;
											long_result   = (self.int_reg.get_reg(inst.rs1) as u64 * unsigned_reg2) as i128;
											self.int_reg.set_reg(inst.rd, ((long_result >> 64) & 0xffffffffffffffff) as i64)
										},
				RISCV_OP_M_MULHU =>		{
											unsigned_reg1 = self.int_reg.get_reg(inst.rs1) as u64;
											unsigned_reg2 = self.int_reg.get_reg(inst.rs2) as u64;
											long_result   = (unsigned_reg1 * unsigned_reg2) as i128;
											self.int_reg.set_reg(inst.rd, ((long_result >> 64) & 0xffffffffffffffff) as i64);
										},
				RISCV_OP_M_DIV =>		self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) / self.int_reg.get_reg(inst.rs2)),
				_ => 					panic!("RISCV_OP : funct7 = 1 (Multiplication) :: Error\n")
			}
		} else {
			match inst.funct3 {
				RISCV_OP_ADD =>			if inst.funct7 == RISCV_OP_ADD_ADD {
											self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) + self.int_reg.get_reg(inst.rs2))
										} else {
											self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) - self.int_reg.get_reg(inst.rs2))
										},
				RISCV_OP_SLL => 		self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) << (self.int_reg.get_reg(inst.rs2) & 0x3f)),
				RISCV_OP_SLT => 		if self.int_reg.get_reg(inst.rs1) < self.int_reg.get_reg(inst.rs2) {
											self.int_reg.set_reg(inst.rd, 1)
										} else {
											self.int_reg.set_reg(inst.rd, 0)
										},
				RISCV_OP_SLTU => 		{
											unsigned_reg1 = self.int_reg.get_reg(inst.rs1) as u64;
											unsigned_reg2 = self.int_reg.get_reg(inst.rs2) as u64;
											if unsigned_reg1 < unsigned_reg2 {
												self.int_reg.set_reg(inst.rd, 1)
											} else {
												self.int_reg.set_reg(inst.rd, 0)
											}
										},
				RISCV_OP_XOR =>			self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) ^ self.int_reg.get_reg(inst.rs2)),
				RISCV_OP_SR =>			self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) >> self.int_reg.get_reg(inst.rs2)), // RISCV_OP_SR_SRL inaccessible
				RISCV_OP_OR =>			self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) | self.int_reg.get_reg(inst.rs2)),
				RISCV_OP_AND =>			self.int_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) & self.int_reg.get_reg(inst.rs2)),
				_ => 					panic!("RISCV_OP undefined case\n")
			}
		}
		Ok(())
	}

	/// Exectutes simple RISC-V *iw instructions on the machine
	fn opiw_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		let local_data = self.int_reg.get_reg(inst.rs1);
		let result = match inst.funct3 {
			RISCV_OPIW_ADDIW =>	local_data + inst.imm12_I_signed as i64,
			RISCV_OPIW_SLLIW =>	local_data << inst.shamt,
			RISCV_OPIW_SRW => 	(local_data >> inst.shamt) & if inst.funct7 == RISCV_OPIW_SRW_SRLIW { self.shiftmask[32 + inst.shamt as usize] as i64 } else { 1 },
			_ => 				Err("In OPI switch case, this should never happen... Instr was {}\n")?,
		};
		self.int_reg.set_reg(inst.rd, result);
		Ok(())
	}

	/// Executes simple RISC-V *w instructions on the machine
	fn opw_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		if inst.funct7 == 1 { // rv64m
			let local_data_a = self.int_reg.get_reg(inst.rs1) & 0xffffffff;
			let local_data_b = self.int_reg.get_reg(inst.rs2) & 0xffffffff;
			let local_data_a_unsigned = self.int_reg.get_reg(inst.rs1) & 0xffffffff;
			let local_data_b_unsigned = self.int_reg.get_reg(inst.rs2) & 0xffffffff;

			// Match case for multiplication operations (in standard extension RV32M)
			match inst.funct3 {
				RISCV_OPW_M_MULW =>		self.int_reg.set_reg(inst.rd, local_data_a * local_data_b),
				RISCV_OPW_M_DIVW =>		self.int_reg.set_reg(inst.rd, local_data_a / local_data_b),
				RISCV_OPW_M_DIVUW => 	self.int_reg.set_reg(inst.rd, local_data_a_unsigned / local_data_b_unsigned),
				RISCV_OPW_M_REMW => 	self.int_reg.set_reg(inst.rd, local_data_a % local_data_b),
				RISCV_OPW_M_REMUW => 	self.int_reg.set_reg(inst.rd, local_data_a_unsigned % local_data_b_unsigned),
				_ => 					panic!("this instruction ({}) doesn't exists", inst.value)
			}
		} else { // others rv64 OPW operations
			let local_dataa = self.int_reg.get_reg(inst.rs1) & 0xffffffff;
			let local_datab = self.int_reg.get_reg(inst.rs2) & 0xffffffff;
			// Match case for base OP operation
			match inst.funct3 {
				RISCV_OPW_ADDSUBW =>	if inst.funct7 == RISCV_OPW_ADDSUBW_ADDW {
											self.int_reg.set_reg(inst.rd, local_dataa + local_datab);
										} else { // SUBW
											self.int_reg.set_reg(inst.rd, local_dataa - local_datab);
										},
				RISCV_OPW_SLLW =>		self.int_reg.set_reg(inst.rd, local_dataa << (local_datab & 0x1f)),
				RISCV_OPW_SRW =>		if inst.funct7 == RISCV_OPW_SRW_SRLW {
											self.int_reg.set_reg(inst.rd, local_dataa >> (local_datab & 0x1f) & self.shiftmask[32 + local_datab as usize] as i64)
										} else { // SRAW
											self.int_reg.set_reg(inst.rd, local_dataa >> (local_datab & 0x1f))
										},
				_ =>					panic!("this instruction ({}) doesn't exist", inst.value)
			}
		}
		Ok(())
	}

	/// Executes simple RISC-V floating point instructions on the machine
	fn fp_instruction(&mut self, inst: Instruction) -> Result<(), MachineError> {
		match inst.funct7 {
			RISCV_FP_ADD =>		self.fp_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) + self.fp_reg.get_reg(inst.rs2)),
			RISCV_FP_SUB =>		self.fp_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) - self.fp_reg.get_reg(inst.rs2)),
			RISCV_FP_MUL =>		self.fp_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) * self.fp_reg.get_reg(inst.rs2)),
			RISCV_FP_DIV =>		self.fp_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) / self.fp_reg.get_reg(inst.rs2)),
			RISCV_FP_SQRT =>	self.fp_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1).sqrt()),
			RISCV_FP_FSGN => 	{
									let local_float = self.fp_reg.get_reg(inst.rs1);
									match inst.funct3 {
										RISCV_FP_FSGN_J =>	if self.fp_reg.get_reg(inst.rs2) < 0f32 {
																self.fp_reg.set_reg(inst.rd, -local_float)
															} else {
																self.fp_reg.set_reg(inst.rd, local_float)
															},
										RISCV_FP_FSGN_JN =>	if self.fp_reg.get_reg(inst.rs2) < 0f32 {
																self.fp_reg.set_reg(inst.rd, local_float)
															} else {
																self.fp_reg.set_reg(inst.rd, -local_float)
															},
										RISCV_FP_FSGN_JX => if (self.fp_reg.get_reg(inst.rs2) < 0.0 && self.fp_reg.get_reg(inst.rs1) >= 0.0) ||
															   (self.fp_reg.get_reg(inst.rs2) >= 0.0 && self.fp_reg.get_reg(inst.rs1) < 0.0) {
																self.fp_reg.set_reg(inst.rd, -local_float)
															} else {
																self.fp_reg.set_reg(inst.rd, local_float)
															},
										_ => 				panic!("this instruction ({}) doesn't exists", inst.value)
									}
								},
			RISCV_FP_MINMAX =>	{
									let r1 = self.fp_reg.get_reg(inst.rs1);
									let r2 = self.fp_reg.get_reg(inst.rs2);
									match inst.funct3 {
										RISCV_FP_MINMAX_MIN =>	self.fp_reg.set_reg(inst.rd, if r1 < r2 {r1} else {r2}),
										RISCV_FP_MINMAX_MAX =>	self.fp_reg.set_reg(inst.rd, if r1 > r2 {r1} else {r2}),
										_ =>					panic!("this instruction ({}) doesn't exists", inst.value)
									}
								},
			RISCV_FP_FCVTW =>	{
									if inst.rs2 == RISCV_FP_FCVTW_W {
										self.int_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) as i64)
									} else {
										self.int_reg.set_reg(inst.rd, (self.fp_reg.get_reg(inst.rs1) as u64) as i64)
									}
								},
			RISCV_FP_FCVTS =>	{
									if inst.rs2 == RISCV_FP_FCVTS_W {
										self.fp_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) as f32);
									} else {
										self.fp_reg.set_reg(inst.rd, (self.int_reg.get_reg(inst.rs1) as u32) as f32);
									}
								},
			RISCV_FP_FMVW =>	self.fp_reg.set_reg(inst.rd, self.int_reg.get_reg(inst.rs1) as f32),
			RISCV_FP_FMVXFCLASS => {
									if inst.funct3 == RISCV_FP_FMVXFCLASS_FMVX {
										self.int_reg.set_reg(inst.rd, self.fp_reg.get_reg(inst.rs1) as i64);
									} else {
										panic!("Fclass instruction is not handled in riscv simulator");
									}
								},
			RISCV_FP_FCMP =>	{
									match inst.funct3 {
										RISCV_FP_FCMP_FEQ =>	self.int_reg.set_reg(inst.rd, (self.fp_reg.get_reg(inst.rs1) == self.fp_reg.get_reg(inst.rs2)) as i64),
										RISCV_FP_FCMP_FLT =>	self.int_reg.set_reg(inst.rd, (self.fp_reg.get_reg(inst.rs1) < self.fp_reg.get_reg(inst.rs2)) as i64),
										RISCV_FP_FCMP_FLE =>	self.int_reg.set_reg(inst.rd, (self.fp_reg.get_reg(inst.rs1) <= self.fp_reg.get_reg(inst.rs2)) as i64),
										_ => 					panic!("this instruction ({}) doesn't exists", inst.value)
									}
								},
			_ => 				panic!("this instruction ({}) doesn't exists", inst.value)
		}
		Ok(())
	}

	/// print memory FOR DEBUG
	/// 
	/// "@"adress [16 bytes]
	pub fn print_memory(&self, from: usize, to: usize) {
		for i in from..to {
			if i%16 == 0 {
				print!("\n@{:04x} ", i);
			}
			print!("{:02x}", self.main_memory[i]);
		}
		println!();
	}

	/// Get value from int register
	pub fn read_int_register(&self, index: usize) -> i64 {
		self.int_reg.get_reg(index as u8)
	}

	/// Get value from float register
	pub fn read_fp_register(&self, index: usize) -> f32 {
		self.fp_reg.get_reg(index as u8)
	}

	/// Write into int register
	pub fn write_int_register(&mut self, index: usize, value: i64) {
		self.int_reg.set_reg(index as u8, value);
	}
	
	/// Write info float register
	pub fn write_fp_register(&mut self, index: usize, value: f32) {
		self.fp_reg.set_reg(index as u8, value);
	}
}

#[cfg(test)]
mod test {
    use std::fs;

    use crate::simulator::{machine::Machine, mem_cmp};

	macro_rules! get_full_path {
		($prefix: expr, $test_name:expr) => {{
			let mut s = String::from("test/machine/");
			s.push_str($prefix);
			s.push_str($test_name);
			s.push_str(".txt");
			&s.to_owned()
		}}
	}

	macro_rules! init_test {
		($a:expr) => {{
			let mut m = Machine::init_machine();
			let end_file_name = { let mut s = String::from($a); s.push_str("End"); s };
			let memory_before = mem_cmp::MemChecker::from(get_full_path!("memory", $a)).unwrap();
			let memory_after = mem_cmp::MemChecker::from(get_full_path!("memory", &end_file_name)).unwrap();
			mem_cmp::MemChecker::fill_memory_from_mem_checker(&memory_before, &mut m);
			m.run();
			let expected_trace = fs::read_to_string(get_full_path!("reg_trace", $a)).unwrap();
			assert!(mem_cmp::MemChecker::compare_machine_memory(&memory_after, &m));
			assert!(expected_trace.contains(m.registers_trace.as_str()));
		}};
	}

	#[test]
	fn test_init_machine() {
		let _ = Machine::init_machine();
	}

	#[test]
	fn test_read_memory() {
		let mut m = Machine::init_machine();
    	m.main_memory[4] = 43;
    	m.main_memory[5] = 150;
		assert_eq!((43 << 8) + 150, m.read_memory(2, 4));
	}

	#[test]
	fn test_write_memory() {
		let mut m = Machine::init_machine();
		m.write_memory(2, 6, (43 << 8) + 150);
		assert_eq!(43, m.main_memory[6]);
		assert_eq!(150, m.main_memory[7]);
		m.write_memory(4, 8, (52 << 24) + (20 << 16) + (43 << 8) + 150);
		assert_eq!(52, m.main_memory[8]);
		assert_eq!(20, m.main_memory[9]);
		assert_eq!(43, m.main_memory[10]);
		assert_eq!(150, m.main_memory[11]);
	}

	#[test]
	fn test_comp() {
		init_test!("Comp")
	}

	#[test]
	fn test_add() {
		init_test!("Add")
	}

	#[test]
	fn test_div() {
		init_test!("Div")
	}

	#[test]
	fn test_if() {
		init_test!("If")
	}

	#[test]
	fn test_jump() {
		init_test!("Jump")
	}

	#[test]
	fn test_mul() {
		init_test!("Mult")
	}

	#[test]
	fn test_ret() {
		init_test!("Ret")
	}

	#[test]
	fn test_sub() {
		init_test!("Sub")
	}

	#[test]
	fn test_switch() {
		init_test!("Switch")
	}
}
