// +build esp8266

package machine

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

func CPUFrequency() uint32 {
	return 80000000 // 80MHz
}

type PinMode uint8

const (
	PinOutput PinMode = iota
	PinInput
)

// Pins that are fixed by the chip.
const (
	UART_TX_PIN Pin = 1
	UART_RX_PIN Pin = 3
)

// Pin functions are not trivial. The below array maps a pin number (GPIO
// number) to the pad as used in the IO mux.
// Tables with the mapping:
// https://www.esp8266.com/wiki/doku.php?id=esp8266_gpio_pin_allocations#pin_functions
// https://www.espressif.com/sites/default/files/documentation/ESP8266_Pin_List_0.xls
var pinPadMapping = [...]uint8{
	12: 0,
	13: 1,
	14: 2,
	15: 3,
	3:  4,
	1:  5,
	6:  6,
	7:  7,
	8:  8,
	9:  9,
	10: 10,
	11: 11,
	0:  12,
	2:  13,
	4:  14,
	5:  15,
}

// getPad returns the pad number and the register to configure this pad.
func (p Pin) getPad() (uint8, *volatile.Register32) {
	pad := pinPadMapping[p]
	var reg *volatile.Register32
	switch pad {
	case 0:
		reg = &esp.IO_MUX.IO_MUX_MTDI
	case 1:
		reg = &esp.IO_MUX.IO_MUX_MTCK
	case 2:
		reg = &esp.IO_MUX.IO_MUX_MTMS
	case 3:
		reg = &esp.IO_MUX.IO_MUX_MTDO
	case 4:
		reg = &esp.IO_MUX.IO_MUX_U0RXD
	case 5:
		reg = &esp.IO_MUX.IO_MUX_U0TXD
	case 6:
		reg = &esp.IO_MUX.IO_MUX_SD_CLK
	case 7:
		reg = &esp.IO_MUX.IO_MUX_SD_DATA0
	case 8:
		reg = &esp.IO_MUX.IO_MUX_SD_DATA1
	case 9:
		reg = &esp.IO_MUX.IO_MUX_SD_DATA2
	case 10:
		reg = &esp.IO_MUX.IO_MUX_SD_DATA3
	case 11:
		reg = &esp.IO_MUX.IO_MUX_SD_CMD
	case 12:
		reg = &esp.IO_MUX.IO_MUX_GPIO0
	case 13:
		reg = &esp.IO_MUX.IO_MUX_GPIO2
	case 14:
		reg = &esp.IO_MUX.IO_MUX_GPIO4
	case 15:
		reg = &esp.IO_MUX.IO_MUX_GPIO5
	}
	return pad, reg
}

// Configure sets the given pin as output or input pin.
func (p Pin) Configure(config PinConfig) {
	switch config.Mode {
	case PinInput, PinOutput:
		pad, reg := p.getPad()
		if pad >= 12 { // pin 0, 2, 4, 5
			reg.Set(0 << 4) // function 0 at bit position 4
		} else {
			reg.Set(3 << 4) // function 3 at bit position 4
		}
		if config.Mode == PinOutput {
			esp.GPIO.GPIO_ENABLE_W1TS.Set(1 << p)
		} else {
			esp.GPIO.GPIO_ENABLE_W1TC.Set(1 << p)
		}
	}
}

// Get returns the current value of a GPIO pin when the pin is configured as an
// input.
func (p Pin) Get() bool {
	// See this document for details
	// https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf

	return esp.GPIO.GPIO_IN.Get()&(1<<p) != 0
}

// Set sets the output value of this pin to high (true) or low (false).
func (p Pin) Set(value bool) {
	if value {
		esp.GPIO.GPIO_OUT_W1TS.Set(1 << p)
	} else {
		esp.GPIO.GPIO_OUT_W1TC.Set(1 << p)
	}
}

// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
	return &esp.GPIO.GPIO_OUT_W1TS.Reg, 1 << p
}

// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
	return &esp.GPIO.GPIO_OUT_W1TC.Reg, 1 << p
}

// UART0 is a hardware UART that supports both TX and RX.
var UART0 = UART{Buffer: NewRingBuffer()}

type UART struct {
	Buffer *RingBuffer
}

// Configure the UART baud rate. TX and RX pins are fixed by the hardware so
// cannot be modified and will be ignored.
func (uart UART) Configure(config UARTConfig) {
	if config.BaudRate == 0 {
		config.BaudRate = 115200
	}
	esp.UART0.UART_CLKDIV.Set(CPUFrequency() / config.BaudRate)
}

// WriteByte writes a single byte to the output buffer. Note that the hardware
// includes a buffer of 128 bytes which will be used first.
func (uart UART) WriteByte(c byte) error {
	for (esp.UART0.UART_STATUS.Get()>>16)&0xff >= 128 {
		// Wait until the TX buffer has room.
	}
	esp.UART0.UART_FIFO.Set(uint32(c))
	return nil
}
