//go:build stm32f4 || stm32f1

package machine

// I2C implementation for 'older' STM32 MCUs, including the F1 and F4 series
// of MCUs.

import (
	"device/stm32"
	"unsafe"
)

const (
	flagOVR     = 0x00010800
	flagAF      = 0x00010400
	flagARLO    = 0x00010200
	flagBERR    = 0x00010100
	flagTXE     = 0x00010080
	flagRXNE    = 0x00010040
	flagSTOPF   = 0x00010010
	flagADD10   = 0x00010008
	flagBTF     = 0x00010004
	flagADDR    = 0x00010002
	flagSB      = 0x00010001
	flagDUALF   = 0x00100080
	flagGENCALL = 0x00100010
	flagTRA     = 0x00100004
	flagBUSY    = 0x00100002
	flagMSL     = 0x00100001
)

func (i2c *I2C) hasFlag(flag uint32) bool {
	const mask = 0x0000FFFF
	if uint8(flag>>16) == 1 {
		return i2c.Bus.SR1.HasBits(flag & mask)
	} else {
		return i2c.Bus.SR2.HasBits(flag & mask)
	}
}

func (i2c *I2C) clearFlag(flag uint32) {
	const mask = 0x0000FFFF
	i2c.Bus.SR1.Set(^(flag & mask))
}

// clearFlagADDR reads both status registers to clear any pending ADDR flags.
func (i2c *I2C) clearFlagADDR() {
	i2c.Bus.SR1.Get()
	i2c.Bus.SR2.Get()
}

func (i2c *I2C) waitForFlag(flag uint32, set bool) bool {
	const tryMax = 10000
	hasFlag := false
	for i := 0; !hasFlag && i < tryMax; i++ {
		hasFlag = i2c.hasFlag(flag) == set
	}
	return hasFlag
}

func (i2c *I2C) waitForFlagOrError(flag uint32, set bool) bool {
	const tryMax = 10000
	hasFlag := false
	for i := 0; !hasFlag && i < tryMax; i++ {
		if hasFlag = i2c.hasFlag(flag) == set; !hasFlag {
			// check for ACK failure
			if i2c.hasFlag(flagAF) {
				// generate stop condition
				i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)
				// clear pending flags
				i2c.clearFlag(flagAF)
				return false
			} else if i2c.hasFlag(flagSTOPF) {
				// clear stop flag
				i2c.clearFlag(flagSTOPF)
				return false
			}
		}
	}
	return hasFlag
}

type transferOption uint32

const (
	frameFirst        = 0x00000001
	frameFirstAndNext = 0x00000002
	frameNext         = 0x00000004
	frameFirstAndLast = 0x00000008
	frameLastNoStop   = 0x00000010
	frameLast         = 0x00000020
	frameNoOption     = 0xFFFF0000
)

// I2C fast mode (Fm) duty cycle
const (
	DutyCycle2    = 0
	DutyCycle16x9 = 1
)

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

// Configure is intended to setup the STM32 I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {

	// The following is the required sequence in controller mode.
	// 1. Program the peripheral input clock in I2C_CR2 Register in order to
	//    generate correct timings
	// 2. Configure the clock control registers
	// 3. Configure the rise time register
	// 4. Program the I2C_CR1 register to enable the peripheral
	// 5. Set the START bit in the I2C_CR1 register to generate a Start condition

	// disable I2C interface before any configuration changes
	i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE)

	// reset I2C bus
	i2c.Bus.CR1.SetBits(stm32.I2C_CR1_SWRST)
	i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_SWRST)

	// enable clock for I2C
	enableAltFuncClock(unsafe.Pointer(i2c.Bus))

	// init pins
	if config.SCL == 0 && config.SDA == 0 {
		config.SCL = I2C0_SCL_PIN
		config.SDA = I2C0_SDA_PIN
	}
	i2c.configurePins(config)

	// default to 100 kHz (Sm, standard mode) if no frequency is set
	if config.Frequency == 0 {
		config.Frequency = 100 * KHz
	}

	// configure I2C input clock
	i2c.Bus.CR2.SetBits(i2c.getFreqRange(config))

	// configure rise time
	i2c.Bus.TRISE.Set(i2c.getRiseTime(config))

	// configure clock control
	i2c.Bus.CCR.Set(i2c.getSpeed(config))

	// disable GeneralCall and NoStretch modes
	i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ENGC | stm32.I2C_CR1_NOSTRETCH)

	// enable I2C interface
	i2c.Bus.CR1.SetBits(stm32.I2C_CR1_PE)

	return nil
}

// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
	// TODO: implement
	return errI2CNotImplemented
}

func (i2c *I2C) Tx(addr uint16, w, r []byte) error {

	if err := i2c.controllerTransmit(addr, w); nil != err {
		return err
	}

	if len(r) > 0 {
		if err := i2c.controllerReceive(addr, r); nil != err {
			return err
		}
	}

	return nil
}

