/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * LoRa modem abstraction layer.
 * Maps runtime lora_radio_params to Zephyr's lora_modem_config enums
 * and provides a simple send/recv API for the KISS bridge.
 *
 * Runtime parameters are stored in g_params, protected by g_params_mutex.
 * send() and recv() snapshot the params under the mutex before configuring
 * the radio, so a concurrent frequency change takes effect on the next call.
 */

#include "lora_modem.h"

#include <zephyr/drivers/lora.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <string.h>

LOG_MODULE_REGISTER(lora_modem, LOG_LEVEL_INF);

#define LORA_DEV DEVICE_DT_GET(DT_ALIAS(lora0))

/* ── Runtime parameter state ─────────────────────────────────────────────── */

static struct lora_radio_params g_params;
static K_MUTEX_DEFINE(g_params_mutex);

/* ── Enum conversion helpers ─────────────────────────────────────────────── */

static enum lora_signal_bandwidth bw_to_enum(uint16_t bw_khz)
{
	switch (bw_khz) {
	case 7: return BW_7_KHZ;
	case 10: return BW_10_KHZ;
	case 15: return BW_15_KHZ;
	case 20: return BW_20_KHZ;
	case 31: return BW_31_KHZ;
	case 41: return BW_41_KHZ;
	case 62: return BW_62_KHZ; /* 62.5 kHz */
	case 125: return BW_125_KHZ;
	case 200: return BW_200_KHZ; /* ~203 kHz */
	case 250: return BW_250_KHZ;
	case 400: return BW_400_KHZ;
	case 500: return BW_500_KHZ;
	case 800: return BW_800_KHZ;
	case 1000: return BW_1000_KHZ;
	case 1600: return BW_1600_KHZ;
	default: return BW_125_KHZ;
	}
}

static enum lora_datarate sf_to_enum(uint8_t sf)
{
	switch (sf) {
	case 6:  return SF_6;
	case 7:  return SF_7;
	case 8:  return SF_8;
	case 9:  return SF_9;
	case 10: return SF_10;
	case 11: return SF_11;
	case 12: /* fall through */
	default: return SF_12;
	}
}

static enum lora_coding_rate cr_to_enum(uint8_t cr_denom)
{
	switch (cr_denom) {
	case 5:  return CR_4_5;
	case 6:  return CR_4_6;
	case 7:  return CR_4_7;
	case 8:  /* fall through */
	default: return CR_4_8;
	}
}

/* Build a lora_modem_config from a params snapshot */
static void params_to_cfg(const struct lora_radio_params *p,
			   struct lora_modem_config *cfg, bool tx)
{
	cfg->frequency    = p->freq_hz;
	cfg->bandwidth    = bw_to_enum(p->bw_khz);
	cfg->datarate     = sf_to_enum(p->sf);
	cfg->coding_rate  = cr_to_enum(p->cr);
	cfg->preamble_len = p->preamble_len;
	cfg->tx_power     = p->tx_power;
	cfg->tx           = tx;
}

/* ── Public API ──────────────────────────────────────────────────────────── */

int lora_modem_init(void)
{
	const struct device *lora_dev = LORA_DEV;

	if (!device_is_ready(lora_dev)) {
		LOG_ERR("LoRa device not ready");
		return -ENODEV;
	}

	/* Load compile-time defaults into runtime params */
	g_params.freq_hz      = CONFIG_LORAMODEM_LORA_FREQ_HZ;
	g_params.sf           = CONFIG_LORAMODEM_LORA_SF;
	g_params.bw_khz       = CONFIG_LORAMODEM_LORA_BW;
	g_params.cr           = CONFIG_LORAMODEM_LORA_CR;
	g_params.tx_power     = CONFIG_LORAMODEM_LORA_TX_POWER_DBM;
	g_params.preamble_len = CONFIG_LORAMODEM_LORA_PREAMBLE_LEN;

	LOG_INF("LoRa init: %u Hz SF%u BW%ukHz CR4/%u %d dBm",
		g_params.freq_hz, g_params.sf, g_params.bw_khz,
		g_params.cr, g_params.tx_power);

	struct lora_modem_config cfg;

	params_to_cfg(&g_params, &cfg, true);
	return lora_config(lora_dev, &cfg);
}

