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

import (
	"errors"

	"tinygo.org/x/drivers"
	"tinygo.org/x/drivers/internal/legacy"
)

type AccelRange uint8
type AccelSampleRate uint8
type AccelBandwidth uint8

type GyroRange uint8
type GyroSampleRate uint8

// Device wraps an I2C connection to a LSM6DS3 device.
type Device struct {
	bus             drivers.I2C
	Address         uint16
	accelRange      AccelRange
	accelSampleRate AccelSampleRate
	accelBandWidth  AccelBandwidth
	gyroRange       GyroRange
	gyroSampleRate  GyroSampleRate
	buf             [6]uint8
}

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

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

// New creates a new LSM6DS3 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) Configure(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.AccelBandWidth != 0 {
		d.accelBandWidth = cfg.AccelBandWidth
	} else {
		d.accelBandWidth = ACCEL_BW_100
	}

	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
	}

	data := d.buf[:1]

	if cfg.IsPedometer { // CONFIGURE AS PEDOMETER
		// Configure accelerometer: 2G + 26Hz
		data[0] = uint8(ACCEL_2G) | uint8(ACCEL_SR_26)
		err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
		if err != nil {
			return
		}

		// Configure Zen_G, Yen_G, Xen_G, reset steps
		data[0] = 0x3C
		if cfg.ResetStepCounter {
			data[0] |= 0x02
		}
		err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL10_C, data)
		if err != nil {
			return
		}

		// Enable pedometer
		data[0] = 0x40
		err = legacy.WriteRegister(d.bus, uint8(d.Address), TAP_CFG, data)
		if err != nil {
			return
		}
	} else { // NORMAL USE
		// Configure accelerometer
		data[0] = uint8(d.accelRange) | uint8(d.accelSampleRate) | uint8(d.accelBandWidth)
		err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL1_XL, data)
		if err != nil {
			return
		}

		// Set ODR bit
		err = legacy.ReadRegister(d.bus, uint8(d.Address), CTRL4_C, data)
		if err != nil {
			return
		}
		data[0] = data[0] &^ BW_SCAL_ODR_ENABLED
		data[0] |= BW_SCAL_ODR_ENABLED
		err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL4_C, data)
		if err != nil {
			return
		}

		// Configure gyroscope
		data[0] = uint8(d.gyroRange) | uint8(d.gyroSampleRate)
		err = legacy.WriteRegister(d.bus, uint8(d.Address), CTRL2_G, data)
		if err != nil {
			return
		}
	}

	return nil
}

// Connected returns whether a LSM6DS3 has been found.
// It does a "who am I" request and checks the response.
func (d *Device) Connected() bool {
	data := d.buf[:1]
	legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
	return data[0] == 0x69
}

// 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 := d.buf[:6]
	err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_XL, data)
	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 := d.buf[:6]
	err = legacy.ReadRegister(d.bus, uint8(d.Address), OUTX_L_G, data)
	if err != nil {
		return
	}
	// k comes from "Table 3. Mechanical characteristics" 3 of the datasheet * 1000
	k := int32(4375) // 125DPS
	if d.gyroRange == GYRO_250DPS {
		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 := d.buf[:2]
	err = legacy.ReadRegister(d.bus, uint8(d.Address), OUT_TEMP_L, data)
	if err != nil {
		return
	}
	// From "Table 5. Temperature sensor characteristics"
	// temp = value/16 + 25
	t = 25000 + (int32(int16((int16(data[1])<<8)|int16(data[0])))*125)/2
	return
}

// ReadSteps returns the steps of the pedometer
func (d *Device) ReadSteps() (s int32, err error) {
	data := d.buf[:2]
	err = legacy.ReadRegister(d.bus, uint8(d.Address), STEP_COUNTER_L, data)
	if err != nil {
		return
	}
	s = int32(int16((uint16(data[1]) << 8) | uint16(data[0])))
	return
}
