// +build avr

package machine

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

const deviceName = avr.DEVICE

const (
	PinInput PinMode = iota
	PinInputPullup
	PinOutput
)

// In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular
// pattern: PINx, DDRx, PORTx in this order without registers in between.
// Therefore, if you know any of them, you can calculate the other two.
//
// For now, I've chosen to let the PORTx register be the one that is returned
// for each specific chip and to calculate the others from that one. Setting an
// output port (done using PORTx) is likely the most common operation and the
// one that is the most time critical. For others, the PINx and DDRx register
// can trivially be calculated using a subtraction.

// Configure sets the pin to input or output.
func (p Pin) Configure(config PinConfig) {
	port, mask := p.getPortMask()
	// The DDRx register can be found by subtracting one from the PORTx
	// register, as this appears to be the case for many (most? all?) AVR chips.
	ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1))
	if config.Mode == PinOutput {
		// set output bit
		ddr.SetBits(mask)

		// Note: if the pin was PinInputPullup before, it'll now be high.
		// Otherwise it will be low.
	} else {
		// configure input: clear output bit
		ddr.ClearBits(mask)

		if config.Mode == PinInput {
			// No pullup (floating).
			// The transition may be one of the following:
			//   output high -> input pullup -> input (safe: output high and input pullup are similar)
			//   output low  -> input        -> input (safe: no extra transition)
			port.ClearBits(mask)
		} else {
			// Pullup.
			// The transition may be one of the following:
			//   output high -> input pullup -> input pullup (safe: no extra transition)
			//   output low  -> input        -> input pullup (possibly problematic)
			// For the last transition (output low -> input -> input pullup),
			// the transition may be problematic in some cases because there is
			// an intermediate floating state (which may cause irratic
			// interrupts, for example). If this is a problem, the application
			// should set the pin high before configuring it as PinInputPullup.
			// We can't do that here because setting it to high as an
			// intermediate state may have other problems.
			port.SetBits(mask)
		}
	}
}

// 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, mask := p.getPortMask()
	// As noted above, the PINx register is always two registers below the PORTx
	// register, so we can find it simply by subtracting two from the PORTx
	// register address.
	pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc
	return (pin.Get() & mask) > 0
}

// Set changes the value of the GPIO pin. The pin must be configured as output.
func (p Pin) Set(value bool) {
	if value { // set bits
		port, mask := p.PortMaskSet()
		port.Set(mask)
	} else { // clear bits
		port, mask := p.PortMaskClear()
		port.Set(mask)
	}
}

// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been
// changed.
func (p Pin) PortMaskSet() (*volatile.Register8, uint8) {
	port, mask := p.getPortMask()
	return port, port.Get() | mask
}

// Return the register and mask to disable a given port. This can be used to
// implement bit-banged drivers.
//
// Warning: there are no separate pin set/clear registers on the AVR. The
// returned mask is only valid as long as no other pin in the same port has been
// changed.
func (p Pin) PortMaskClear() (*volatile.Register8, uint8) {
	port, mask := p.getPortMask()
	return port, port.Get() &^ mask
}

// InitADC initializes the registers needed for ADC.
func InitADC() {
	// set a2d prescaler so we are inside the desired 50-200 KHz range at 16MHz.
	avr.ADCSRA.SetBits(avr.ADCSRA_ADPS2 | avr.ADCSRA_ADPS1 | avr.ADCSRA_ADPS0)

	// enable a2d conversions
	avr.ADCSRA.SetBits(avr.ADCSRA_ADEN)
}

// Configure configures a ADCPin to be able to be used to read data.
func (a ADC) Configure(ADCConfig) {
	return // no pin specific setup on AVR machine.
}

// Get returns the current value of a ADC pin, in the range 0..0xffff. The AVR
// has an ADC of 10 bits precision so the lower 6 bits will be zero.
func (a ADC) Get() uint16 {
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits), masked to only turn on one ADC at a time.
	// set the ADLAR bit (left-adjusted result) to get a value scaled to 16
	// bits. This has the same effect as shifting the return value left by 6
	// bits.
	avr.ADMUX.Set(avr.ADMUX_REFS0 | avr.ADMUX_ADLAR | (uint8(a.Pin) & 0x07))

	// start the conversion
	avr.ADCSRA.SetBits(avr.ADCSRA_ADSC)

	// ADSC is cleared when the conversion finishes
	for ok := true; ok; ok = avr.ADCSRA.HasBits(avr.ADCSRA_ADSC) {
	}

	return uint16(avr.ADCL.Get()) | uint16(avr.ADCH.Get())<<8
}

var Ticks int64     // nanoseconds since start
var tickNanos int64 // nanoseconds per each tick

func InitMonotonicTimer() {
	tickNanos = 0
	Ticks = 0

	interrupt.New(avr.IRQ_TIMER0_OVF, func(i interrupt.Interrupt) {
		Ticks += tickNanos
	})

	// initial initialization of the Timer0
	// - Mask interrupt
	avr.TIMSK0.ClearBits(avr.TIMSK0_TOIE0 | avr.TIMSK0_OCIE0A | avr.TIMSK0_OCIE0B)

	// - Write new values to TCNT2, OCR2x, and TCCR2x.
	avr.TCNT0.Set(0)
	avr.OCR0A.Set(0xff)
	// - Set mode 3
	avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01)
	// - Set prescaler 1
	avr.TCCR0B.Set(avr.TCCR0B_CS00)

	AdjustMonotonicTimer()

	// - Unmask interrupt
	avr.TIMSK0.SetBits(avr.TIMSK0_TOIE0)
}

func AdjustMonotonicTimer() {
	// adjust the tickNanos
	tickNanos = currentTickNanos()
}

func currentTickNanos() int64 {
	// this time depends on clk_IO, prescale, mode and OCR0A
	// assuming the clock source is CPU clock
	prescaler := int64(avr.TCCR0B.Get() & 0x7)
	clock := (int64(1e12) / prescaler) / int64(CPUFrequency())
	mode := avr.TCCR0A.Get() & 0x7

	/*
	 Mode WGM02 WGM01 WGM00 Timer/Counter       TOP  Update of  TOV Flag
	                        Mode of Operation        OCRx at    Set on
	 0    0     0     0     Normal              0xFF Immediate  MAX
	 1    0     0     1     PWM, Phase Correct  0xFF TOP        BOTTOM
	 2    0     1     0     CTC                 OCRA Immediate  MAX
	 3    0     1     1     Fast PWM            0xFF BOTTOM     MAX
	 5    1     0     1     PWM, Phase Correct  OCRA TOP        BOTTOM
	 7    1     1     1     Fast PWM            OCRA BOTTOM     TOP
	*/
	switch mode {
	case 0, 3:
		// normal & fast PWM
		// TOV0 Interrupt when moving from MAX (0xff) to 0x00
		return clock * 256 / 1000
	case 1:
		// Phase Correct PWM
		// TOV0 Interrupt when moving from MAX (0xff) to 0x00
		return clock * 256 * 2 / 1000
	case 2, 7:
		// CTC & fast PWM
		// TOV0 Interrupt when moving from MAX (OCRA) to 0x00
		return clock * int64(avr.OCR0A.Get()) / 1000
	case 5:
		// Phase Correct PWM
		// TOV0 Interrupt when moving from MAX (OCRA) to 0x00
		return clock * int64(avr.OCR0A.Get()) * 2 / 1000
	}

	return clock / 1000 // for unknown
}
