//go:build (esp32c3 || esp32c6 || esp32s3) && !m5stamp_c3

package machine

import (
	"device/esp"
	"runtime/volatile"
	"unsafe"
)

type I2C struct {
	Bus              *esp.I2C_Type
	funcSCL, funcSDA uint32
	useExt1          bool
	txCmdBuf         [8]i2cCommand
}

// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
	Frequency uint32 // in Hz
	SCL       Pin
	SDA       Pin
}

const (
	clkXTAL               = 0
	clkFOSC               = 1
	clkXTALFrequency      = uint32(40e6)
	clkFOSCFrequency      = uint32(17.5e6)
	i2cClkSourceFrequency = clkXTALFrequency
	i2cClkSource          = clkXTAL
)

func (i2c *I2C) Configure(config I2CConfig) error {
	if config.Frequency == 0 {
		config.Frequency = 400 * KHz
	}
	if config.SCL == 0 {
		config.SCL = SCL_PIN
	}
	if config.SDA == 0 {
		config.SDA = SDA_PIN
	}

	i2c.initClock(config)
	i2c.initNoiseFilter()
	i2c.initPins(config)
	i2c.initFrequency(config)
	i2c.startMaster()
	return nil
}

//go:inline
func (i2c *I2C) initClock(config I2CConfig) {
	if !i2c.useExt1 {
		enableI2C0PeriphClock()
	} else {
		initI2CExt1Clock()
	}
	// disable interrupts
	i2c.Bus.INT_CLR.Set(0x3fff)
	i2c.Bus.INT_ENA.ClearBits(0x3fff)

	i2c.Bus.SetCLK_CONF_SCLK_SEL(i2cClkSource)
	i2c.Bus.SetCLK_CONF_SCLK_ACTIVE(1)
	i2c.Bus.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024))
	i2c.Bus.SetCTR_CLK_EN(1)
}

//go:inline
func (i2c *I2C) initNoiseFilter() {
	i2c.Bus.FILTER_CFG.Set(0x377)
}

//go:inline
func (i2c *I2C) initPins(config I2CConfig) {
	config.SDA.configure(PinConfig{Mode: PinOutput}, i2c.funcSDA)
	inFunc(i2c.funcSDA).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDA)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
	config.SDA.Set(true)
	config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
	i2c.Bus.SetCTR_SDA_FORCE_OUT(1)

	config.SCL.configure(PinConfig{Mode: PinOutput}, i2c.funcSCL)
	inFunc(i2c.funcSCL).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SCL)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
	config.SCL.Set(true)
	// Configure the pad with the given IO mux configuration.
	config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER)
	i2c.Bus.SetCTR_SCL_FORCE_OUT(1)
}

//go:inline
func (i2c *I2C) initFrequency(config I2CConfig) {

	clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1
	sclkFreq := i2cClkSourceFrequency / clkmDiv
	halfCycle := sclkFreq / config.Frequency / 2
	// SCL
	sclLow := halfCycle
	sclWaitHigh := uint32(0)
	if config.Frequency > 50000 {
		sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K
	}
	sclHigh := halfCycle - sclWaitHigh
	// SDA
	sdaHold := halfCycle / 4
	sda_sample := halfCycle / 2
	setup := halfCycle
	hold := halfCycle

	i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1)
	i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh)
	i2c.Bus.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25)
	i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup)
	i2c.Bus.SetSCL_STOP_SETUP_TIME(setup)
	i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1)
	i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1)
	i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample)
	i2c.Bus.SetSDA_HOLD_TIME(sdaHold)
}

//go:inline
func (i2c *I2C) startMaster() {
	// FIFO mode for data
	i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0)
	// Reset TX & RX buffers
	i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1)
	i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0)
	i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1)
	i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0)
	// set timeout value
	i2c.Bus.TO.Set(0x10)
	// enable master mode
	i2c.Bus.CTR.Set(0x113)
	i2c.Bus.SetCTR_CONF_UPGATE(1)
	i2c.resetMaster()
}

//go:inline
func (i2c *I2C) resetMaster() {
	// reset FSM
	i2c.Bus.SetCTR_FSM_RST(1)
	// clear the bus
	i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9)
	i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_EN(1)
	i2c.Bus.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1)
	i2c.Bus.SetCTR_CONF_UPGATE(1)
	i2c.Bus.FILTER_CFG.Set(0x377)
	// wait for SCL_RST_SLV_EN
	for i2c.Bus.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 {
	}
	i2c.Bus.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0)
}

type i2cCommandType = uint32
type i2cAck = uint32

