//go:build nrf

package machine

import (
	"device/arm"
	"device/nrf"
	"errors"
	"internal/binary"
	"runtime/interrupt"
	"unsafe"
)

const deviceName = nrf.Device

var deviceID [8]byte

// DeviceID returns an identifier that is unique within
// a particular chipset.
//
// The identity is one burnt into the MCU itself, or the
// flash chip at time of manufacture.
//
// It's possible that two different vendors may allocate
// the same DeviceID, so callers should take this into
// account if needing to generate a globally unique id.
//
// The length of the hardware ID is vendor-specific, but
// 8 bytes (64 bits) is common.
func DeviceID() []byte {
	words := make([]uint32, 2)
	words[0] = nrf.FICR.DEVICEID[0].Get()
	words[1] = nrf.FICR.DEVICEID[1].Get()

	for i := 0; i < 8; i++ {
		shift := (i % 4) * 8
		w := i / 4
		deviceID[i] = byte(words[w] >> shift)
	}

	return deviceID[:]
}

const (
	PinInput         PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos)
	PinInputPullup   PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos)
	PinInputPulldown PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pulldown << nrf.GPIO_PIN_CNF_PULL_Pos)
	PinOutput        PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos)
)

type PinChange uint8

// Pin change interrupt constants for SetInterrupt.
const (
	PinRising  PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi
	PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo
	PinToggle  PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle
)

// Callbacks to be called for pins configured with SetInterrupt.
var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin)

// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
	cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled
	port, pin := p.getPortPin()
	port.PIN_CNF[pin].Set(uint32(cfg))
}

// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(high bool) {
	port, pin := p.getPortPin()
	if high {
		port.OUTSET.Set(1 << pin)
	} else {
		port.OUTCLR.Set(1 << pin)
	}
}

// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
func (p Pin) PortMaskSet() (*uint32, uint32) {
	port, pin := p.getPortPin()
	return &port.OUTSET.Reg, 1 << pin
}

// Return the register and mask to disable a given port. This can be used to
// implement bit-banged drivers.
func (p Pin) PortMaskClear() (*uint32, uint32) {
	port, pin := p.getPortPin()
	return &port.OUTCLR.Reg, 1 << pin
}

// Get returns the current value of a GPIO pin when the pin is configured as an
// input or as an output.
func (p Pin) Get() bool {
	port, pin := p.getPortPin()
	return (port.IN.Get()>>pin)&1 != 0
}

// SetInterrupt sets an interrupt to be executed when a particular pin changes
// state. The pin should already be configured as an input, including a pull up
// or down if no external pull is provided.
//
// This call will replace a previously set callback on this pin. You can pass a
// nil func to unset the pin change interrupt. If you do so, the change
// parameter is ignored and can be set to any value (such as 0).
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
	// Some variables to easily check whether a channel was already configured
	// as an event channel for the given pin.
	// This is not just an optimization, this is required: the datasheet says
	// that configuring more than one channel for a given pin results in
	// unpredictable behavior.
	expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk)
	expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<<nrf.GPIOTE_CONFIG_MODE_Pos | uint32(p)<<nrf.GPIOTE_CONFIG_PSEL_Pos

	foundChannel := false
	for i := range nrf.GPIOTE.CONFIG {
		config := nrf.GPIOTE.CONFIG[i].Get()
		if config == 0 || config&expectedConfigMask == expectedConfig {
			// Found an empty GPIOTE channel or one that was already configured
			// for this pin.
			if callback == nil {
				// Disable this channel.
				nrf.GPIOTE.INTENCLR.Set(uint32(1 << uint(i)))
				pinCallbacks[i] = nil
				return nil
			}
			// Enable this channel with the given callback.
			nrf.GPIOTE.INTENCLR.Set(uint32(1 << uint(i)))
			nrf.GPIOTE.CONFIG[i].Set(nrf.GPIOTE_CONFIG_MODE_Event<<nrf.GPIOTE_CONFIG_MODE_Pos |
				uint32(p)<<nrf.GPIOTE_CONFIG_PSEL_Pos |
				uint32(change)<<nrf.GPIOTE_CONFIG_POLARITY_Pos)
			pinCallbacks[i] = callback
			nrf.GPIOTE.INTENSET.Set(uint32(1 << uint(i)))
			foundChannel = true
			break
		}
	}

	if !foundChannel {
		return ErrNoPinChangeChannel
	}

	// Set and enable the GPIOTE interrupt. It's not a problem if this happens
	// more than once.
	intr := interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
		for i := range nrf.GPIOTE.EVENTS_IN {
			if nrf.GPIOTE.EVENTS_IN[i].Get() != 0 {
				nrf.GPIOTE.EVENTS_IN[i].Set(0)
				pin := Pin((nrf.GPIOTE.CONFIG[i].Get() & nrf.GPIOTE_CONFIG_PSEL_Msk) >> nrf.GPIOTE_CONFIG_PSEL_Pos)
				pinCallbacks[i](pin)
			}
		}
	})
	intr.SetPriority(0x40) // interrupt priority 2 (highest priority not reserved by the SoftDevice)
	intr.Enable()

	// Everything was configured correctly.
	return nil
}

