package main

import (
	"encoding/binary"
	"math"
	"time"

	"tinygo.org/x/bluetooth"
)

const (
	bleCyclingPowerServiceUUID               = 0x1818 // BLE Cycling Power Service UUID
	bleCyclingPowerFeatureCharacteristic     = 0x2A65 // BLE Cycling Power Feature characteristic
	bleCyclingPowerMeasurementCharacteristic = 0x2A63 // BLE Cycling Power Measurement characteristic
	bleSignalDetectedFlag                    = 0x20   // Signal Detected bit in BLE payload header
)

var adapter = bluetooth.DefaultAdapter

func must(err error) {
	if err != nil {
		panic(err)
	}
}

func ExposeBluetooth(events <-chan Event) {
	must(adapter.Enable())

	var powerChar bluetooth.Characteristic

	must(adapter.AddService(&bluetooth.Service{
		UUID: bluetooth.New16BitUUID(bleCyclingPowerServiceUUID),
		Characteristics: []bluetooth.CharacteristicConfig{
			{
				UUID:  bluetooth.New16BitUUID(bleCyclingPowerFeatureCharacteristic),
				Flags: bluetooth.CharacteristicReadPermission,
				Value: []byte{0, 0, 0, 0},
			},
			{
				UUID:   bluetooth.New16BitUUID(bleCyclingPowerMeasurementCharacteristic),
				Flags:  bluetooth.CharacteristicNotifyPermission,
				Handle: &powerChar,
			},
		},
	}))

	must(adapter.DefaultAdvertisement().Configure(bluetooth.AdvertisementOptions{
		LocalName:    "LinuxAntBtBridge",
		ServiceUUIDs: []bluetooth.UUID{bluetooth.New16BitUUID(bleCyclingPowerServiceUUID)},
	}))
	must(adapter.DefaultAdvertisement().Start())
	logMsg("BLE advertising...")

	var totalRevs float64
	var lastCompletedRev float64 // time in 1/1024s of last completed revolution
	lastUpdate := time.Now()
	var elapsedTicks float64
	for event := range events {
		now := time.Now()
		deltaT := now.Sub(lastUpdate).Seconds()
		lastUpdate = now

		watts := event.Power
		rpm := float64(event.Cadence)

		totalRevs += rpm / 60.0 * deltaT
		elapsedTicks += deltaT * 1024.0

		completedRevs := math.Floor(totalRevs)
		// time at which the last completed revolution occurred
		// = current time - fractional part of revolution * time per revolution
		fracRev := totalRevs - completedRevs
		timePerRev := 1024.0 * 60.0 / rpm
		lastCompletedRev = elapsedTicks - fracRev*timePerRev

		payload := make([]byte, 8)
		binary.LittleEndian.PutUint16(payload[0:], bleSignalDetectedFlag)
		binary.LittleEndian.PutUint16(payload[2:], watts)
		binary.LittleEndian.PutUint16(payload[4:], uint16(completedRevs))
		binary.LittleEndian.PutUint16(payload[6:], uint16(lastCompletedRev))

		if _, err := powerChar.Write(payload); err != nil {
			logPrintf("bt: failed to write power characteristic: %v\n", err)
		}
		logPrintf("bt: watts: %d  rpm: %.0f  completedRevs: %.0f  lastRevTime: %.0f processingTime: %v\n",
			watts, rpm, completedRevs, lastCompletedRev, time.Now().Sub(now))
	}
	logMsg("BLE advertising stopped")
}
