// Package lsm6ds3tr implements a driver for the LSM6DS3TR
// a 6 axis Inertial Measurement Unit (IMU)
//
// Datasheet: https://www.st.com/resource/en/datasheet/lsm6ds3tr.pdf
package lsm6ds3tr // import "tinygo.org/x/drivers/lsm6ds3tr"

import (
	"errors"

	"tinygo.org/x/drivers"
)

type AccelRange uint8
type AccelSampleRate uint8
type AccelBandwidth uint8

type GyroRange uint8
type GyroSampleRate uint8

// Device wraps an I2C connection to a LSM6DS3TR device.
type Device struct {
	bus             drivers.I2C
	Address         uint16
	accelRange      AccelRange
	accelSampleRate AccelSampleRate
	gyroRange       GyroRange
	gyroSampleRate  GyroSampleRate
	buf             [7]uint8 // up to 6 bytes for read + 1 byte for the register address
}

// Configuration for LSM6DS3TR device.
type Configuration struct {
	AccelRange       AccelRange
	AccelSampleRate  AccelSampleRate
	AccelBandWidth   AccelBandwidth
	GyroRange        GyroRange
	GyroSampleRate   GyroSampleRate
	IsPedometer      bool
	ResetStepCounter bool
}

var errNotConnected = errors.New("lsm6ds3tr: failed to communicate with acel/gyro sensor")

// New creates a new LSM6DS3TR connection. The I2C bus must already be configured.
//
// This function only creates the Device object, it does not touch the device.
func New(bus drivers.I2C) *Device {
	return &Device{
		bus:     bus,
		Address: Address,
	}
}

// Configure sets up the device for communication.
func (d *Device) doConfigure(cfg Configuration) (err error) {

	// Verify unit communication
	if !d.Connected() {
		return errNotConnected
	}

	if cfg.AccelRange != 0 {
		d.accelRange = cfg.AccelRange
	} else {
		d.accelRange = ACCEL_2G
	}

	if cfg.AccelSampleRate != 0 {
		d.accelSampleRate = cfg.AccelSampleRate
	} else {
		d.accelSampleRate = ACCEL_SR_104
	}

	if cfg.GyroRange != 0 {
		d.gyroRange = cfg.GyroRange
	} else {
		d.gyroRange = GYRO_2000DPS
	}

	if cfg.GyroSampleRate != 0 {
		d.gyroSampleRate = cfg.GyroSampleRate
	} else {
		d.gyroSampleRate = GYRO_SR_104
	}

	// Configure accelerometer
	err = d.writeByte(CTRL1_XL, uint8(d.accelRange)|uint8(d.accelSampleRate))
	if err != nil {
		return
	}

	// Enable ODR scaling
	err = d.setBits(CTRL4_C, BW_SCAL_ODR_ENABLED)
	if err != nil {
		return
	}

	// Configure gyroscope
	err = d.writeByte(CTRL2_G, uint8(d.gyroRange)|uint8(d.gyroSampleRate))
	if err != nil {
		return
	}

	return nil
}

// Connected returns whether a LSM6DS3TR has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
	data, err := d.readBytes(WHO_AM_I, 1)
	if err != nil {
		return false
	}
	return data[0] == 0x6A
}

// ReadAcceleration reads the current acceleration from the device and returns
// it in µg (micro-gravity). When one of the axes is pointing straight to Earth
// and the sensor is not moving the returned value will be around 1000000 or
// -1000000.
func (d *Device) ReadAcceleration() (x, y, z int32, err error) {
	data, err := d.readBytes(OUTX_L_XL, 6)
	if err != nil {
		return
	}
	// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
	k := int32(61) // 2G
	if d.accelRange == ACCEL_4G {
		k = 122
	} else if d.accelRange == ACCEL_8G {
		k = 244
	} else if d.accelRange == ACCEL_16G {
		k = 488
	}
	x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * k
	y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * k
	z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * k
	return
}

// ReadRotation reads the current rotation from the device and returns it in
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
// rotation along one axis and while doing so integrate all values over time,
// you would get a value close to 360000000.
func (d *Device) ReadRotation() (x, y, z int32, err error) {
	data, err := d.readBytes(OUTX_L_G, 6)
	if err != nil {
		return
	}
	// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
	k := int32(4375) // 125DPS
	if d.gyroRange == GYRO_245DPS {
		k = 8750
	} else if d.gyroRange == GYRO_500DPS {
		k = 17500
	} else if d.gyroRange == GYRO_1000DPS {
		k = 35000
	} else if d.gyroRange == GYRO_2000DPS {
		k = 70000
	}
	x = int32(int16((uint16(data[1])<<8)|uint16(data[0]))) * k
	y = int32(int16((uint16(data[3])<<8)|uint16(data[2]))) * k
	z = int32(int16((uint16(data[5])<<8)|uint16(data[4]))) * k
	return
}

// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
func (d *Device) ReadTemperature() (t int32, err error) {
	data, err := d.readBytes(OUT_TEMP_L, 2)
	if err != nil {
		return
	}
	// From "Table 5. Temperature sensor characteristics"
	// temp = value/256 + 25
	t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/32
	return
}

func (d *Device) readBytes(reg, size uint8) ([]byte, error) {
	d.buf[0] = reg
	err := d.bus.Tx(d.Address, d.buf[0:1], d.buf[1:size+1])
	if err != nil {
		return nil, err
	}
	return d.buf[1 : size+1], nil
}

func (d *Device) writeByte(reg, value uint8) error {
	d.buf[0] = reg
	d.buf[1] = value
	return d.bus.Tx(d.Address, d.buf[0:2], nil)
}

func (d *Device) setBits(reg, bits uint8) error {
	data, err := d.readBytes(reg, 1)
	if err != nil {
		return err
	}
	return d.writeByte(reg, (data[0]&^bits)|bits)
}
