//go:build stm32

package machine

// The type alias `arrtype` should be defined to either uint32 or uint16
// depending on the size of that register in the MCU's TIM_Type structure.

import (
	"device/stm32"
	"runtime/interrupt"
	"runtime/volatile"
)

const PWM_MODE1 = 0x6

type TimerCallback func()
type ChannelCallback func(channel uint8)

type PinFunction struct {
	Pin     Pin
	AltFunc uint8
}

type TimerChannel struct {
	Pins []PinFunction
}

type TIM struct {
	EnableRegister *volatile.Register32
	EnableFlag     uint32
	Device         *stm32.TIM_Type
	Channels       [4]TimerChannel
	UpInterrupt    interrupt.Interrupt
	OCInterrupt    interrupt.Interrupt

	wraparoundCallback TimerCallback
	channelCallbacks   [4]ChannelCallback

	busFreq uint64
}

// Configure enables and configures this PWM.
func (t *TIM) Configure(config PWMConfig) error {
	// Enable device
	t.EnableRegister.SetBits(t.EnableFlag)

	err := t.setPeriod(config.Period, true)
	if err != nil {
		return err
	}

	// Auto-repeat
	t.Device.EGR.SetBits(stm32.TIM_EGR_UG)

	// Enable the timer
	t.Device.CR1.SetBits(stm32.TIM_CR1_CEN | stm32.TIM_CR1_ARPE)

	return nil
}

func (t *TIM) Count() uint32 {
	return uint32(t.Device.CNT.Get())
}

// SetWraparoundInterrupt configures a callback to be called each
// time the timer 'wraps-around'.
//
// For example, if `Configure(PWMConfig{Period:1000000})` is used,
// to set the timer period to 1ms, this callback will be called every
// 1ms.
func (t *TIM) SetWraparoundInterrupt(callback TimerCallback) error {
	// Disable this interrupt to prevent race conditions
	//t.UpInterrupt.Disable()

	// Ensure the interrupt handler for Update events is registered
	t.UpInterrupt = t.registerUPInterrupt()

	// Clear update flag
	t.Device.SR.ClearBits(stm32.TIM_SR_UIF)

	t.wraparoundCallback = callback
	t.UpInterrupt.SetPriority(0xc1)
	t.UpInterrupt.Enable()

	// Enable the hardware interrupt
	t.Device.DIER.SetBits(stm32.TIM_DIER_UIE)

	return nil
}

// Sets a callback to be called when a channel reaches it's set-point.
//
// For example, if `t.Set(ch, t.Top() / 4)` is used then the callback will
// be called every quarter-period of the timer's base Period.
func (t *TIM) SetMatchInterrupt(channel uint8, callback ChannelCallback) error {
	t.channelCallbacks[channel] = callback

	// Ensure the interrupt handler for Output Compare events is registered
	t.OCInterrupt = t.registerOCInterrupt()

	// Clear the interrupt flag
	t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel)

	// Enable the interrupt
	t.OCInterrupt.SetPriority(0xc1)
	t.OCInterrupt.Enable()

	// Enable the hardware interrupt
	t.Device.DIER.SetBits(stm32.TIM_DIER_CC1IE << channel)

	return nil
}

// SetPeriod updates the period of this PWM peripheral.
// To set a particular frequency, use the following formula:
//
//	period = 1e9 / frequency
//
// If you use a period of 0, a period that works well for LEDs will be picked.
//
// SetPeriod will not change the prescaler, but also won't change the current
// value in any of the channels. This means that you may need to update the
// value for the particular channel.
//
// Note that you cannot pick any arbitrary period after the PWM peripheral has
// been configured. If you want to switch between frequencies, pick the lowest
// frequency (longest period) once when calling Configure and adjust the
// frequency here as needed.
func (t *TIM) SetPeriod(period uint64) error {
	return t.setPeriod(period, false)
}

func (t *TIM) setPeriod(period uint64, updatePrescaler bool) error {
	var top uint64
	if period == 0 {
		top = ARR_MAX
	} else {
		top = (period / 1000) * (t.busFreq / 1000) / 1000
	}

	var psc uint64
	if updatePrescaler {
		if top > ARR_MAX*PSC_MAX {
			return ErrPWMPeriodTooLong
		}

		// Select the minimum PSC that scales the ARR value into
		// range to maintain precision in ARR for changing frequencies
		// later
		psc = ceil(top, ARR_MAX)
		top = top / psc

		t.Device.PSC.Set(uint32(psc - 1))
	} else {
		psc = uint64(t.Device.PSC.Get()) + 1
		top = top / psc

		if top > ARR_MAX {
			return ErrPWMPeriodTooLong
		}
	}

	t.Device.ARR.Set(arrtype(top - 1))
	return nil
}

// Top returns the current counter top, for use in duty cycle calculation. It
// will only change with a call to Configure or SetPeriod, otherwise it is
// constant.
//
// The value returned here is hardware dependent. In general, it's best to treat
// it as an opaque value that can be divided by some number and passed to
// pwm.Set (see pwm.Set for more information).
func (t *TIM) Top() uint32 {
	return uint32(t.Device.ARR.Get()) + 1
}

