/*
 * Copyright (c) 2021 Leica Geosystems AG
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT sbs_sbs_gauge

#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/byteorder.h>

#include "sbs_gauge.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(sbs_gauge, CONFIG_SENSOR_LOG_LEVEL);

static int sbs_cmd_reg_read(const struct device *dev,
			    uint8_t reg_addr,
			    uint16_t *val)
{
	const struct sbs_gauge_config *cfg;
	uint8_t i2c_data[2];
	int status;

	cfg = dev->config;
	status = i2c_burst_read_dt(&cfg->i2c, reg_addr, i2c_data,
				   ARRAY_SIZE(i2c_data));
	if (status < 0) {
		LOG_ERR("Unable to read register");
		return status;
	}

	*val = sys_get_le16(i2c_data);

	return 0;
}

/**
 * @brief sensor value get
 *
 * @return -ENOTSUP for unsupported channels
 */
static int sbs_gauge_channel_get(const struct device *dev,
				 enum sensor_channel chan,
				 struct sensor_value *val)
{
	struct sbs_gauge_data *data;
	int32_t int_temp;

	data = dev->data;
	val->val2 = 0;

	switch (chan) {
	case SENSOR_CHAN_GAUGE_VOLTAGE:
		val->val1 = data->voltage / 1000;
		val->val2 = (data->voltage % 1000) * 1000;
		break;

	case SENSOR_CHAN_GAUGE_AVG_CURRENT:
		val->val1 = data->avg_current / 1000;
		val->val2 = (data->avg_current % 1000) * 1000;
		break;

	case SENSOR_CHAN_GAUGE_TEMP:
		int_temp = (data->internal_temperature * 10);
		int_temp = int_temp - 27315;
		val->val1 = int_temp / 100;
		val->val2 = (int_temp % 100) * 1000000;
		break;

	case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
		val->val1 = data->state_of_charge;
		break;

	case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY:
		val->val1 = data->full_charge_capacity;
		break;

	case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY:
		val->val1 = data->remaining_charge_capacity;
		break;

	case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY:
		val->val1 = data->nom_avail_capacity;
		break;

	case SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY:
		val->val1 = data->full_avail_capacity;
		break;

	case SENSOR_CHAN_GAUGE_TIME_TO_EMPTY:
		val->val1 = data->time_to_empty;
		break;

	case SENSOR_CHAN_GAUGE_TIME_TO_FULL:
		val->val1 = data->time_to_full;
		break;

	case SENSOR_CHAN_GAUGE_CYCLE_COUNT:
		val->val1 = data->cycle_count;
		break;

	default:
		return -ENOTSUP;
	}

	return 0;
}

static const uint16_t all_channels[] = {
	SENSOR_CHAN_GAUGE_VOLTAGE,
	SENSOR_CHAN_GAUGE_AVG_CURRENT,
	SENSOR_CHAN_GAUGE_TEMP,
	SENSOR_CHAN_GAUGE_STATE_OF_CHARGE,
	SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY,
	SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY,
	SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY,
	SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY,
	SENSOR_CHAN_GAUGE_TIME_TO_EMPTY,
	SENSOR_CHAN_GAUGE_TIME_TO_FULL,
	SENSOR_CHAN_GAUGE_CYCLE_COUNT
};

static int sbs_gauge_sample_fetch(const struct device *dev,
			    enum sensor_channel chan)
{
	struct sbs_gauge_data *data;
	int status = 0;

	data = dev->data;

	switch (chan) {
	case SENSOR_CHAN_GAUGE_VOLTAGE:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_VOLTAGE,
					  &data->voltage);
		if (status < 0) {
			LOG_ERR("Failed to read voltage");
		}
		break;

