package apa102

import (
	"tinygo.org/x/drivers/internal/legacy"
	"tinygo.org/x/drivers/internal/pin"
)

// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
// Note: making this unexported for now because it is probable not suitable
// most purposes other than the APA102 package. It might be desirable to make
// this more generic and include it in the TinyGo "machine" package instead.
type bbSPI struct {
	SCK           pin.OutputFunc
	SDO           pin.OutputFunc
	Delay         uint32
	configurePins func()
}

// Configure sets up the SCK and SDO pins as outputs and sets them low
func (s *bbSPI) Configure() {
	if s.configurePins == nil {
		panic(legacy.ErrConfigBeforeInstantiated)
	}
	s.configurePins()
	s.SCK.Low()
	s.SDO.Low()
	if s.Delay == 0 {
		s.Delay = 1
	}
}

// Tx matches signature of machine.SPI.Tx() and is used to send multiple bytes.
// The r slice is ignored and no error will ever be returned.
func (s *bbSPI) Tx(w []byte, r []byte) error {
	s.Configure()
	for _, b := range w {
		s.Transfer(b)
	}
	return nil
}

// delay represents a quarter of the clock cycle
func (s *bbSPI) delay() {
	for i := uint32(0); i < s.Delay; {
		i++
	}
}

// Transfer matches signature of machine.SPI.Transfer() and is used to send a
// single byte. The received data is ignored and no error will ever be returned.
func (s *bbSPI) Transfer(b byte) (byte, error) {
	for i := uint8(0); i < 8; i++ {

		// half clock cycle high to start
		s.SCK.High()
		s.delay()

		// write the value to SDO (MSB first)
		if b&(1<<(7-i)) == 0 {
			s.SDO.Low()
		} else {
			s.SDO.High()
		}
		s.delay()

		// half clock cycle low
		s.SCK.Low()
		s.delay()

		// for actual SPI would try to read the SDI value here
		s.delay()
	}

	return 0, nil
}