// Channel returns a PWM channel for the given pin.
func (t *TIM) Channel(pin Pin) (uint8, error) {

	for chi, ch := range t.Channels {
		for _, p := range ch.Pins {
			if p.Pin == pin {
				t.configurePin(uint8(chi), p)
				//p.Pin.ConfigureAltFunc(PinConfig{Mode: PinModePWMOutput}, p.AltFunc)
				return uint8(chi), nil
			}
		}
	}

	return 0, ErrInvalidOutputPin
}

// Set updates the channel value. This is used to control the channel duty
// cycle. For example, to set it to a 25% duty cycle, use:
//
//	t.Set(ch, t.Top() / 4)
//
// ch.Set(0) will set the output to low and ch.Set(ch.Top()) will set the output
// to high, assuming the output isn't inverted.
func (t *TIM) Set(channel uint8, value uint32) {
	t.enableMainOutput()

	ccr := t.channelCCR(channel)
	ccmr, offset := t.channelCCMR(channel)

	// Disable interrupts whilst programming to prevent spurious OC interrupts
	mask := interrupt.Disable()

	// Set the PWM to Mode 1 (active below set value, inactive above)
	// Preload is disabled so we can change OC value within one update period.
	var ccmrVal uint32
	ccmrVal |= PWM_MODE1 << stm32.TIM_CCMR1_Output_OC1M_Pos
	ccmr.ReplaceBits(ccmrVal, 0xFF, offset)

	// Set the compare value
	ccr.Set(arrtype(value))

	// Enable the channel (if not already)
	t.Device.CCER.ReplaceBits(stm32.TIM_CCER_CC1E, 0xD, channel*4)

	// Force update
	t.Device.EGR.SetBits(stm32.TIM_EGR_CC1G << channel)

	// Reset Interrupt Flag
	t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel)

	// Restore interrupts
	interrupt.Restore(mask)
}

// Unset disables a channel, including any configured interrupts.
func (t *TIM) Unset(channel uint8) {
	// Disable interrupts whilst programming to prevent spurious OC interrupts
	mask := interrupt.Disable()

	// Disable the channel
	t.Device.CCER.ReplaceBits(0, 0xD, channel*4)

	// Reset to zero value
	ccr := t.channelCCR(channel)
	ccr.Set(0)

	// Disable the hardware interrupt
	t.Device.DIER.ClearBits(stm32.TIM_DIER_CC1IE << channel)

	// Clear the interrupt flag
	t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF << channel)

	// Restore interrupts
	interrupt.Restore(mask)
}

// SetInverting sets whether to invert the output of this channel.
// Without inverting, a 25% duty cycle would mean the output is high for 25% of
// the time and low for the rest. Inverting flips the output as if a NOT gate
// was placed at the output, meaning that the output would be 25% low and 75%
// high with a duty cycle of 25%.
func (t *TIM) SetInverting(channel uint8, inverting bool) {
	// Enable the channel (if not already)

	var val = uint32(0)
	if inverting {
		val |= stm32.TIM_CCER_CC1P
	}

	t.Device.CCER.ReplaceBits(val, stm32.TIM_CCER_CC1P_Msk, channel*4)
}

func (t *TIM) handleUPInterrupt(interrupt.Interrupt) {
	if t.Device.SR.HasBits(stm32.TIM_SR_UIF) {
		// clear the update flag
		t.Device.SR.ClearBits(stm32.TIM_SR_UIF)

		if t.wraparoundCallback != nil {
			t.wraparoundCallback()
		}
	}
}

func (t *TIM) handleOCInterrupt(interrupt.Interrupt) {
	if t.Device.SR.HasBits(stm32.TIM_SR_CC1IF) {
		if t.channelCallbacks[0] != nil {
			t.channelCallbacks[0](0)
		}
	}
	if t.Device.SR.HasBits(stm32.TIM_SR_CC2IF) {
		if t.channelCallbacks[1] != nil {
			t.channelCallbacks[1](1)
		}
	}
	if t.Device.SR.HasBits(stm32.TIM_SR_CC3IF) {
		if t.channelCallbacks[2] != nil {
			t.channelCallbacks[2](2)
		}
	}
	if t.Device.SR.HasBits(stm32.TIM_SR_CC4IF) {
		if t.channelCallbacks[3] != nil {
			t.channelCallbacks[3](3)
		}
	}

	// Reset interrupt flags
	t.Device.SR.ClearBits(stm32.TIM_SR_CC1IF | stm32.TIM_SR_CC2IF | stm32.TIM_SR_CC3IF | stm32.TIM_SR_CC4IF)
}

func (t *TIM) channelCCR(channel uint8) *arrRegType {
	switch channel {
	case 0:
		return &t.Device.CCR1
	case 1:
		return &t.Device.CCR2
	case 2:
		return &t.Device.CCR3
	case 3:
		return &t.Device.CCR4
	}

	return nil
}

func (t *TIM) channelCCMR(channel uint8) (reg *volatile.Register32, offset uint8) {
	switch channel {
	case 0:
		return &t.Device.CCMR1_Output, 0
	case 1:
		return &t.Device.CCMR1_Output, 8
	case 2:
		return &t.Device.CCMR2_Output, 0
	case 3:
		return &t.Device.CCMR2_Output, 8
	}

	return nil, 0
}

//go:inline
func ceil(num uint64, denom uint64) uint64 {
	return (num + denom - 1) / denom
}
