// Package veml6070 provides a driver for the VEML6070 digital UV light sensor
// by Vishay.
//
// Datasheet:
// https://www.vishay.com/docs/84277/veml6070.pdf
// Application Notes:
// https://www.vishay.com/docs/84310/designingveml6070.pdf
package veml6070 // import "tinygo.org/x/drivers/veml6070"

import (
	"time"

	"tinygo.org/x/drivers"
)

// Device wraps an I2C connection to a VEML6070 device.
type Device struct {
	bus         drivers.I2C
	AddressLow  uint16
	AddressHigh uint16
	RSET        uint32
	IT          uint8
}

// New creates a new VEML6070 connection. The I2C bus must already be
// configured.
//
// This function only creates the Device object, it does not initialize the device.
// You must call Configure() first in order to use the device itself.
func New(bus drivers.I2C) Device {
	return Device{
		bus:         bus,
		AddressLow:  ADDR_L,
		AddressHigh: ADDR_H,
		RSET:        RSET_240K,
		// Note: default to maximum to get as much precision as possible since
		// raw data values larger than 16 bit can hardly occur with RSET below
		// 300 kOhm in real world applications. Power saving due to shorter
		// sampling time might be a reason to reduce this.
		IT: IT_4,
	}
}

// Configure sets up the device for communication
func (d *Device) Configure() bool {
	// save power by shutdown as early as possible, also serves as presence test
	if err := d.disable(); err != nil {
		return false
	}

	return true
}

// ReadUVALightIntensity returns the UVA light intensity (irradiance)
// in milli Watt per square meter (mW/(m*m))
func (d *Device) ReadUVALightIntensity() (uint32, error) {
	var err2 error

	if err := d.enable(); err != nil {
		return 0, err
	}

	// wait two times the refresh time to allow completion of a previous cycle
	// with old settings (worst case)
	time.Sleep(time.Duration(d.getRefreshTime()) * 2 * time.Millisecond)

	msb, err2 := d.readData(d.AddressHigh)
	if err2 != nil {
		return 0, err2
	}

	lsb, err2 := d.readData(d.AddressLow)
	if err2 != nil {
		return 0, err2
	}

	if err := d.disable(); err != nil {
		return 0, err
	}

	rawData := (uint32(msb) << 8) | uint32(lsb)

	// normalize raw data (step count sampled in d.getRefreshTime()) into the
	// linearly scaled normalized data (step count sampled in 100ms) for which
	// we know the UVA sensitivity
	normalizedData := float32(rawData) * NORMALIZED_REFRESHTIME / d.getRefreshTime()

	// now we can calculate the absolute UVA power detected combining normalized
	// data with known UVA sensitivity for this data, from datasheet
	intensity := normalizedData * NORMALIZED_UVA_SENSITIVITY // mW/(m*m)

	return uint32(intensity + 0.5), nil
}

// GetEstimatedRiskLevel returns estimated risk level from comparing UVA light
// intensity values in mW/(m*m) with thresholds calculated from application notes
func (d *Device) GetEstimatedRiskLevel(intensity uint32) uint8 {
	if intensity <= 24888 {
		return UVI_RISK_LOW
	} else if intensity <= 49800 {
		return UVI_RISK_MODERATE
	} else if intensity <= 66400 {
		return UVI_RISK_HIGH
	} else if intensity <= 91288 {
		return UVI_RISK_VERY_HIGH
	} else {
		return UVI_RISK_EXTREME
	}
}

func (d *Device) disable() error {
	return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_DISABLE}, nil)
}

func (d *Device) enable() error {
	return d.bus.Tx(uint16(d.AddressLow), []byte{CONFIG_ENABLE | d.IT}, nil)
}

func (d *Device) readData(address uint16) (byte, error) {
	data := []byte{0}
	err := d.bus.Tx(address, []byte{}, data)
	return data[0], err
}

// getRefreshTime returns the refresh time (aka sample time) in milliseconds
func (d *Device) getRefreshTime() float32 {
	var it float32
	switch d.IT {
	case IT_HALF:
		it = 0.5
	case IT_1:
		it = 1
	case IT_2:
		it = 2
	case IT_4:
		it = 4
	}
	return float32(d.RSET) * RSET_TO_REFRESHTIME_SCALE * it
}
