// Package apa102 implements a driver for the APA102 SPI LED.
//
// Datasheet: https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf
package apa102 // import "tinygo.org/x/drivers/apa102"

import (
	"image/color"

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

const (
	// BGR aka "Blue Green Red" is the current APA102 LED color order.
	BGR = iota

	// BRG aka "Blue Red Green" is the typical APA102 color order from 2015-2017.
	BRG

	// GRB aka "Green Red Blue" is the typical APA102 color order from pre-2015.
	GRB
)

var startFrame = []byte{0x00, 0x00, 0x00, 0x00}

// Device wraps APA102 SPI LEDs.
type Device struct {
	bus   drivers.SPI
	Order int
	buf   [4]byte
}

// New returns a new APA102 driver. Pass in a fully configured SPI bus.
func New(b drivers.SPI) *Device {
	return &Device{bus: b, Order: BGR}
}

// NewSoftwareSPI returns a new APA102 driver that will use a software based
// implementation of the SPI protocol.
func NewSoftwareSPI(sckPin, sdoPin pin.Output, delay uint32) *Device {
	return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
		legacy.ConfigurePinOut(sckPin)
		legacy.ConfigurePinOut(sdoPin)
	}})
}

// WriteColors writes the given RGBA color slice out using the APA102 protocol.
// The A value (Alpha channel) is used for brightness, set to 0xff (255) for maximum.
func (d *Device) WriteColors(cs []color.RGBA) (n int, err error) {
	d.startFrame()

	// write data
	for _, c := range cs {
		// brightness is scaled to 5 bit value
		d.buf[0] = 0xe0 | (c.A >> 3)

		// set the colors
		switch d.Order {
		case BRG:
			d.buf[1] = c.B
			d.buf[2] = c.R
			d.buf[3] = c.G
		case GRB:
			d.buf[1] = c.G
			d.buf[2] = c.R
			d.buf[3] = c.B
		case BGR:
			d.buf[1] = c.B
			d.buf[2] = c.G
			d.buf[3] = c.R
		}
		d.bus.Tx(d.buf[:], nil)
	}

	d.endFrame(len(cs))

	return len(cs), nil
}

// Write the raw bytes using the APA102 protocol.
func (d *Device) Write(buf []byte) (n int, err error) {
	d.startFrame()
	d.bus.Tx(buf, nil)
	d.endFrame(len(buf) / 4)

	return len(buf), nil
}

// startFrame sends the start bytes for a strand of LEDs.
func (d *Device) startFrame() {
	d.bus.Tx(startFrame, nil)
}

// endFrame sends the end frame marker with one extra bit per LED so
// long strands of LEDs receive the necessary termination for updates.
// See https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/
func (d *Device) endFrame(count int) {
	for i := 0; i < count/16; i++ {
		d.bus.Transfer(0xff)
	}
}
