/*
 * Copyright (c) 2023 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT asahi_kasei_akm09918c

#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/emul_sensor.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>

#include "akm09918c.h"
#include "akm09918c_emul.h"
#include "akm09918c_reg.h"

LOG_MODULE_DECLARE(AKM09918C, CONFIG_SENSOR_LOG_LEVEL);

#define NUM_REGS AKM09918C_REG_TS2

struct akm09918c_emul_data {
	uint8_t reg[NUM_REGS];
};

struct akm09918c_emul_cfg {
};

void akm09918c_emul_set_reg(const struct emul *target, uint8_t reg_addr, const uint8_t *val,
			    size_t count)
{
	struct akm09918c_emul_data *data = target->data;

	__ASSERT_NO_MSG(reg_addr + count < NUM_REGS);
	memcpy(data->reg + reg_addr, val, count);
}

void akm09918c_emul_get_reg(const struct emul *target, uint8_t reg_addr, uint8_t *val, size_t count)
{
	struct akm09918c_emul_data *data = target->data;

	__ASSERT_NO_MSG(reg_addr + count < NUM_REGS);
	memcpy(val, data->reg + reg_addr, count);
}

void akm09918c_emul_reset(const struct emul *target)
{
	struct akm09918c_emul_data *data = target->data;

	memset(data->reg, 0, NUM_REGS);
	data->reg[AKM09918C_REG_WIA1] = AKM09918C_WIA1;
	data->reg[AKM09918C_REG_WIA2] = AKM09918C_WIA2;
}

static int akm09918c_emul_handle_write(const struct emul *target, uint8_t regn, uint8_t value)
{
	struct akm09918c_emul_data *data = target->data;

	switch (regn) {
	case AKM09918C_REG_CNTL2:
		data->reg[AKM09918C_REG_CNTL2] = value;
		break;
	case AKM09918C_REG_CNTL3:
		if (FIELD_GET(AKM09918C_CNTL3_SRST, value) == 1) {
			akm09918c_emul_reset(target);
		}
		break;
	}
	return 0;
}

static int akm09918c_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
				       int num_msgs, int addr)
{
	struct akm09918c_emul_data *data = target->data;

	i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);

	if (num_msgs < 1) {
		LOG_ERR("Invalid number of messages: %d", num_msgs);
		return -EIO;
	}
	if (FIELD_GET(I2C_MSG_READ, msgs->flags)) {
		LOG_ERR("Unexpected read");
		return -EIO;
	}
	if (msgs->len < 1) {
		LOG_ERR("Unexpected msg0 length %s", msgs->len);
		return -EIO;
	}

	uint8_t regn = msgs->buf[0];
	bool is_read = FIELD_GET(I2C_MSG_READ, msgs->flags) == 1;
	bool is_stop = FIELD_GET(I2C_MSG_STOP, msgs->flags) == 1;

	if (!is_stop && !is_read) {
		/* First message was a write with the register number, check next message */
		msgs++;
		is_read = FIELD_GET(I2C_MSG_READ, msgs->flags) == 1;
		is_stop = FIELD_GET(I2C_MSG_STOP, msgs->flags) == 1;
	}
	if (is_read) {
		/* Read data */
		uint8_t mode = data->reg[AKM09918C_REG_CNTL2];

		for (int i = 0; i < msgs->len; ++i) {
			msgs->buf[i] = data->reg[regn + i];
			if (regn + i == AKM09918C_REG_TMPS &&
			    mode == AKM09918C_CNTL2_SINGLE_MEASURE) {
				/* Reading the TMPS register clears the DRDY bit */
				data->reg[AKM09918C_REG_ST1] = 0;
			}
		}
	} else {
		/* Write data */
		int rc = akm09918c_emul_handle_write(target, regn, msgs->buf[1]);

		if (rc != 0) {
			return rc;
		}
	}

	return 0;
}

static int akm09918c_emul_init(const struct emul *target, const struct device *parent)
{
	ARG_UNUSED(parent);
	akm09918c_emul_reset(target);

	return 0;
}

static int akm09918c_emul_backend_set_channel(const struct emul *target, enum sensor_channel ch,
					      q31_t value, int8_t shift)
{
	if (!target || !target->data) {
		return -EINVAL;
	}

	struct akm09918c_emul_data *data = target->data;
	uint8_t reg;

	switch (ch) {
	case SENSOR_CHAN_MAGN_X:
		reg = AKM09918C_REG_HXL;
		break;
	case SENSOR_CHAN_MAGN_Y:
		reg = AKM09918C_REG_HYL;
		break;
	case SENSOR_CHAN_MAGN_Z:
		reg = AKM09918C_REG_HZL;
		break;
	/* This function only supports setting single channels, so skip MAGN_XYZ */
	default:
		return -ENOTSUP;
	}

	/* Set the ST1 register to show we have data */
	data->reg[AKM09918C_REG_ST1] |= AKM09918C_ST1_DRDY;

	/* Convert fixed-point Gauss values into microgauss and then into its bit representation */
	int32_t microgauss = (shift < 0 ? ((int64_t)value >> -shift) : ((int64_t)value << shift)) *
			     1000000 / ((int64_t)INT32_MAX + 1);

	int16_t reg_val =
		CLAMP(microgauss, AKM09918C_MAGN_MIN_MICRO_GAUSS, AKM09918C_MAGN_MAX_MICRO_GAUSS) /
		AKM09918C_MICRO_GAUSS_PER_BIT;

	/* Insert reading into registers */
	data->reg[reg] = reg_val & 0xFF;
	data->reg[reg + 1] = (reg_val >> 8) & 0xFF;

	return 0;
}

static int akm09918c_emul_backend_get_sample_range(const struct emul *target,
						   enum sensor_channel ch, q31_t *lower,
						   q31_t *upper, q31_t *epsilon, int8_t *shift)
{
	ARG_UNUSED(target);

	if (!lower || !upper || !epsilon || !shift) {
		return -EINVAL;
	}

	switch (ch) {
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
		/* +/- 49.12 Gs is the measurement range. 0.0015 Gs is the granularity */
		*shift = 6;
		*upper = (int64_t)(49.12 * ((int64_t)INT32_MAX + 1)) >> *shift;
		*lower = -*upper;
		*epsilon = (int64_t)(0.0015 * ((int64_t)INT32_MAX + 1)) >> *shift;
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static const struct i2c_emul_api akm09918c_emul_api_i2c = {
	.transfer = akm09918c_emul_transfer_i2c,
};

static const struct emul_sensor_backend_api akm09918c_emul_sensor_backend_api = {
	.set_channel = akm09918c_emul_backend_set_channel,
	.get_sample_range = akm09918c_emul_backend_get_sample_range,
};

#define AKM09918C_EMUL(n)                                                                          \
	const struct akm09918c_emul_cfg akm09918c_emul_cfg_##n;                                    \
	struct akm09918c_emul_data akm09918c_emul_data_##n;                                        \
	EMUL_DT_INST_DEFINE(n, akm09918c_emul_init, &akm09918c_emul_data_##n,                      \
			    &akm09918c_emul_cfg_##n, &akm09918c_emul_api_i2c,                      \
			    &akm09918c_emul_sensor_backend_api)

DT_INST_FOREACH_STATUS_OKAY(AKM09918C_EMUL)
