package main

// BLE counter example using the Nordic UART Service (NUS).
// Compatible apps: nRF Toolbox, Serial Bluetooth Terminal (Android/iOS).
// - Subscribe to TX notifications to receive the counter value every second.
// - Send "reset" to the RX characteristic to reset the counter to zero.

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

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

// Nordic UART Service UUIDs (6E400001-B5A3-F393-E0A9-E50E24DCCA9E)
var (
	adapter = bluetooth.DefaultAdapter

	serviceUUID = bluetooth.NewUUID([16]byte{
		0x6E, 0x40, 0x00, 0x01, 0xB5, 0xA3, 0xF3, 0x93,
		0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
	})
	rxUUID = bluetooth.NewUUID([16]byte{
		0x6E, 0x40, 0x00, 0x02, 0xB5, 0xA3, 0xF3, 0x93,
		0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
	})
	txUUID = bluetooth.NewUUID([16]byte{
		0x6E, 0x40, 0x00, 0x03, 0xB5, 0xA3, 0xF3, 0x93,
		0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E,
	})
)

var (
	display   st7789.Device
	connected bool
	counter   int
)

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

func main() {
	initDisplay()
	drawStatus("Advertising...")
	drawCounter(0)

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

	var txChar bluetooth.Characteristic

	must("add service", adapter.AddService(&bluetooth.Service{
		UUID: serviceUUID,
		Characteristics: []bluetooth.CharacteristicConfig{
			{
				UUID:  rxUUID,
				Flags: bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
				WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
					cmd := string(value)
					if cmd == "reset" || cmd == "reset\n" {
						counter = 0
					}
				},
			},
			{
				Handle: &txChar,
				UUID:   txUUID,
				Flags:  bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
			},
		},
	}))

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

	for {
		counter++
		drawCounter(counter)

		// Write sends a BLE notification to subscribed centrals.
		// If no device is subscribed, Write returns an error.
		_, err := txChar.Write([]byte(strconv.Itoa(counter) + "\n"))

		wasConnected := connected
		connected = err == nil
		if wasConnected != connected {
			if connected {
				drawStatus("Connected   ")
			} else {
				drawStatus("Advertising...")
				must("restart adv", adv.Start())
			}
		}

		time.Sleep(time.Second)
	}
}

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 drawStatus(status string) {
	display.FillRectangle(0, 0, 240, 32, black)
	tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 22, "BLE: "+status, cyan)
}

func drawCounter(n int) {
	display.FillRectangle(0, 38, 240, 97, black)
	tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 68, "Counter", gray)
	tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 110, strconv.Itoa(n), yellow)
}

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