/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * LoRa KISS Modem — main application.
 *
 * Two threads bridge a KISS serial interface to a LoRa radio:
 *
 *   kiss_rx_thread: reads bytes from the KISS UART, decodes KISS frames,
 *                   dispatches data frames to lora_modem_send(), and
 *                   handles SetHardware (0x06) frames for radio parameter
 *                   changes (frequency, SF, BW, CR, TX power).
 *
 *   lora_rx_thread: loops with a 1-second timeout on lora_modem_recv();
 *                   on each successful receive it encodes the payload as
 *                   a KISS data frame and writes it to the KISS UART.
 *                   The finite timeout ensures frequency changes applied
 *                   by kiss_rx_thread take effect within 1 second.
 *
 * SetHardware sub-command protocol (see kiss.h for KISS_HW_* defines):
 *
 *   Host → device:  FEND | 0x06 | <sub-cmd> [params...] | FEND
 *   Device → host:  FEND | 0x06 | <sub-cmd> [current-value...] | FEND
 *                   (only sent in response to GET commands)
 *
 *   SET_FREQ (0x01): payload = 4 bytes freq_hz big-endian uint32
 *   GET_FREQ (0x02): no params; response = sub-cmd + 4-byte freq
 *   SET_SF   (0x03): payload = 1 byte sf (7-12)
 *   SET_BW   (0x04): payload = 2 bytes bw_khz big-endian uint16 (125/250/500)
 *   SET_CR   (0x05): payload = 1 byte cr denominator (5-8)
 *   SET_TXPWR(0x06): payload = 1 byte tx_power_dbm (int8)
 *   GET_PARAMS(0x07): no params; response = sub-cmd + 10-byte param block
 *
 * GET_PARAMS response (10 bytes after sub-cmd):
 *   [0..3] freq_hz  BE uint32
 *   [4]    sf       uint8
 *   [5..6] bw_khz   BE uint16
 *   [7]    cr       uint8 (denominator)
 *   [8]    tx_power int8 (dBm)
 *   [9]    preamble uint8
 */

#include "kiss.h"
#include "lora_modem.h"

#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(main, LOG_LEVEL_INF);

/* Resolve the KISS UART from the board overlay */
#define KISS_UART_NODE DT_CHOSEN(loramodem_kiss_uart)
#define KISS_UART      DEVICE_DT_GET(KISS_UART_NODE)

/* Thread stack sizes */
#define KISS_RX_STACK_SIZE 2048
#define LORA_RX_STACK_SIZE 2048

/* lora_rx_fn polls with this timeout so frequency changes take effect promptly */
#define LORA_RX_TIMEOUT_MS 1000

/* Packet buffers */
static uint8_t lora_rx_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE];
/* kiss_tx_buf is shared by both threads — protected by uart_tx_mutex */
static uint8_t kiss_tx_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE * 2 + 4];

/* Mutex protecting writes to the KISS UART (used by both threads) */
static K_MUTEX_DEFINE(uart_tx_mutex);

/* ── UART helpers ─────────────────────────────────────────────────────────── */

static void uart_write_bytes(const struct device *uart,
			     const uint8_t *buf, size_t len)
{
	for (size_t i = 0; i < len; i++) {
		uart_poll_out(uart, buf[i]);
	}
}

/* Encode and send a KISS SetHardware response frame to the host */
static void send_hw_response(const struct device *uart,
			     const uint8_t *payload, size_t payload_len)
{
	k_mutex_lock(&uart_tx_mutex, K_FOREVER);
	int enc = kiss_encode_cmd(KISS_CMD_SETHARDWARE,
				  payload, payload_len,
				  kiss_tx_buf, sizeof(kiss_tx_buf));
	if (enc > 0) {
		uart_write_bytes(uart, kiss_tx_buf, (size_t)enc);
	}
	k_mutex_unlock(&uart_tx_mutex);
}

/* ── SetHardware dispatcher ──────────────────────────────────────────────── */