func (i2c *I2C) controllerTransmit(addr uint16, w []byte) error {

	if !i2c.waitForFlag(flagBUSY, false) {
		return errI2CBusReadyTimeout
	}

	// disable POS
	i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS)

	pos := 0
	rem := len(w)

	// send peripheral address
	if err := i2c.controllerRequestWrite(addr, frameNoOption); nil != err {
		return err
	}

	// clear ADDR flag
	i2c.clearFlagADDR()

	for rem > 0 {
		// wait for TXE flag set
		if !i2c.waitForFlagOrError(flagTXE, true) {
			return errI2CAckExpected
		}

		// write data to DR
		i2c.Bus.DR.Set(uint32(w[pos]))
		// update counters
		pos++
		rem--

		if i2c.hasFlag(flagBTF) && rem != 0 {
			// write data to DR
			i2c.Bus.DR.Set(uint32(w[pos]))
			// update counters
			pos++
			rem--
		}

		// wait for transfer finished flag BTF set
		if !i2c.waitForFlagOrError(flagBTF, true) {
			return errI2CWriteTimeout
		}
	}

	// generate stop condition
	i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)

	return nil
}

func (i2c *I2C) controllerRequestWrite(addr uint16, option transferOption) error {

	if frameFirstAndLast == option || frameFirst == option || frameNoOption == option {
		// generate start condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START)
	} else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) */ {
		// generate restart condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START)
	}

	// ensure start bit is set
	if !i2c.waitForFlag(flagSB, true) {
		return errI2CSignalStartTimeout
	}

	// send peripheral address
	i2c.Bus.DR.Set(uint32(addr) << 1)

	// wait for address ACK from peripheral
	if !i2c.waitForFlagOrError(flagADDR, true) {
		return errI2CSignalStartTimeout
	}

	return nil
}

func (i2c *I2C) controllerReceive(addr uint16, r []byte) error {

	if !i2c.waitForFlag(flagBUSY, false) {
		return errI2CBusReadyTimeout
	}

	// disable POS
	i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_POS)

	pos := 0
	rem := len(r)

	// send peripheral address
	if err := i2c.controllerRequestRead(addr, frameNoOption); nil != err {
		return err
	}

	switch rem {
	case 0:
		// clear ADDR flag
		i2c.clearFlagADDR()
		// generate stop condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)

	case 1:
		// disable ACK
		i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
		// clear ADDR flag
		i2c.clearFlagADDR()
		// generate stop condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)

	case 2:
		// disable ACK
		i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)
		// enable POS
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_POS)
		// clear ADDR flag
		i2c.clearFlagADDR()

	default:
		// enable ACK
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)
		// clear ADDR flag
		i2c.clearFlagADDR()
	}

	for rem > 0 {
		switch rem {
		case 1:
			// wait until RXNE flag is set
			if !i2c.waitForFlagOrError(flagRXNE, true) {
				return errI2CReadTimeout
			}

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

		case 2:
			// wait until transfer finished flag BTF is set
			if !i2c.waitForFlag(flagBTF, true) {
				return errI2CReadTimeout
			}

			// generate stop condition
			i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

		case 3:
			// wait until transfer finished flag BTF is set
			if !i2c.waitForFlag(flagBTF, true) {
				return errI2CReadTimeout
			}

			// disable ACK
			i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_ACK)

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

			// wait until transfer finished flag BTF is set
			if !i2c.waitForFlag(flagBTF, true) {
				return errI2CReadTimeout
			}

			// generate stop condition
			i2c.Bus.CR1.SetBits(stm32.I2C_CR1_STOP)

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

		default:
			// wait until RXNE flag is set
			if !i2c.waitForFlagOrError(flagRXNE, true) {
				return errI2CReadTimeout
			}

			// read data from DR
			r[pos] = byte(i2c.Bus.DR.Get())

			// update counters
			pos++
			rem--

			if i2c.hasFlag(flagBTF) {
				// read data from DR
				r[pos] = byte(i2c.Bus.DR.Get())

				// update counters
				pos++
				rem--
			}
		}
	}

	return nil
}

func (i2c *I2C) controllerRequestRead(addr uint16, option transferOption) error {

	// enable ACK
	i2c.Bus.CR1.SetBits(stm32.I2C_CR1_ACK)

	if frameFirstAndLast == option || frameFirst == option || frameNoOption == option {
		// generate start condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START)
	} else if false /* (hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) */ {
		// generate restart condition
		i2c.Bus.CR1.SetBits(stm32.I2C_CR1_START)
	}

	// ensure start bit is set
	if !i2c.waitForFlag(flagSB, true) {
		return errI2CSignalStartTimeout
	}

	// send peripheral address
	i2c.Bus.DR.Set(uint32(addr)<<1 | 1)

	// wait for address ACK from peripheral
	if !i2c.waitForFlagOrError(flagADDR, true) {
		return errI2CSignalStartTimeout
	}

	return nil
}