void lora_modem_get_params(struct lora_radio_params *params)
{
	k_mutex_lock(&g_params_mutex, K_FOREVER);
	*params = g_params;
	k_mutex_unlock(&g_params_mutex);
}

int lora_modem_set_params(const struct lora_radio_params *params)
{
	if (params->sf < 6 || params->sf > 12) {
		return -EINVAL;
	}
	/* Accept all Zephyr lora driver supported bandwidths (units: kHz). */
	switch (params->bw_khz) {
	case 7: case 10: case 15: case 20: case 31: case 41:
	case 62: case 125: case 200: case 250: case 400:
	case 500: case 800: case 1000: case 1600:
		break;
	default:
		return -EINVAL;
	}
	if (params->cr < 5 || params->cr > 8) {
		return -EINVAL;
	}
	if (params->freq_hz == 0) {
		return -EINVAL;
	}

	k_mutex_lock(&g_params_mutex, K_FOREVER);
	g_params = *params;
	k_mutex_unlock(&g_params_mutex);

	LOG_INF("Params updated: %u Hz SF%u BW%ukHz CR4/%u %d dBm",
		params->freq_hz, params->sf, params->bw_khz,
		params->cr, params->tx_power);
	return 0;
}

int lora_modem_set_freq(uint32_t freq_hz)
{
	if (freq_hz == 0) {
		return -EINVAL;
	}

	k_mutex_lock(&g_params_mutex, K_FOREVER);
	g_params.freq_hz = freq_hz;
	k_mutex_unlock(&g_params_mutex);

	LOG_INF("Frequency set to %u Hz", freq_hz);
	return 0;
}

int lora_modem_send(const uint8_t *data, size_t len)
{
	const struct device *lora_dev = LORA_DEV;
	struct lora_radio_params snap;
	struct lora_modem_config tx_cfg;
	int ret;

	k_mutex_lock(&g_params_mutex, K_FOREVER);
	snap = g_params;
	k_mutex_unlock(&g_params_mutex);

	params_to_cfg(&snap, &tx_cfg, true);

	ret = lora_config(lora_dev, &tx_cfg);
	if (ret < 0) {
		LOG_ERR("lora_config(TX) failed: %d", ret);
		return ret;
	}

	ret = lora_send(lora_dev, (uint8_t *)data, (uint32_t)len);
	if (ret < 0) {
		LOG_ERR("lora_send failed: %d", ret);
	}

	return ret;
}

int lora_modem_recv(uint8_t *buf, size_t buf_size, k_timeout_t timeout,
		    int16_t *rssi_out, int8_t *snr_out)
{
	const struct device *lora_dev = LORA_DEV;
	struct lora_radio_params snap;
	struct lora_modem_config rx_cfg;
	int ret;

	k_mutex_lock(&g_params_mutex, K_FOREVER);
	snap = g_params;
	k_mutex_unlock(&g_params_mutex);

	params_to_cfg(&snap, &rx_cfg, false);

	ret = lora_config(lora_dev, &rx_cfg);
	if (ret < 0) {
		LOG_ERR("lora_config(RX) failed: %d", ret);
		return ret;
	}

	int16_t rssi = 0;
	int8_t  snr  = 0;

	ret = lora_recv(lora_dev, buf, (uint8_t)buf_size, timeout, &rssi, &snr);

	if (ret >= 0) {
		LOG_DBG("RX %d bytes RSSI=%d SNR=%d", ret, rssi, snr);
		if (rssi_out) {
			*rssi_out = rssi;
		}
		if (snr_out) {
			*snr_out = snr;
		}
	}

	return ret;
}
