package main

// BLE scanner example.
// Scans for nearby BLE devices and shows their names, addresses and signal
// strength (RSSI) on the display. Press button A to clear the list.

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"
)

const maxDevices = 5

type bleDevice struct {
	name string
	addr string
	rssi int16
}

var (
	adapter = bluetooth.DefaultAdapter
	display st7789.Device
	devices [maxDevices]bleDevice
	count   int
)

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

func main() {
	initDisplay()

	btnA := machine.P1_06
	btnA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})

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

	drawHeader()

	// scan runs in a goroutine; found devices are stored and displayed in the main loop
	go func() {
		adapter.Scan(func(a *bluetooth.Adapter, result bluetooth.ScanResult) {
			name := result.LocalName()
			if name == "" {
				name = result.Address.String()
			}

			// update existing entry if already seen, otherwise add new
			found := false
			for i := 0; i < count; i++ {
				if devices[i].addr == result.Address.String() {
					devices[i].rssi = result.RSSI
					found = true
					break
				}
			}
			if !found && count < maxDevices {
				devices[count] = bleDevice{
					name: name,
					addr: result.Address.String(),
					rssi: result.RSSI,
				}
				count++
			}
		})
	}()

	for {
		// button A clears the list
		if !btnA.Get() {
			count = 0
			display.FillRectangle(0, 32, 240, 103, black)
			time.Sleep(200 * time.Millisecond)
		}

		drawDevices()
		time.Sleep(500 * time.Millisecond)
	}
}

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 drawHeader() {
	tinyfont.WriteLine(&display, &freesans.Bold12pt7b, 10, 24, "BLE Scanner", cyan)
}

func drawDevices() {
	// clear device list area
	display.FillRectangle(0, 32, 240, 103, black)

	if count == 0 {
		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 10, 55, "No devices found...", gray)
		return
	}

	for i := 0; i < count; i++ {
		y := int16(52 + i*20)
		name := devices[i].name
		if len(name) > 16 {
			name = name[:16]
		}
		rssi := " " + strconv.Itoa(int(devices[i].rssi)) + "dB"

		// color by signal strength
		c := white
		if devices[i].rssi > -60 {
			c = green
		} else if devices[i].rssi > -80 {
			c = yellow
		}

		tinyfont.WriteLine(&display, &freesans.Regular9pt7b, 5, y, name+rssi, c)
	}
}

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