const (
	i2cCMD_RSTART   i2cCommandType = 6 << 11
	i2cCMD_WRITE    i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en
	i2cCMD_READ     i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en
	i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK
	i2cCMD_STOP     i2cCommandType = 2 << 11
	i2cCMD_END      i2cCommandType = 4 << 11
)

type i2cCommand struct {
	cmd  i2cCommandType
	data []byte
	head int
}

//go:linkname nanotime runtime.nanotime
func nanotime() int64

func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error {
	const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk
	i2c.Bus.INT_CLR.SetBits(intMask)
	i2c.Bus.INT_ENA.SetBits(intMask)
	i2c.Bus.SetCTR_CONF_UPGATE(1)

	defer func() {
		i2c.Bus.INT_CLR.SetBits(intMask)
		i2c.Bus.INT_ENA.ClearBits(intMask)
	}()

	timeoutNS := int64(timeoutMS) * 1000000
	needAddress := true
	needRestart := false
	readLast := false
	var readTo []byte
	for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); {
		c := &cmd[cmdIdx]

		switch c.cmd {
		case i2cCMD_RSTART:
			reg.Set(i2cCMD_RSTART)
			reg = nextAddress(reg)
			cmdIdx++

		case i2cCMD_WRITE:
			count := 32
			if needAddress {
				needAddress = false
				i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1)
				count--
				i2c.Bus.SLAVE_ADDR.Set(uint32(addr))
				i2c.Bus.SetCTR_CONF_UPGATE(1)
			}
			for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 {
				i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head]))
			}
			reg.Set(i2cCMD_WRITE | uint32(32-count))
			reg = nextAddress(reg)

			if c.head < len(c.data) {
				reg.Set(i2cCMD_END)
				reg = nil
			} else {
				cmdIdx++
			}
			needRestart = true

		case i2cCMD_READ:
			if needAddress {
				needAddress = false
				i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1)
				i2c.Bus.SLAVE_ADDR.Set(uint32(addr))
				reg.Set(i2cCMD_WRITE | 1)
				reg = nextAddress(reg)
			}
			if needRestart {
				// We need to send RESTART again after i2cCMD_WRITE.
				reg.Set(i2cCMD_RSTART)

				reg = nextAddress(reg)
				reg.Set(i2cCMD_WRITE | 1)

				reg = nextAddress(reg)
				i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1)
				needRestart = false
			}
			count := 32
			bytes := len(c.data) - c.head
			// Only last byte in sequence must be sent with ACK set to 1 to indicate end of data.
			split := bytes <= count
			if split {
				bytes--
			}
			if bytes > 32 {
				bytes = 32
			}
			if bytes > 0 {
				reg.Set(i2cCMD_READ | uint32(bytes))
				reg = nextAddress(reg)
			}
			if split {
				readLast = true
				reg.Set(i2cCMD_READLAST | 1)
				reg = nextAddress(reg)
				readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte
				cmdIdx++
			} else {
				reg.Set(i2cCMD_END)
				readTo = c.data[c.head : c.head+bytes]
				reg = nil
			}

		case i2cCMD_STOP:
			reg.Set(i2cCMD_STOP)
			reg = nil
			cmdIdx++
		}
		if reg == nil {
			// transmit now
			i2c.Bus.SetCTR_CONF_UPGATE(1)
			i2c.Bus.SetCTR_TRANS_START(1)
			end := nanotime() + timeoutNS
			var mask uint32
			for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() {
				if nanotime() > end {
					if readTo != nil {
						return errI2CReadTimeout
					}
					return errI2CWriteTimeout
				}
			}
			switch {
			case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast:
				return errI2CAckExpected
			case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0:
				if readTo != nil {
					return errI2CReadTimeout
				}
				return errI2CWriteTimeout
			}
			i2c.Bus.INT_CLR.SetBits(intMask)
			for i := 0; i < len(readTo); i++ {
				readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff)
				c.head++
			}
			readTo = nil
			reg = &i2c.Bus.COMD0
		}
	}
	return nil
}

// Tx does a single I2C transaction at the specified address.
// It clocks out the given address, writes the bytes in w, reads back len(r)
// bytes and stores them in r, and generates a stop condition on the bus.
func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) {
	// timeout in milliseconds.
	const timeout = 40 // 40ms is a reasonable time for a real-time system.

	cmd := i2c.txCmdBuf[:0]
	cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART})
	if len(w) > 0 {
		cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w})
	}
	if len(r) > 0 {
		cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r})
	}
	cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP})

	return i2c.transmit(addr, cmd, timeout)
}

func (i2c *I2C) SetBaudRate(br uint32) error {
	return nil
}

func nextAddress(reg *volatile.Register32) *volatile.Register32 {
	return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4))
}
