package main

// BLE LED color control example.
// A mobile app writes 3 bytes (R, G, B) to the color characteristic
// and both WS2812 LEDs light up with that color.
//
// Service UUID:  BADA5501-B5A3-F393-E0A9-E50E24DCCA9E
// Color char:    BADA5502-B5A3-F393-E0A9-E50E24DCCA9E
//
// Compatible apps: nRF Connect, LightBlue (iOS/Android).
// To set color: write 3 hex bytes to the color characteristic, e.g. FF0080 = red:255 green:0 blue:128.

import (
	"image/color"
	"machine"
	"strconv"

	"github.com/tinygo-org/bluetooth"
	"tinygo.org/x/drivers/st7789"
	"tinygo.org/x/drivers/ws2812"
	"tinygo.org/x/tinyfont"
	"tinygo.org/x/tinyfont/freesans"
)

var (
	adapter = bluetooth.DefaultAdapter

	ledServiceUUID = bluetooth.NewUUID([16]byte{
		0xBA, 0xDA, 0x55, 0x01, 0xB5, 0xA3, 0xF3, 0x93,
		0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
	})
	colorCharUUID = bluetooth.NewUUID([16]byte{
		0xBA, 0xDA, 0x55, 0x02, 0xB5, 0xA3, 0xF3, 0x93,
		0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
	})
)

var (
	display   st7789.Device
	leds      ws2812.Device
	connected bool
	ledColor  color.RGBA
)

var (
	black = color.RGBA{0, 0, 0, 255}
	cyan  = color.RGBA{0, 200, 255, 255}
	white = color.RGBA{255, 255, 255, 255}
	gray  = color.RGBA{150, 150, 150, 255}
)

func main() {
	initDisplay()
	initLEDs()
	drawStatus("Advertising...")
	drawColor(color.RGBA{0, 0, 0, 255})

	must("enable BLE", adapter.Enable())

	must("add service", adapter.AddService(&bluetooth.Service{
		UUID: ledServiceUUID,
		Characteristics: []bluetooth.CharacteristicConfig{
			{
				UUID:  colorCharUUID,
				Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
				WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
					if len(value) < 3 {
						return
					}
					// value[0]=R, value[1]=G, value[2]=B
					ledColor = color.RGBA{value[0], value[1], value[2], 255}
					setLEDs(ledColor)
					drawColor(ledColor)

					if !connected {
						connected = true
						drawStatus("Connected   ")
					}
				},
			},
		},
	}))

	adv := adapter.DefaultAdvertisement()
	must("configure adv", adv.Configure(bluetooth.AdvertisementOptions{
		LocalName:    "NiceBadge",
		ServiceUUIDs: []bluetooth.UUID{ledServiceUUID},
	}))
	must("start adv", adv.Start())

	// wait forever; all logic is driven by the BLE WriteEvent callback
	select {}
}

func initDisplay() {
	machine.SPI0.Configure(machine.SPIConfig{
		SCK:       machine.P1_01,
		SDO:       machine.P1_02,
		Frequency: 8000000,
		Mode:      0,
	})
	display = st7789.New(machine.SPI0,
		machine.P1_15, machine.P1_13, machine.P0_10, machine.P0_09)
	display.Configure(st7789.Config{
		Rotation:     st7789.ROTATION_90,
		Width:        135,
		Height:       240,
		RowOffset:    40,
		ColumnOffset: 53,
	})
	display.FillScreen(black)
}

func initLEDs() {
	neo := machine.P1_11
	neo.Configure(machine.PinConfig{Mode: machine.PinOutput})
	leds = ws2812.New(neo)
}

func setLEDs(c color.RGBA) {
	leds.WriteColors([]color.RGBA{c, c})
}

func drawStatus(status string) {
	display.FillRectangle(0, 0, 240, 32, black)
	tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 22, "BLE: "+status, cyan)
}

func drawColor(c color.RGBA) {
	// show a filled rectangle with the current color and its RGB values below
	display.FillRectangle(0, 38, 240, 60, color.RGBA{c.R, c.G, c.B, 255})
	display.FillRectangle(0, 100, 240, 35, black)
	rgb := "R:" + strconv.Itoa(int(c.R)) +
		"  G:" + strconv.Itoa(int(c.G)) +
		"  B:" + strconv.Itoa(int(c.B))
	tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 122, rgb, white)
}

func must(action string, err error) {
	if err != nil {
		panic("failed to " + action + ": " + err.Error())
	}
}