// UART on the NRF.
type UART struct {
	Buffer *RingBuffer
}

// UART
var (
	// UART0 is the hardware UART on the NRF SoC.
	_UART0 = UART{Buffer: NewRingBuffer()}
	UART0  = &_UART0
)

// Configure the UART.
func (uart *UART) Configure(config UARTConfig) {
	// Default baud rate to 115200.
	if config.BaudRate == 0 {
		config.BaudRate = 115200
	}

	uart.SetBaudRate(config.BaudRate)

	// Set TX and RX pins
	if config.TX == 0 && config.RX == 0 {
		// Use default pins
		uart.setPins(UART_TX_PIN, UART_RX_PIN)
	} else {
		uart.setPins(config.TX, config.RX)
	}

	nrf.UART0.ENABLE.Set(nrf.UART_ENABLE_ENABLE_Enabled)
	nrf.UART0.TASKS_STARTTX.Set(1)
	nrf.UART0.TASKS_STARTRX.Set(1)
	nrf.UART0.INTENSET.Set(nrf.UART_INTENSET_RXDRDY_Msk)

	// Enable RX IRQ.
	intr := interrupt.New(nrf.IRQ_UART0, _UART0.handleInterrupt)
	intr.SetPriority(0xc0) // low priority
	intr.Enable()
}

// SetBaudRate sets the communication speed for the UART.
func (uart *UART) SetBaudRate(br uint32) {
	// Magic: calculate 'baudrate' register from the input number.
	// Every value listed in the datasheet will be converted to the
	// correct register value, except for 192600. I suspect the value
	// listed in the nrf52 datasheet (0x0EBED000) is incorrectly rounded
	// and should be 0x0EBEE000, as the nrf51 datasheet lists the
	// nonrounded value 0x0EBEDFA4.
	// Some background:
	// https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values/2046#2046
	rate := uint32((uint64(br/400)*uint64(400*0xffffffff/16000000) + 0x800) & 0xffffff000)

	nrf.UART0.BAUDRATE.Set(rate)
}

// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
	nrf.UART0.EVENTS_TXDRDY.Set(0)
	nrf.UART0.TXD.Set(uint32(c))
	for nrf.UART0.EVENTS_TXDRDY.Get() == 0 {
	}
	return nil
}

func (uart *UART) flush() {}

func (uart *UART) handleInterrupt(interrupt.Interrupt) {
	if nrf.UART0.EVENTS_RXDRDY.Get() != 0 {
		uart.Receive(byte(nrf.UART0.RXD.Get()))
		nrf.UART0.EVENTS_RXDRDY.Set(0x0)
	}
}

const i2cTimeout = 0xffff // this is around 29ms on a nrf52

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

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

	i2c.disable()

	// Default I2C bus speed is 100 kHz.
	if config.Frequency == 0 {
		config.Frequency = 100 * KHz
	}
	// Default I2C pins if not set.
	if config.SDA == 0 && config.SCL == 0 {
		config.SDA = SDA_PIN
		config.SCL = SCL_PIN
	}

	// do config
	sclPort, sclPin := config.SCL.getPortPin()
	sclPort.PIN_CNF[sclPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) |
		(nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) |
		(nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) |
		(nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) |
		(nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos))

	sdaPort, sdaPin := config.SDA.getPortPin()
	sdaPort.PIN_CNF[sdaPin].Set((nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) |
		(nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) |
		(nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) |
		(nrf.GPIO_PIN_CNF_DRIVE_S0D1 << nrf.GPIO_PIN_CNF_DRIVE_Pos) |
		(nrf.GPIO_PIN_CNF_SENSE_Disabled << nrf.GPIO_PIN_CNF_SENSE_Pos))

	i2c.setPins(config.SCL, config.SDA)

	i2c.mode = config.Mode
	if i2c.mode == I2CModeController {
		i2c.SetBaudRate(config.Frequency)

		i2c.enableAsController()
	} else {
		i2c.enableAsTarget()
	}

	return nil
}