static void handle_sethardware(const struct device *uart,
				const uint8_t *payload, size_t len)
{
	if (len == 0) {
		LOG_WRN("SetHardware: empty payload");
		return;
	}

	uint8_t sub_cmd = payload[0];
	const uint8_t *args = payload + 1;
	size_t args_len = len - 1;

	switch (sub_cmd) {

	case KISS_HW_SET_FREQ: {
		if (args_len < 4) {
			LOG_WRN("SET_FREQ: need 4 bytes, got %zu", args_len);
			return;
		}
		uint32_t freq = ((uint32_t)args[0] << 24) |
				((uint32_t)args[1] << 16) |
				((uint32_t)args[2] <<  8) |
				((uint32_t)args[3]);
		if (lora_modem_set_freq(freq) < 0) {
			LOG_WRN("SET_FREQ: invalid frequency %u Hz", freq);
		}
		break;
	}

	case KISS_HW_GET_FREQ: {
		struct lora_radio_params p;

		lora_modem_get_params(&p);
		uint8_t resp[5];

		resp[0] = KISS_HW_GET_FREQ;
		resp[1] = (uint8_t)(p.freq_hz >> 24);
		resp[2] = (uint8_t)(p.freq_hz >> 16);
		resp[3] = (uint8_t)(p.freq_hz >>  8);
		resp[4] = (uint8_t)(p.freq_hz);
		send_hw_response(uart, resp, sizeof(resp));
		break;
	}

	case KISS_HW_SET_SF: {
		if (args_len < 1) {
			LOG_WRN("SET_SF: need 1 byte");
			return;
		}
		struct lora_radio_params p;

		lora_modem_get_params(&p);
		p.sf = args[0];
		if (lora_modem_set_params(&p) < 0) {
			LOG_WRN("SET_SF: invalid SF %u", args[0]);
		}
		break;
	}

	case KISS_HW_SET_BW: {
		if (args_len < 2) {
			LOG_WRN("SET_BW: need 2 bytes");
			return;
		}
		uint16_t bw = ((uint16_t)args[0] << 8) | args[1];
		struct lora_radio_params p;

		lora_modem_get_params(&p);
		p.bw_khz = bw;
		if (lora_modem_set_params(&p) < 0) {
			LOG_WRN("SET_BW: invalid BW %u kHz", bw);
		}
		break;
	}

	case KISS_HW_SET_CR: {
		if (args_len < 1) {
			LOG_WRN("SET_CR: need 1 byte");
			return;
		}
		struct lora_radio_params p;

		lora_modem_get_params(&p);
		p.cr = args[0];
		if (lora_modem_set_params(&p) < 0) {
			LOG_WRN("SET_CR: invalid CR %u", args[0]);
		}
		break;
	}

	case KISS_HW_SET_TXPWR: {
		if (args_len < 1) {
			LOG_WRN("SET_TXPWR: need 1 byte");
			return;
		}
		struct lora_radio_params p;

		lora_modem_get_params(&p);
		p.tx_power = (int8_t)args[0];
		lora_modem_set_params(&p);
		break;
	}

	case KISS_HW_GET_PARAMS: {
		struct lora_radio_params p;

		lora_modem_get_params(&p);

		uint8_t resp[11];

		resp[0]  = KISS_HW_GET_PARAMS;
		resp[1]  = (uint8_t)(p.freq_hz >> 24);
		resp[2]  = (uint8_t)(p.freq_hz >> 16);
		resp[3]  = (uint8_t)(p.freq_hz >>  8);
		resp[4]  = (uint8_t)(p.freq_hz);
		resp[5]  = p.sf;
		resp[6]  = (uint8_t)(p.bw_khz >> 8);
		resp[7]  = (uint8_t)(p.bw_khz);
		resp[8]  = p.cr;
		resp[9]  = (uint8_t)p.tx_power;
		resp[10] = p.preamble_len;
		send_hw_response(uart, resp, sizeof(resp));
		break;
	}

	default:
		LOG_WRN("SetHardware: unknown sub-cmd 0x%02x", sub_cmd);
		break;
	}
}

/* ── KISS RX thread: serial → LoRa ────────────────────────────────────────── */

K_THREAD_STACK_DEFINE(kiss_rx_stack, KISS_RX_STACK_SIZE);
static struct k_thread kiss_rx_thread;

