package main

import (
	"encoding/binary"
	"machine"
	"time"

	"tinygo.org/x/bluetooth"
	"tinygo.org/x/drivers/lsm6ds3tr"
)

var adapter = bluetooth.DefaultAdapter

var isBleConnected bool = false

var isCapturing bool = false

var (
	LSM6DS3TRService = [16]byte{0x4C, 0x53, 0x4D, 0x36, 0x44, 0x53, 0x33, 0x54, 0x52, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
	accelerationData = [16]byte{0x61, 0x63, 0x63, 0x65, 0x6C, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x44, 0x61, 0x74, 0x61}
	unixTimeStampRst = [16]byte{0x75, 0x6E, 0x69, 0x78, 0x54, 0x69, 0x6D, 0x65, 0x53, 0x74, 0x61, 0x6D, 0x70, 0x52, 0x73, 0x74}
	capturingService = [16]byte{0x63, 0x61, 0x70, 0x74, 0x75, 0x72, 0x69, 0x6E, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
	//tempSenseService = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x53, 0x65, 0x6E, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65}
	//temperatureSense = [16]byte{0x74, 0x65, 0x6D, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x53, 0x65, 0x6E, 0x73, 0x65}
)

var currentTimeStamp time.Time = time.Now()
var lastTimeStamp time.Time = time.Now()

const sleepDuration time.Duration = time.Millisecond * 100

func main() {
	// Configure LSM6DS3TR
	machine.I2C0.Configure(machine.I2CConfig{})

	accel := lsm6ds3tr.New(machine.I2C0)
	err := accel.Configure(lsm6ds3tr.Configuration{})
	if err != nil {
		for {
			println("Failed to configure", err.Error())
			time.Sleep(time.Second)
		}
	}

	// Configure Bluetooth
	must("enable BLE stack", adapter.Enable())
	adv := adapter.DefaultAdvertisement()
	must("config adv", adv.Configure(bluetooth.AdvertisementOptions{
		LocalName: "Go Bluetooth",
		ManufacturerData: []bluetooth.ManufacturerDataElement{
			// 0xFFFF: Special Use/Default ID
			// Bluetooth Company Identifiers:
			// https://gist.github.com/angorb/f92f76108b98bb0d81c74f60671e9c67
			{CompanyID: 0xffff, Data: []byte{0x01, 0x02}},
		},
	}))

	adapter.SetConnectHandler(func(device bluetooth.Device, connected bool) {
		if connected {
			isBleConnected = true
		} else {
			isBleConnected = false
			isCapturing = false
		}
	})
	//
	// Start Bluetooth advertisment
	must("start adv", adv.Start())

	var senseCharacteristic bluetooth.Characteristic
	must("add sense service", adapter.AddService(&bluetooth.Service{
		UUID: bluetooth.NewUUID(LSM6DS3TRService),
		Characteristics: []bluetooth.CharacteristicConfig{
			{
				Handle: &senseCharacteristic,
				UUID:   bluetooth.NewUUID(accelerationData),
				// can only send a max amount of 20 bytes in one packet
				//Value: []byte{},
				Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
			},
			{
				UUID:  bluetooth.NewUUID(unixTimeStampRst),
				Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
				WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
					if len(value) != 8 {
						return
					}
					millisFromEpoch := binary.BigEndian.Uint64(value)
					currentTimeStamp = time.Unix(0, int64(millisFromEpoch)*int64(time.Millisecond))
				},
			},
			{
				UUID:  bluetooth.NewUUID(capturingService),
				Flags: bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission | bluetooth.CharacteristicWritePermission | bluetooth.CharacteristicWriteWithoutResponsePermission,
				WriteEvent: func(client bluetooth.Connection, offset int, value []byte) {
					if len(value) != 1 {
						return
					}
					if value[0] == 1 {
						isCapturing = true
					} else {
						isCapturing = false
					}
				},
			},
		},
	}))

	// var tempCharacteristic bluetooth.Characteristic
	// must("add temperature service", adapter.AddService(&bluetooth.Service{
	// 	UUID: bluetooth.NewUUID(tempSenseService),
	// 	Characteristics: []bluetooth.CharacteristicConfig{
	// 		{
	// 			Handle: &tempCharacteristic,
	// 			UUID:   bluetooth.NewUUID(temperatureSense),
	// 			Value:  []byte(tempData),
	// 			Flags:  bluetooth.CharacteristicNotifyPermission | bluetooth.CharacteristicReadPermission,
	// 		},
	// 	},
	// }))

	// Main Loop
	for {
		// Only read and update sensor data
		// with an active bluetooth connection
		// and when `isCapturing` is set to true
		if isBleConnected && isCapturing {
			X, Y, Z, _ := accel.ReadRotation()
			x, y, z, _ := accel.ReadAcceleration()

			arrRot := valuesToByteArray(X, Y, Z, int8(1))
			arrAcc := valuesToByteArray(x, y, z, int8(2))

			currentTimeStamp = currentTimeStamp.Add(time.Now().Sub(lastTimeStamp))
			arrTime := timeStampToByteArray(currentTimeStamp.UnixMilli(), int8(3))

			senseCharacteristic.Write(arrRot)
			senseCharacteristic.Write(arrAcc)
			senseCharacteristic.Write(arrTime)
		}

		if isCapturing {
			lastTimeStamp = time.Now()
		}
		time.Sleep(sleepDuration)
	}
}

func valuesToByteArray(x int32, y int32, z int32, p int8) []byte {
	arr := make([]byte, 13)
	arr[0] = byte(x)
	arr[1] = byte(x >> 8)
	arr[2] = byte(x >> 16)
	arr[3] = byte(x >> 24)
	arr[4] = byte(y)
	arr[5] = byte(y >> 8)
	arr[6] = byte(y >> 16)
	arr[7] = byte(y >> 24)
	arr[8] = byte(z)
	arr[9] = byte(z >> 8)
	arr[10] = byte(z >> 16)
	arr[11] = byte(z >> 24)
	arr[12] = byte(p)
	return arr
}

func timeStampToByteArray(value int64, p int8) []byte {
	arr := make([]byte, 9)
	arr[0] = byte(value)
	arr[1] = byte(value >> 8)
	arr[2] = byte(value >> 16)
	arr[3] = byte(value >> 24)
	arr[4] = byte(value >> 32)
	arr[5] = byte(value >> 40)
	arr[6] = byte(value >> 48)
	arr[7] = byte(value >> 56)
	arr[8] = byte(p)
	return arr
}

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