// SetBaudRate sets the I2C frequency. It has the side effect of also
// enabling the I2C hardware if disabled beforehand.
//
//go:inline
func (i2c *I2C) SetBaudRate(br uint32) error {
	switch {
	case br >= 400*KHz:
		i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K400)
	case br >= 250*KHz:
		i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K250)
	default:
		i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K100)
	}

	return nil
}

// signalStop sends a stop signal to the I2C peripheral and waits for confirmation.
func (i2c *I2C) signalStop() error {
	tries := 0
	i2c.Bus.TASKS_STOP.Set(1)
	for i2c.Bus.EVENTS_STOPPED.Get() == 0 {
		tries++
		if tries >= i2cTimeout {
			return errI2CSignalStopTimeout
		}
	}
	i2c.Bus.EVENTS_STOPPED.Set(0)
	return nil
}

var rngStarted = false

// getRNG returns 32 bits of non-deterministic random data based on internal thermal noise.
// According to Nordic's documentation, the random output is suitable for cryptographic purposes.
func getRNG() (ret uint32, err error) {
	// There's no apparent way to check the status of the RNG peripheral's task, so simply start it
	// to avoid deadlocking while waiting for output.
	if !rngStarted {
		nrf.RNG.TASKS_START.Set(1)
		nrf.RNG.SetCONFIG_DERCEN(nrf.RNG_CONFIG_DERCEN_Enabled)
		rngStarted = true
	}

	// The RNG returns one byte at a time, so stack up four bytes into a single uint32 for return.
	for i := 0; i < 4; i++ {
		// Wait for data to be ready.
		for nrf.RNG.EVENTS_VALRDY.Get() == 0 {
		}
		// Append random byte to output.
		ret = (ret << 8) ^ nrf.RNG.GetVALUE()
		// Unset the EVENTS_VALRDY register to avoid reading the same random output twice.
		nrf.RNG.EVENTS_VALRDY.Set(0)
	}

	return ret, nil
}

// ReadTemperature reads the silicon die temperature of the chip. The return
// value is in milli-celsius.
func ReadTemperature() int32 {
	nrf.TEMP.TASKS_START.Set(1)
	for nrf.TEMP.EVENTS_DATARDY.Get() == 0 {
	}
	temp := int32(nrf.TEMP.TEMP.Get()) * 250 // the returned value is in units of 0.25°C
	nrf.TEMP.EVENTS_DATARDY.Set(0)
	return temp
}

const memoryStart = 0x0

// compile-time check for ensuring we fulfill BlockDevice interface
var _ BlockDevice = flashBlockDevice{}

var Flash flashBlockDevice

type flashBlockDevice struct {
}

// ReadAt reads the given number of bytes from the block device.
func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
	if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
		return 0, errFlashCannotReadPastEOF
	}

	data := unsafe.Slice((*byte)(unsafe.Pointer(FlashDataStart()+uintptr(off))), len(p))
	copy(p, data)

	return len(p), nil
}

// WriteAt writes the given number of bytes to the block device.
// Only double-word (64 bits) length data can be programmed. See rm0461 page 78.
// If the length of p is not long enough it will be padded with 0xFF bytes.
// This method assumes that the destination is already erased.
func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
	if len(p) == 0 {
		return 0, nil // nothing to do (and it would fail in sd_flash_write)
	}

	if FlashDataStart()+uintptr(off)+uintptr(len(p)) > FlashDataEnd() {
		return 0, errFlashCannotWritePastEOF
	}

	address := FlashDataStart() + uintptr(off)
	padded := flashPad(p, int(f.WriteBlockSize()))

	// When the SoftDevice is enabled, access to the flash is restricted and
	// must go through the SoftDevice API.
	if isSoftDeviceEnabled() {
		// Call sd_flash_write, which is SVC_SOC_BASE + 9 in all the
		// SoftDevices I've checked.
		// Documentation:
		// https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html
		numberOfWords := len(padded) / 4 // flash access goes in 32-bit words
		result := arm.SVCall3(0x20+9, address, &padded[0], uint32(numberOfWords))
		if result != 0 {
			// Could not queue flash operation? Not sure when this can
			// happen.
			return 0, flashError
		}

		// Wait until the SoftDevice is finished.
		flashStatus = flashStatusBusy
		for flashStatus == flashStatusBusy {
			handleSoftDeviceEvents()
		}

		// Check whether the operation was successful.
		if flashStatus != flashStatusOk {
			flashStatus = flashStatusOk
			return 0, flashError
		}
		return len(p), nil
	}

	waitWhileFlashBusy()

	nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Wen)
	defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)

	for j := 0; j < len(padded); j += int(f.WriteBlockSize()) {
		// write word
		*(*uint32)(unsafe.Pointer(address)) = binary.LittleEndian.Uint32(padded[j : j+int(f.WriteBlockSize())])
		address += uintptr(f.WriteBlockSize())
		waitWhileFlashBusy()
	}

	return len(padded), nil
}