static void kiss_rx_fn(void *p1, void *p2, void *p3)
{
	ARG_UNUSED(p1);
	ARG_UNUSED(p2);
	ARG_UNUSED(p3);

	const struct device *uart = KISS_UART;
	struct kiss_decoder_ctx dec;
	static uint8_t dec_buf[CONFIG_LORAMODEM_MAX_PACKET_SIZE];

	kiss_decoder_init(&dec, dec_buf, sizeof(dec_buf));

#if defined(CONFIG_LORAMODEM_KISS_IFACE_USB)
	uint32_t dtr = 0;

	LOG_INF("Waiting for USB host...");
	while (!dtr) {
		uart_line_ctrl_get(uart, UART_LINE_CTRL_DTR, &dtr);
		k_sleep(K_MSEC(100));
	}
	LOG_INF("USB host connected");
#endif

	LOG_INF("KISS RX thread started");

	uint8_t byte;

	while (true) {
		if (uart_poll_in(uart, &byte) == 0) {
			if (kiss_decoder_feed(&dec, byte)) {
				if (dec.cmd == KISS_CMD_DATA &&
				    dec.frame_len > 0) {
					LOG_DBG("TX %zu bytes via LoRa",
						dec.frame_len);
					lora_modem_send(dec.frame_buf,
							dec.frame_len);
				} else if (dec.cmd == KISS_CMD_SETHARDWARE) {
					handle_sethardware(uart,
							   dec.frame_buf,
							   dec.frame_len);
				}
				kiss_decoder_init(&dec, dec_buf,
						  sizeof(dec_buf));
			}
		} else {
			k_yield();
		}
	}
}

/* ── LoRa RX thread: LoRa → serial ────────────────────────────────────────── */

K_THREAD_STACK_DEFINE(lora_rx_stack, LORA_RX_STACK_SIZE);
static struct k_thread lora_rx_thread;

static void lora_rx_fn(void *p1, void *p2, void *p3)
{
	ARG_UNUSED(p1);
	ARG_UNUSED(p2);
	ARG_UNUSED(p3);

	const struct device *uart = KISS_UART;
	int16_t rssi;
	int8_t  snr;

	LOG_INF("LoRa RX thread started");

	while (true) {
		/*
		 * Use a finite timeout so any frequency change applied by
		 * kiss_rx_thread takes effect within LORA_RX_TIMEOUT_MS.
		 * lora_modem_recv() snapshots g_params at the start of each
		 * call, so the new frequency is picked up automatically on
		 * the next iteration.
		 */
		int len = lora_modem_recv(lora_rx_buf, sizeof(lora_rx_buf),
					  K_MSEC(LORA_RX_TIMEOUT_MS),
					  &rssi, &snr);

		if (len == -ETIMEDOUT) {
			continue;
		}

		if (len < 0) {
			LOG_WRN("lora_modem_recv error: %d", len);
			continue;
		}

		LOG_DBG("RX %d bytes RSSI=%d SNR=%d", len, rssi, snr);

		k_mutex_lock(&uart_tx_mutex, K_FOREVER);
		int enc_len = kiss_encode(lora_rx_buf, (size_t)len,
					  kiss_tx_buf, sizeof(kiss_tx_buf));
		if (enc_len > 0) {
			uart_write_bytes(uart, kiss_tx_buf, (size_t)enc_len);
		} else {
			LOG_WRN("kiss_encode error: %d", enc_len);
		}
		k_mutex_unlock(&uart_tx_mutex);
	}
}

/* ── Entry point ──────────────────────────────────────────────────────────── */

int main(void)
{
	int ret;

	LOG_INF("LoRa KISS Modem starting");

	ret = lora_modem_init();
	if (ret < 0) {
		LOG_ERR("LoRa modem init failed: %d", ret);
		return ret;
	}

	k_thread_create(&kiss_rx_thread, kiss_rx_stack,
			K_THREAD_STACK_SIZEOF(kiss_rx_stack),
			kiss_rx_fn, NULL, NULL, NULL,
			K_PRIO_COOP(5), 0, K_NO_WAIT);
	k_thread_name_set(&kiss_rx_thread, "kiss_rx");

	k_thread_create(&lora_rx_thread, lora_rx_stack,
			K_THREAD_STACK_SIZEOF(lora_rx_stack),
			lora_rx_fn, NULL, NULL, NULL,
			K_PRIO_COOP(5), 0, K_NO_WAIT);
	k_thread_name_set(&lora_rx_thread, "lora_rx");

	return 0;
}
