//go:build avr && atmega

package machine

import (
	"device/avr"
	"runtime/interrupt"
	"runtime/volatile"
	"unsafe"
)

// I2C on AVR.
type I2C struct {
	srReg *volatile.Register8
	brReg *volatile.Register8
	crReg *volatile.Register8
	drReg *volatile.Register8

	srPS0 byte
	srPS1 byte
	crEN  byte
	crINT byte
	crSTO byte
	crEA  byte
	crSTA byte
}

// I2CConfig is used to store config info for I2C.
type I2CConfig struct {
	Frequency uint32
}

// Configure is intended to setup the I2C interface.
func (i2c *I2C) Configure(config I2CConfig) error {
	// Default I2C bus speed is 100 kHz.
	if config.Frequency == 0 {
		config.Frequency = 100 * KHz
	}

	// Activate internal pullups for twi.
	avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D))

	return i2c.SetBaudRate(config.Frequency)
}

// SetBaudRate sets the communication speed for I2C.
func (i2c *I2C) SetBaudRate(br uint32) error {
	// Initialize twi prescaler and bit rate.
	i2c.srReg.SetBits((i2c.srPS0 | i2c.srPS1))

	// twi bit rate formula from atmega128 manual pg. 204:
	// SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR))
	// NOTE: TWBR should be 10 or higher for controller mode.
	// It is 72 for a 16mhz board with 100kHz TWI
	i2c.brReg.Set(uint8(((CPUFrequency() / br) - 16) / 2))

	// Enable twi module.
	i2c.crReg.Set(i2c.crEN)

	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) error {
	if len(w) != 0 {
		i2c.start(uint8(addr), true) // start transmission for writing
		for _, b := range w {
			i2c.writeByte(b)
		}
	}
	if len(r) != 0 {
		i2c.start(uint8(addr), false) // re-start transmission for reading
		for i := range r {            // read each char
			r[i] = i2c.readByte()
		}
	}
	if len(w) != 0 || len(r) != 0 {
		// Stop the transmission after it has been started.
		i2c.stop()
	}
	return nil
}

// start starts an I2C communication session.
func (i2c *I2C) start(address uint8, write bool) {
	// Clear TWI interrupt flag, put start condition on SDA, and enable TWI.
	i2c.crReg.Set((i2c.crINT | i2c.crSTA | i2c.crEN))

	// Wait till start condition is transmitted.
	for !i2c.crReg.HasBits(i2c.crINT) {
	}

	// Write 7-bit shifted peripheral address.
	address <<= 1
	if !write {
		address |= 1 // set read flag
	}
	i2c.writeByte(address)
}

// stop ends an I2C communication session.
func (i2c *I2C) stop() {
	// Send stop condition.
	i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crSTO)

	// Wait for stop condition to be executed on bus.
	for !i2c.crReg.HasBits(i2c.crSTO) {
	}
}

// writeByte writes a single byte to the I2C bus.
func (i2c *I2C) writeByte(data byte) error {
	// Write data to register.
	i2c.drReg.Set(data)

	// Clear TWI interrupt flag and enable TWI.
	i2c.crReg.Set(i2c.crEN | i2c.crINT)

	// Wait till data is transmitted.
	for !i2c.crReg.HasBits(i2c.crINT) {
	}
	return nil
}

// readByte reads a single byte from the I2C bus.
func (i2c *I2C) readByte() byte {
	// Clear TWI interrupt flag and enable TWI.
	i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crEA)

	// Wait till read request is transmitted.
	for !i2c.crReg.HasBits(i2c.crINT) {
	}

	return byte(i2c.drReg.Get())
}

// Always use UART0 as the serial output.
var DefaultUART = UART0

// UART
var (
	// UART0 is the hardware serial port on the AVR.
	UART0  = &_UART0
	_UART0 = UART{
		Buffer: NewRingBuffer(),

		dataReg:    avr.UDR0,
		baudRegH:   avr.UBRR0H,
		baudRegL:   avr.UBRR0L,
		statusRegA: avr.UCSR0A,
		statusRegB: avr.UCSR0B,
		statusRegC: avr.UCSR0C,
	}
)

func init() {
	// Register the UART interrupt.
	interrupt.New(irq_USART0_RX, _UART0.handleInterrupt)
}

// UART on the AVR.
type UART struct {
	Buffer *RingBuffer

	dataReg  *volatile.Register8
	baudRegH *volatile.Register8
	baudRegL *volatile.Register8

	statusRegA *volatile.Register8
	statusRegB *volatile.Register8
	statusRegC *volatile.Register8
}

