#!/usr/bin/env python3
# 
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

import ctypes
from enum import IntEnum

from qiling.hw.peripheral import QlPeripheral
from qiling.hw.connectivity import QlConnectivityPeripheral
from qiling.hw.const.stm32f4xx_i2c import I2C_CR1, I2C_CR2, I2C_SR1, I2C_SR2, I2C_DR, I2C_OAR1, I2C_OAR2
from qiling.hw.utils.access import Access, AccessSequence, Action


class STM32F4xxI2c(QlConnectivityPeripheral):
	class Type(ctypes.Structure):
		""" the structure is available in :
			stm32f423xx.h
			stm32f469xx.h
			stm32f427xx.h
			stm32f479xx.h
			stm32f413xx.h
			stm32f429xx.h
			stm32f439xx.h
			stm32f412cx.h
			stm32f412rx.h
			stm32f410tx.h
			stm32f410cx.h
			stm32f412zx.h
			stm32f446xx.h
			stm32f401xc.h
			stm32f437xx.h
			stm32f401xe.h
			stm32f412vx.h
			stm32f410rx.h
			stm32f411xe.h 
		"""

		_fields_ = [
			('CR1'  , ctypes.c_uint32),  # I2C Control register 1,     Address offset: 0x00
			('CR2'  , ctypes.c_uint32),  # I2C Control register 2,     Address offset: 0x04
			('OAR1' , ctypes.c_uint32),  # I2C Own address register 1, Address offset: 0x08
			('OAR2' , ctypes.c_uint32),  # I2C Own address register 2, Address offset: 0x0C
			('DR'   , ctypes.c_uint32),  # I2C Data register,          Address offset: 0x10
			('SR1'  , ctypes.c_uint32),  # I2C Status register 1,      Address offset: 0x14
			('SR2'  , ctypes.c_uint32),  # I2C Status register 2,      Address offset: 0x18
			('CCR'  , ctypes.c_uint32),  # I2C Clock control register, Address offset: 0x1C
			('TRISE', ctypes.c_uint32),  # I2C TRISE register,         Address offset: 0x20
			('FLTR' , ctypes.c_uint32),  # I2C FLTR register,          Address offset: 0x24
		]

	def __init__(self, ql, label, ev_intn=None, er_intn=None):
		super().__init__(ql, label, 2)		

		self.history = AccessSequence()

		self.ev_intn = ev_intn # event interrupt
		self.er_intn = er_intn # error interrupt		
		
		self.reset()

	def reset(self):
		self.instance = self.struct(
			TRISE = 0x0002
		)

	@QlPeripheral.recorder()
	@QlPeripheral.monitor()
	def read(self, offset: int, size: int) -> int:	
		buf = ctypes.create_string_buffer(size)
		ctypes.memmove(buf, ctypes.addressof(self.instance) + offset, size)

		if self.history.match([
			Access(Action.READ, self.struct.SR1.offset),
			Access(Action.READ, self.struct.SR2.offset)
		]):				
			self.instance.SR1 &= ~I2C_SR1.ADDR

		return int.from_bytes(buf.raw, byteorder='little')

	@QlPeripheral.recorder()
	@QlPeripheral.monitor()
	def write(self, offset: int, size: int, value: int):
		if offset in [self.struct.SR1.offset, self.struct.SR2.offset]:
			return		

		if offset == self.struct.CR1.offset:
			self.instance.CR1 = value & I2C_CR1.RW_MASK

			if value & I2C_CR1.START:
				self.generate_start()

			if value & I2C_CR1.STOP:
				self.generate_stop()

			return

		if offset == self.struct.DR.offset:
			self.instance.DR = value & I2C_DR.DR
			self.instance.SR1 &= ~I2C_SR1.TXE

			if self.is_master_mode():
				if self.is_7bit_mode():	
					if self.instance.SR2 & I2C_SR2.TRA:
						self.send_data()

					else:
						self.send_address()					

				# TODO 10-bit mode

			return

		data = (value).to_bytes(size, 'little')
		ctypes.memmove(ctypes.addressof(self.instance) + offset, data, size)

	## I2C Control register 2 (I2C_CR2)
	def send_event_interrupt(self):
		"""
			ITBUFEN: Buffer interrupt enable
				0: TxE = 1 or RxNE = 1 does not generate any interrupt.
				1: TxE = 1 or RxNE = 1 generates Event Interrupt (whatever the state of DMAEN)

			ITEVTEN: Event interrupt enable
				0: Event interrupt disabled
				1: Event interrupt enabled
				This interrupt is generated when:
				- SB = 1 (Master)
				- ADDR = 1 (Master/Slave)
				- ADD10= 1 (Master)
				- STOPF = 1 (Slave)
				- BTF = 1 with no TxE or RxNE event
				- TxE event to 1 if ITBUFEN = 1
				- RxNE event to 1if ITBUFEN = 1
		"""
		if self.ev_intn is None:
			return
		
		if not self.instance.CR2 & I2C_CR2.ITEVTEN:
			return

		BUF_IT = I2C_SR1.TXE|I2C_SR1.RXNE
		SLAVE_IT = I2C_SR1.STOPF|I2C_SR1.ADDR|I2C_SR1.BTF
		MASTER_IT = I2C_SR1.SB|I2C_SR1.ADDR|I2C_SR1.ADD10|I2C_SR1.BTF

		if  (self.instance.CR2 & I2C_CR2.ITBUFEN and self.instance.SR1 & BUF_IT) or \
			(self.is_slave_mode()           and self.instance.SR1 & SLAVE_IT) or \
			(self.is_master_mode()          and self.instance.SR1 & MASTER_IT):
			self.ql.hw.nvic.set_pending(self.ev_intn)

	## I2C Status register 1 (I2C_SR1)
	def generate_start(self):
		"""
		  	SB: Start bit (Master mode)
			0: No Start condition
			1: Start condition generated.
		- Set when a Start condition generated.
		- Cleared by software by reading the SR1 register followed by writing the DR register, or by hardware when PE=0
		"""

		# TODO: generate a start condition
		self.fetch_device_address()
		self.instance.SR1 |= I2C_SR1.SB
		self.instance.CR1 &= ~I2C_CR1.START

		self.set_master_mode()

	def generate_stop(self):
		# TODO: generate a stop condition
		self.instance.CR1 &= ~I2C_CR1.STOP
		
		self.instance.SR1 |= I2C_SR1.STOPF
		self.instance.SR1 &= ~I2C_SR1.ADDR
		
		self.set_slave_mode()
		self.instance.SR2 &= ~I2C_SR2.TRA
	
	def send_address(self):
		if self.instance.DR == self.instance.OAR1 >> 1:
			
			# TODO: send ACK
			self.instance.SR1 &= ~I2C_SR1.SB
			self.instance.SR1 |= I2C_SR1.ADDR | I2C_SR1.TXE | I2C_SR1.AF
			self.instance.SR2 |= I2C_SR2.TRA

	def send_data(self):
		self.instance.SR1 |= I2C_SR1.BTF | I2C_SR1.TXE

		self.send_to_user(self.instance.DR)

	## I2C Status register 2 (I2C_SR2)
	def is_master_mode(self):
		"""
		  	I2C Status register 2 (I2C_SR2) MSL bit
			0: Slave Mode
			1: Master Mode
		"""
		return self.instance.SR2 & I2C_SR2.MSL

	def is_slave_mode(self):
		return not self.is_master_mode()

	def set_master_mode(self):
		"""
			I2C Status register 2 (I2C_SR2) MSL bit
			- Set by hardware as soon as the interface is in Master mode (SB=1)			
		"""
		self.instance.SR2 |= I2C_SR2.MSL
	
	def set_slave_mode(self):		
		"""
			I2C Status register 2 (I2C_SR2) MSL bit
			- Cleared by hardware after detecting a Stop condition on the bus 
			  or a loss of arbitration (ARLO=1), or by hardware when PE=0.
		"""
		self.instance.SR2 &= ~I2C_SR2.MSL

	## I2C Own address register 1 (I2C_OAR1)
	def is_7bit_mode(self):
		return self.instance.OAR2 & I2C_OAR2.ENDUAL or not self.instance.OAR1 & I2C_OAR1.ADDMODE

	def fetch_device_address(self):
		# dual addressing mode
		if self.instance.OAR2 & I2C_OAR2.ENDUAL:
			self.instance.OAR1 = self.device_list[0].address << 1
			self.instance.OAR2 = I2C_OAR2.ENDUAL | (self.device_list[1].address << 1)

		# single device, 10-bit slave address
		elif self.instance.OAR1 & I2C_OAR1.ADDMODE: 
			self.instance.OAR1 = I2C_OAR1.ADDMODE | self.device_list[0].address
		
		# single device, 7-bit slave address
		else:
			self.instance.OAR1 = self.device_list[0].address << 1

	@QlConnectivityPeripheral.device_handler
	def step(self):
		self.send_event_interrupt()
