/** @file
 * @brief Modem receiver driver
 *
 * A modem receiver driver allowing application to handle all
 * aspects of received protocol data.
 */

/*
 * Copyright (c) 2018 Foundries.io
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/pm/device.h>

#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(mdm_receiver, CONFIG_MODEM_LOG_LEVEL);

#include "modem_receiver.h"

#define MAX_MDM_CTX	CONFIG_MODEM_RECEIVER_MAX_CONTEXTS
#define MAX_READ_SIZE	128

static struct mdm_receiver_context *contexts[MAX_MDM_CTX];

/**
 * @brief  Finds receiver context which manages provided device.
 *
 * @param  dev: device used by the receiver context.
 *
 * @retval Receiver context or NULL.
 */
static struct mdm_receiver_context *context_from_dev(const struct device *dev)
{
	int i;

	for (i = 0; i < MAX_MDM_CTX; i++) {
		if (contexts[i] && contexts[i]->uart_dev == dev) {
			return contexts[i];
		}
	}

	return NULL;
}

/**
 * @brief  Persists receiver context if there is a free place.
 *
 * @note   Amount of stored receiver contexts is determined by
 *         MAX_MDM_CTX.
 *
 * @param  ctx: receiver context to persist.
 *
 * @retval 0 if ok, < 0 if error.
 */
static int mdm_receiver_get(struct mdm_receiver_context *ctx)
{
	int i;

	for (i = 0; i < MAX_MDM_CTX; i++) {
		if (!contexts[i]) {
			contexts[i] = ctx;
			return 0;
		}
	}

	return -ENOMEM;
}

/**
 * @brief  Drains UART.
 *
 * @note   Discards remaining data.
 *
 * @param  ctx: receiver context.
 *
 * @retval None.
 */
static void mdm_receiver_flush(struct mdm_receiver_context *ctx)
{
	uint8_t c;

	__ASSERT(ctx, "invalid ctx");
	__ASSERT(ctx->uart_dev, "invalid ctx device");

	while (uart_fifo_read(ctx->uart_dev, &c, 1) > 0) {
		continue;
	}
}

/**
 * @brief  Receiver UART interrupt handler.
 *
 * @note   Fills contexts ring buffer with received data.
 *         When ring buffer is full the data is discarded.
 *
 * @param  uart_dev: uart device.
 *
 * @retval None.
 */
static void mdm_receiver_isr(const struct device *uart_dev, void *user_data)
{
	struct mdm_receiver_context *ctx;
	int rx, ret;
	static uint8_t read_buf[MAX_READ_SIZE];

	ARG_UNUSED(user_data);

	/* lookup the device */
	ctx = context_from_dev(uart_dev);
	if (!ctx) {
		return;
	}

	/* get all of the data off UART as fast as we can */
	while (uart_irq_update(ctx->uart_dev) &&
	       uart_irq_rx_ready(ctx->uart_dev)) {
		rx = uart_fifo_read(ctx->uart_dev, read_buf, sizeof(read_buf));
		if (rx > 0) {
			ret = ring_buf_put(&ctx->rx_rb, read_buf, rx);
			if (ret != rx) {
				LOG_ERR("Rx buffer doesn't have enough space. "
						"Bytes pending: %d, written: %d",
						rx, ret);
				mdm_receiver_flush(ctx);
				k_sem_give(&ctx->rx_sem);
				break;
			}
			k_sem_give(&ctx->rx_sem);
		}
	}
}

/**
 * @brief  Configures receiver context and assigned device.
 *
 * @param  ctx: receiver context.
 *
 * @retval None.
 */
static void mdm_receiver_setup(struct mdm_receiver_context *ctx)
{
	__ASSERT(ctx, "invalid ctx");

	uart_irq_rx_disable(ctx->uart_dev);
	uart_irq_tx_disable(ctx->uart_dev);
	mdm_receiver_flush(ctx);
	uart_irq_callback_set(ctx->uart_dev, mdm_receiver_isr);
	uart_irq_rx_enable(ctx->uart_dev);
}

struct mdm_receiver_context *mdm_receiver_context_from_id(int id)
{
	if (id >= 0 && id < MAX_MDM_CTX) {
		return contexts[id];
	} else {
		return NULL;
	}
}

int mdm_receiver_recv(struct mdm_receiver_context *ctx,
		      uint8_t *buf, size_t size, size_t *bytes_read)
{
	if (!ctx) {
		return -EINVAL;
	}

	if (size == 0) {
		*bytes_read = 0;
		return 0;
	}

	*bytes_read = ring_buf_get(&ctx->rx_rb, buf, size);
	return 0;
}

int mdm_receiver_send(struct mdm_receiver_context *ctx,
		      const uint8_t *buf, size_t size)
{
	if (!ctx) {
		return -EINVAL;
	}

	if (size == 0) {
		return 0;
	}

	do {
		uart_poll_out(ctx->uart_dev, *buf++);
	} while (--size);

	return 0;
}

int mdm_receiver_sleep(struct mdm_receiver_context *ctx)
{
	uart_irq_rx_disable(ctx->uart_dev);
#ifdef CONFIG_PM_DEVICE
	pm_device_action_run(ctx->uart_dev, PM_DEVICE_ACTION_SUSPEND);
#endif
	return 0;
}

int mdm_receiver_wake(struct mdm_receiver_context *ctx)
{
#ifdef CONFIG_PM_DEVICE
	pm_device_action_run(ctx->uart_dev, PM_DEVICE_ACTION_RESUME);
#endif
	uart_irq_rx_enable(ctx->uart_dev);

	return 0;
}

int mdm_receiver_register(struct mdm_receiver_context *ctx,
			  const struct device *uart_dev,
			  uint8_t *buf, size_t size)
{
	int ret;

	if ((!ctx) || (size == 0)) {
		return -EINVAL;
	}

	if (!device_is_ready(uart_dev)) {
		LOG_ERR("Device is not ready: %s",
			uart_dev ? uart_dev->name : "<null>");
		return -ENODEV;
	}

	ctx->uart_dev = uart_dev;
	ring_buf_init(&ctx->rx_rb, size, buf);
	k_sem_init(&ctx->rx_sem, 0, 1);

	ret = mdm_receiver_get(ctx);
	if (ret < 0) {
		return ret;
	}

	mdm_receiver_setup(ctx);
	return 0;
}