// Configure the UART on the AVR. Defaults to 9600 baud on Arduino.
func (uart *UART) Configure(config UARTConfig) {
	if config.BaudRate == 0 {
		config.BaudRate = 9600
	}

	// Prescale formula for u2x mode from AVR MiniCore source code.
	// Same as formula from specification but taking into account rounding error.
	ps := (CPUFrequency()/4/config.BaudRate - 1) / 2
	uart.statusRegA.SetBits(avr.UCSR0A_U2X0)

	// Hardcoded exception for 57600 for compatibility with older bootloaders.
	// Also, prescale cannot be > 4095, so switch back to non-u2x mode if the baud rate is too low.
	if (CPUFrequency() == 16000000 && config.BaudRate == 57600) || ps > 0xfff {
		ps = (CPUFrequency()/8/config.BaudRate - 1) / 2
		uart.statusRegA.ClearBits(avr.UCSR0A_U2X0)
	}

	uart.baudRegH.Set(uint8(ps >> 8))
	uart.baudRegL.Set(uint8(ps & 0xff))

	// enable RX, TX and RX interrupt
	uart.statusRegB.Set(avr.UCSR0B_RXEN0 | avr.UCSR0B_TXEN0 | avr.UCSR0B_RXCIE0)

	// 8-bits data
	uart.statusRegC.Set(avr.UCSR0C_UCSZ01 | avr.UCSR0C_UCSZ00)
}

func (uart *UART) handleInterrupt(intr interrupt.Interrupt) {
	// Read register to clear it.
	data := uart.dataReg.Get()

	// Ensure no error.
	if !uart.statusRegA.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
		// Put data from UDR register into buffer.
		uart.Receive(byte(data))
	}
}

// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
	// Wait until UART buffer is not busy.
	for !uart.statusRegA.HasBits(avr.UCSR0A_UDRE0) {
	}
	uart.dataReg.Set(c) // send char
	return nil
}

func (uart *UART) flush() {}

// SPIConfig is used to store config info for SPI.
type SPIConfig struct {
	Frequency uint32
	LSBFirst  bool
	Mode      uint8
}

// SPI is for the Serial Peripheral Interface
// Data is taken from http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf page 169 and following
type SPI struct {
	// The registers for the SPIx port set by the chip
	spcr *volatile.Register8
	spdr *volatile.Register8
	spsr *volatile.Register8

	spcrR0   byte
	spcrR1   byte
	spcrCPHA byte
	spcrCPOL byte
	spcrDORD byte
	spcrSPE  byte
	spcrMSTR byte

	spsrI2X  byte
	spsrSPIF byte

	// The io pins for the SPIx port set by the chip
	sck Pin
	sdi Pin
	sdo Pin
	cs  Pin
}

// Configure is intended to setup the SPI interface.
func (s SPI) Configure(config SPIConfig) error {

	// This is only here to help catch a bug with the configuration
	// where a machine missed a value.
	if s.spcr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
		s.spsr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
		s.spdr == (*volatile.Register8)(unsafe.Pointer(uintptr(0))) ||
		s.sck == 0 || s.sdi == 0 || s.sdo == 0 || s.cs == 0 {
		return errSPIInvalidMachineConfig
	}

	// Make the defaults meaningful
	if config.Frequency == 0 {
		config.Frequency = 4000000
	}

	// Default all port configuration bits to 0 for simplicity
	s.spcr.Set(0)
	s.spsr.Set(0)

	// Setup pins output configuration
	s.sck.Configure(PinConfig{Mode: PinOutput})
	s.sdi.Configure(PinConfig{Mode: PinInput})
	s.sdo.Configure(PinConfig{Mode: PinOutput})

	// Prevent CS glitches if the pin is enabled Low (0, default)
	s.cs.High()
	// If the CS pin is not configured as output the SPI port operates in
	// slave mode.
	s.cs.Configure(PinConfig{Mode: PinOutput})

	frequencyDivider := CPUFrequency() / config.Frequency

	switch {
	case frequencyDivider >= 128:
		s.spcr.SetBits(s.spcrR0 | s.spcrR1)
	case frequencyDivider >= 64:
		s.spcr.SetBits(s.spcrR1)
	case frequencyDivider >= 32:
		s.spcr.SetBits(s.spcrR1)
		s.spsr.SetBits(s.spsrI2X)
	case frequencyDivider >= 16:
		s.spcr.SetBits(s.spcrR0)
	case frequencyDivider >= 8:
		s.spcr.SetBits(s.spcrR0)
		s.spsr.SetBits(s.spsrI2X)
	case frequencyDivider >= 4:
		// The clock is already set to all 0's.
	default: // defaults to fastest which is /2
		s.spsr.SetBits(s.spsrI2X)
	}

	switch config.Mode {
	case Mode1:
		s.spcr.SetBits(s.spcrCPHA)
	case Mode2:
		s.spcr.SetBits(s.spcrCPHA)
	case Mode3:
		s.spcr.SetBits(s.spcrCPHA | s.spcrCPOL)
	default: // default is mode 0
	}

	if config.LSBFirst {
		s.spcr.SetBits(s.spcrDORD)
	}

	// enable SPI, set controller, set clock rate
	s.spcr.SetBits(s.spcrSPE | s.spcrMSTR)

	return nil
}

// Transfer writes the byte into the register and returns the read content
func (s SPI) Transfer(b byte) (byte, error) {
	s.spdr.Set(uint8(b))

	for !s.spsr.HasBits(s.spsrSPIF) {
	}

	return byte(s.spdr.Get()), nil
}