	case SENSOR_CHAN_GAUGE_AVG_CURRENT:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_AVG_CURRENT,
					  &data->avg_current);
		if (status < 0) {
			LOG_ERR("Failed to read average current ");
		}
		break;

	case SENSOR_CHAN_GAUGE_TEMP:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_TEMP,
					  &data->internal_temperature);
		if (status < 0) {
			LOG_ERR("Failed to read internal temperature");
		}
		break;

	case SENSOR_CHAN_GAUGE_STATE_OF_CHARGE:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_ASOC,
					  &data->state_of_charge);
		if (status < 0) {
			LOG_ERR("Failed to read state of charge");
		}
		break;

	case SENSOR_CHAN_GAUGE_FULL_CHARGE_CAPACITY:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_FULL_CAPACITY,
					  &data->full_charge_capacity);
		if (status < 0) {
			LOG_ERR("Failed to read full charge capacity");
		}
		break;

	case SENSOR_CHAN_GAUGE_REMAINING_CHARGE_CAPACITY:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_REM_CAPACITY,
					  &data->remaining_charge_capacity);
		if (status < 0) {
			LOG_ERR("Failed to read remaining charge capacity");
		}
		break;

	case SENSOR_CHAN_GAUGE_NOM_AVAIL_CAPACITY:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_NOM_CAPACITY,
					  &data->nom_avail_capacity);
		if (status < 0) {
			LOG_ERR("Failed to read nominal available capacity");
		}
		break;

	case SENSOR_CHAN_GAUGE_FULL_AVAIL_CAPACITY:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_FULL_CAPACITY,
					  &data->full_avail_capacity);
		if (status < 0) {
			LOG_ERR("Failed to read full available capacity");
		}
		break;

	case SENSOR_CHAN_GAUGE_TIME_TO_EMPTY:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_AVG_TIME2EMPTY,
					  &data->time_to_empty);
		data->time_to_empty = (data->time_to_empty) & 0x00FF;

		if (status < 0) {
			LOG_ERR("Failed to read time to empty");
		}
		break;

	case SENSOR_CHAN_GAUGE_TIME_TO_FULL:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_AVG_TIME2FULL,
					  &data->time_to_full);
		data->time_to_full = (data->time_to_full) & 0x00FF;

		if (status < 0) {
			LOG_ERR("Failed to read time to full");
		}
		break;

	case SENSOR_CHAN_GAUGE_CYCLE_COUNT:
		status = sbs_cmd_reg_read(dev,
					  SBS_GAUGE_CMD_CYCLE_COUNT,
					  &data->cycle_count);
		data->cycle_count = (data->cycle_count) & 0x00FF;

		if (status < 0) {
			LOG_ERR("Failed to read cycle count");
		}
		break;

	case SENSOR_CHAN_ALL:
		for (int i = 0; i < ARRAY_SIZE(all_channels); i++) {
			status = sbs_gauge_sample_fetch(dev, all_channels[i]);
			if (status != 0) {
				break;
			}
		}

		break;

	default:
		return -ENOTSUP;
	}

	return status;
}

/**
 * @brief initialize the fuel gauge
 *
 * @return 0 for success
 */
static int sbs_gauge_init(const struct device *dev)
{
	const struct sbs_gauge_config *cfg;

	cfg = dev->config;

	if (!device_is_ready(cfg->i2c.bus)) {
		LOG_ERR("Bus device is not ready");
		return -ENODEV;
	}

	return 0;
}

static const struct sensor_driver_api sbs_gauge_driver_api = {
	.sample_fetch = sbs_gauge_sample_fetch,
	.channel_get = sbs_gauge_channel_get,
};

#define SBS_GAUGE_INIT(index) \
	static struct sbs_gauge_data sbs_gauge_driver_##index; \
\
	static const struct sbs_gauge_config sbs_gauge_config_##index = { \
		.i2c = I2C_DT_SPEC_INST_GET(index), \
	}; \
\
	SENSOR_DEVICE_DT_INST_DEFINE(index, \
			    &sbs_gauge_init, \
			    NULL, \
			    &sbs_gauge_driver_##index, \
			    &sbs_gauge_config_##index, POST_KERNEL, \
			    CONFIG_SENSOR_INIT_PRIORITY, \
			    &sbs_gauge_driver_api);

DT_INST_FOREACH_STATUS_OKAY(SBS_GAUGE_INIT)