// Size returns the number of bytes in this block device.
func (f flashBlockDevice) Size() int64 {
	return int64(FlashDataEnd() - FlashDataStart())
}

const writeBlockSize = 4

// WriteBlockSize returns the block size in which data can be written to
// memory. It can be used by a client to optimize writes, non-aligned writes
// should always work correctly.
func (f flashBlockDevice) WriteBlockSize() int64 {
	return writeBlockSize
}

// EraseBlockSize returns the smallest erasable area on this particular chip
// in bytes. This is used for the block size in EraseBlocks.
// It must be a power of two, and may be as small as 1. A typical size is 4096.
func (f flashBlockDevice) EraseBlockSize() int64 {
	return eraseBlockSize()
}

// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (f flashBlockDevice) EraseBlocks(start, len int64) error {
	// When the SoftDevice is enabled, access to the flash is restricted and
	// must go through the SoftDevice API.
	if isSoftDeviceEnabled() {
		for i := range uint32(len) {
			flashPage := uint32(FlashDataStart())/eraseBlockSizeValue + uint32(start) + i

			// Call sd_flash_page_erase, which is SVC_SOC_BASE + 8 in all the
			// SoftDevices I've checked.
			// Documentation:
			// https://docs.nordicsemi.com/bundle/s140_v6.0.0_api/page/group_n_r_f_s_o_c_f_u_n_c_t_i_o_n_s.html#ga9c93dd94a138ad8b5ed3693ea38ffb3e
			result := arm.SVCall1(0x20+8, flashPage)
			if result != 0 {
				// Could not queue flash operation? Not sure when this can
				// happen.
				return flashError
			}

			// Wait until the SoftDevice is finished.
			flashStatus = flashStatusBusy
			for flashStatus == flashStatusBusy {
				handleSoftDeviceEvents()
			}

			// Check whether the operation was successful.
			if flashStatus != flashStatusOk {
				flashStatus = flashStatusOk
				return flashError
			}
		}
		return nil
	}

	// SoftDevice is not used or enabled. Use NVIC directly.

	address := FlashDataStart() + uintptr(start*f.EraseBlockSize())
	waitWhileFlashBusy()

	nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Een)
	defer nrf.NVMC.SetCONFIG_WEN(nrf.NVMC_CONFIG_WEN_Ren)

	for i := start; i < start+len; i++ {
		nrf.NVMC.ERASEPAGE.Set(uint32(address))
		waitWhileFlashBusy()
		address += uintptr(f.EraseBlockSize())
	}

	return nil
}

func waitWhileFlashBusy() {
	for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready {
	}
}

var flashError = errors.New("machine: flash operation failed")

const (
	flashStatusOk = iota
	flashStatusError
	flashStatusBusy
)

var flashStatus uint8 = flashStatusOk

var sdEvent uint32

// Process all queued SoftDevice events. May only be called when the SoftDevice is enabled.
//
// Normally these are handled in the same interrupt where Bluetooth events are
// handled. But in TinyGo, that's complicated. One option would be to put it in
// the tinygo.org/x/bluetooth package, but that would cause a circular
// dependency between the machine and the bluetooth package. Another would be to
// put it here, but let the bluetooth package call handleSoftDeviceEvents, but
// that relies on updating the bluetooth package at the same time. As a
// compromise, these events are handled directly where they are expected (here
// in the machine package). This works in practice since there are only very few
// of such events (at the moment, only flash-related ones which is in the
// machine package anyway).
func handleSoftDeviceEvents() {
	for {
		var result uintptr
		if nrf.Device == "nrf52" || nrf.Device == "nrf52840" || nrf.Device == "nrf52833" {
			// sd_evt_get: SOC_SVC_BASE_NOT_AVAILABLE + 31
			result = arm.SVCall1(0x2C+31, &sdEvent)
		} else {
			return // TODO: nrf51 etc
		}
		if result != 0 {
			// Some error occurred. The only possible error is
			// NRF_ERROR_NOT_FOUND, which means there are no more events.
			return
		}

		// The following events are the same numbers in all SoftDevices I've checked.
		switch sdEvent {
		case 2: // NRF_EVT_FLASH_OPERATION_SUCCESS
			flashStatus = flashStatusOk
		case 3: // NRF_EVT_FLASH_OPERATION_ERROR
			flashStatus = flashStatusError
		}
	}
}
