from components.ijvm import ijvm

# TODO: Switch MAR as 32bits address (multiply its value by for)
#		then same for SP and LV

class Microprogram:
	
	def __init__(self,components):
		self.c=components # Link components to microprogram
		if self.c["RAM"]==None: # Check if RAM is initialize
			raise RuntimeError("Microprogram initialization fail, RAM is not initialized")
	
	def run(self):
		"""
			Start microprogram
		"""
		self.c["LV"]=(1024)# Place stack to 1024
		self.c["SP"]=(1024-1) # Init SP to LV-1 (because otherwise first element of the stack will be enty because of BIPUSH impl
		
		for i in range(1,30): # Launche first 30 insctructions
			self.fetch() # Fetch
			self.c["PC"]+=1 # INC PC after fetch
			if self.exec()==1: # Execute opcode and halt if return code is 1
				break;
			
	def fetch(self):
		"""
			Fetch next byte from memory into MBR
		"""
		opcode=self.c["RAM"].fetch()
		self.c["MBR"]=opcode # Opcode to MBR
		
	def rd(self):
		"""
			Read data into memory
		"""
		data=self.c["RAM"].read()
		self.c["MDR"]=data
		
	def wr(self):
		"""
			Write data into memory
		"""
		self.c["RAM"].write()

	def exec(self): # TODO: Implement opcode
		"""
			Execute next opcode
		"""
		opcode=self.c["MBRU"] # Get loaded OpCode (/!\ We used MBRU not MBR because MBR is signed)
		if opcode==ijvm["NOP"]: # NOP
			pass
		elif opcode==ijvm["BIPUSH"]: # BIPUSH
			self.fetch();self.c["PC"]+=1 # Fetch byte to push in MBR
			self.c["SP"]+=1 # Increment stack pointer
			self.c["MAR"]=self.c["SP"] # Copy SP to MAR
			self.c["MDR"]=self.c["MBR"] # Set MDR to MBR
			self.c["TOS"]=self.c["MBR"] # Set MDR to MBR
			self.wr() # Write data to stack
		elif opcode==ijvm["IADD"]:
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.c["H"]=self.c["TOS"]
			self.rd()
			self.c["TOS"]=self.c["MDR"]+self.c["H"]
			self.c["MDR"]=self.c["TOS"]
			self.wr()
		elif opcode==ijvm["ISUB"]:
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.c["H"]=self.c["TOS"]
			self.rd()
			self.c["TOS"]=self.c["MDR"]-self.c["H"]
			self.c["MDR"]=self.c["TOS"]
			self.wr()
		elif opcode==ijvm["POP"]:
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.rd()
			self.c["TOS"]=self.c["MDR"]
		elif opcode==ijvm["DUP"]:
			self.c["SP"]+=1
			self.c["MAR"]=self.c["SP"]
			self.c["MDR"]=self.c["TOS"]
			self.wr()
		elif opcode==ijvm["IAND"]:
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.c["H"]=self.c["TOS"]
			self.rd()
			self.c["TOS"]=(self.c["MDR"] & self.c["H"])
			self.c["MDR"]=self.c["TOS"]
			self.wr()
		elif opcode==ijvm["IOR"]:
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.c["H"]=self.c["TOS"]
			self.rd()
			self.c["TOS"]=(self.c["MDR"] | self.c["H"])
			self.c["MDR"]=self.c["TOS"]
			self.wr()
		elif opcode==ijvm["SWAP"]:
			self.c["MAR"]=self.c["SP"]-1
			self.rd()
			self.c["MAR"]=self.c["SP"]
			self.c["H"]=self.c["MDR"]
			self.wr()
			self.c["MDR"]=self.c["TOS"]
			self.c["MAR"]=self.c["SP"]-1
			self.wr()
			self.c["TOS"]=self.c["H"]
		elif opcode==ijvm["ILOAD"]:
			self.fetch();self.c["PC"]+=1 # Fetch local variable  to push onto the stack
			self.c["H"]=self.c["LV"]
			self.c["MAR"]=self.c["MBRU"]+self.c["H"]
			self.rd()
			self.c["SP"]+=1
			self.c["MAR"]=self.c["SP"]
			self.wr()
			self.c["TOS"]=self.c["MDR"]
		elif opcode==ijvm["ISTORE"]:
			self.fetch();self.c["PC"]+=1 # Fetch local variable offset where to store
			self.c["H"]=self.c["LV"]
			self.c["MAR"]=self.c["MBRU"]+self.c["H"]
			self.c["MDR"]=self.c["TOS"]
			self.wr()
			self.c["SP"]-=1
			self.c["MAR"]=self.c["SP"]
			self.rd()
			self.c["TOS"]=self.c["MDR"]
		elif opcode==ijvm["IINC"]:
			self.fetch();self.c["PC"]+=1 # Fetch local variable offset to inc
			self.c["H"]=self.c["LV"]
			self.c["MAR"]=self.c["MBRU"]+self.c["H"]
			self.rd()
			self.fetch();self.c["PC"]+=1 # Fetch inc value
			self.c["H"]=self.c["MDR"]
			self.c["MDR"]=self.c["MBR"]+self.c["H"]
			self.wr()
		elif opcode==ijvm["GOTO"]:
			self.fetch();self.c["PC"]+=1 # Fetch first byte
			self.c["OPC"]=self.c["PC"]-1
			self.c["H"]=self.c["MBR"]<<8
			self.fetch();self.c["PC"]+=1 # Fetch second byte
			self.c["H"]=self.c["MBRU"]|self.c["H"]
			self.c["PC"]=self.c["OPC"]+self.c["H"]
		elif opcode==ijvm["OUT"]:
			self.fetch();self.c["PC"]+=1 # Fetch byte to push in MBR
			print(str(chr(self.c["MBRU"])),end="") # MBRU because no char which are negative
		elif opcode==ijvm["HALT"]:
			return(1)
		else:
			if opcode in ijvm:
				print("Instruction {} not yet implemented.".format(ijvm[opcode]))
			else:
				raise RuntimeError("Instruction {} not found on address {}".format(opcode,self.c["PC"]-1))
		return(0)
	
	def dump(self):
		"""
			Print RAM, stack and registers
		"""
		print("-------------- RAM --------------")
		self.c["RAM"].dump()
		print("------------- Stack -------------")
		self.c["RAM"].dumpRange(self.c["LV"],self.c["SP"])
		print("----------- Registers -----------")
		for key,value in self.c.items():
			if key!="RAM":
				print("{}={}".format(key,value))
		print("---------------------------------")