/** @file
 * @brief Modem command handler header file.
 *
 * Text-based command handler implementation for modem context driver.
 */

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

#ifndef ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_
#define ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_

#include <zephyr/kernel.h>

#include "modem_context.h"

#ifdef __cplusplus
extern "C" {
#endif

#define MODEM_CMD_DEFINE(name_) \
static int name_(struct modem_cmd_handler_data *data, uint16_t len, \
		 uint8_t **argv, uint16_t argc)

#define MODEM_CMD(cmd_, func_cb_, acount_, adelim_) { \
	.cmd = cmd_, \
	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
	.func = func_cb_, \
	.arg_count_min = acount_, \
	.arg_count_max = acount_, \
	.delim = adelim_, \
	.direct = false, \
}

#define MODEM_CMD_ARGS_MAX(cmd_, func_cb_, acount_, acountmax_, adelim_) { \
	.cmd = cmd_, \
	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
	.func = func_cb_, \
	.arg_count_min = acount_, \
	.arg_count_max = acountmax_, \
	.delim = adelim_, \
	.direct = false, \
}

#define MODEM_CMD_DIRECT_DEFINE(name_) MODEM_CMD_DEFINE(name_)

#define MODEM_CMD_DIRECT(cmd_, func_cb_) { \
	.cmd = cmd_, \
	.cmd_len = (uint16_t)sizeof(cmd_)-1, \
	.func = func_cb_, \
	.arg_count_min = 0, \
	.arg_count_max = 0, \
	.delim = "", \
	.direct = true, \
}

#define CMD_RESP	0
#define CMD_UNSOL	1
#define CMD_HANDLER	2
#define CMD_MAX		3

/*
 * Flags for modem_send_cmd_ext.
 */
#define MODEM_NO_TX_LOCK	BIT(0)
#define MODEM_NO_SET_CMDS	BIT(1)
#define MODEM_NO_UNSET_CMDS	BIT(2)

struct modem_cmd_handler_data;

struct modem_cmd {
	int (*func)(struct modem_cmd_handler_data *data, uint16_t len,
		    uint8_t **argv, uint16_t argc);
	const char *cmd;
	const char *delim;
	uint16_t cmd_len;
	uint16_t arg_count_min;
	uint16_t arg_count_max;
	bool direct;
};

#define SETUP_CMD(cmd_send_, match_cmd_, func_cb_, num_param_, delim_) { \
	.send_cmd = cmd_send_, \
	MODEM_CMD(match_cmd_, func_cb_, num_param_, delim_) \
}

#define SETUP_CMD_NOHANDLE(send_cmd_) \
		SETUP_CMD(send_cmd_, NULL, NULL, 0U, NULL)

/* series of modem setup commands to run */
struct setup_cmd {
	const char *send_cmd;
	struct modem_cmd handle_cmd;
};

struct modem_cmd_handler_data {
	const struct modem_cmd *cmds[CMD_MAX];
	size_t cmds_len[CMD_MAX];

	char *match_buf;
	size_t match_buf_len;

	int last_error;

	const char *eol;
	size_t eol_len;

	/* rx net buffer */
	struct net_buf *rx_buf;

	/* allocation info */
	struct net_buf_pool *buf_pool;
	k_timeout_t alloc_timeout;

	/* locks */
	struct k_sem sem_tx_lock;
	struct k_sem sem_parse_lock;

	/* user data */
	void *user_data;
};

/**
 * @brief  get the last error code
 *
 * @param  data: command handler data reference
 *
 * @retval last handled error.
 */
int modem_cmd_handler_get_error(struct modem_cmd_handler_data *data);

/**
 * @brief  set the last error code
 *
 * @param  data: command handler data reference
 * @param  error_code: error
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_handler_set_error(struct modem_cmd_handler_data *data,
				int error_code);

/**
 * @brief  update the parser's handler commands
 *
 * @param  data: handler data to use
 * @param  handler_cmds: commands to attach
 * @param  handler_cmds_len: size of commands array
 * @param  reset_error_flag: reset last error code
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_handler_update_cmds(struct modem_cmd_handler_data *data,
				  const struct modem_cmd *handler_cmds,
				  size_t handler_cmds_len,
				  bool reset_error_flag);

/**
 * @brief  send AT command to interface with behavior defined by flags
 *
 * This function is similar to @ref modem_cmd_send, but it allows to choose a
 * specific behavior regarding acquiring tx_lock, setting and unsetting
 * @a handler_cmds.
 *
 * @param  iface: interface to use
 * @param  handler: command handler to use
 * @param  handler_cmds: commands to attach
 * @param  handler_cmds_len: size of commands array
 * @param  buf: NULL terminated send buffer
 * @param  sem: wait for response semaphore
 * @param  timeout: timeout of command
 * @param  flags: flags which influence behavior of command sending
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_send_ext(struct modem_iface *iface,
		       struct modem_cmd_handler *handler,
		       const struct modem_cmd *handler_cmds,
		       size_t handler_cmds_len, const uint8_t *buf,
		       struct k_sem *sem, k_timeout_t timeout, int flags);

/**
 * @brief  send AT command to interface w/o locking TX
 *
 * @param  iface: interface to use
 * @param  handler: command handler to use
 * @param  handler_cmds: commands to attach
 * @param  handler_cmds_len: size of commands array
 * @param  buf: NULL terminated send buffer
 * @param  sem: wait for response semaphore
 * @param  timeout: timeout of command
 *
 * @retval 0 if ok, < 0 if error.
 */
static inline int modem_cmd_send_nolock(struct modem_iface *iface,
					struct modem_cmd_handler *handler,
					const struct modem_cmd *handler_cmds,
					size_t handler_cmds_len,
					const uint8_t *buf, struct k_sem *sem,
					k_timeout_t timeout)
{
	return modem_cmd_send_ext(iface, handler, handler_cmds,
				  handler_cmds_len, buf, sem, timeout,
				  MODEM_NO_TX_LOCK);
}

/**
 * @brief  send AT command to interface w/ a TX lock
 *
 * @param  iface: interface to use
 * @param  handler: command handler to use
 * @param  handler_cmds: commands to attach
 * @param  handler_cmds_len: size of commands array
 * @param  buf: NULL terminated send buffer
 * @param  sem: wait for response semaphore
 * @param  timeout: timeout of command
 *
 * @retval 0 if ok, < 0 if error.
 */
static inline int modem_cmd_send(struct modem_iface *iface,
				 struct modem_cmd_handler *handler,
				 const struct modem_cmd *handler_cmds,
				 size_t handler_cmds_len, const uint8_t *buf,
				 struct k_sem *sem, k_timeout_t timeout)
{
	return modem_cmd_send_ext(iface, handler, handler_cmds,
				  handler_cmds_len, buf, sem, timeout, 0);
}

/**
 * @brief  send a series of AT commands w/ a TX lock
 *
 * @param  iface: interface to use
 * @param  handler: command handler to use
 * @param  cmds: array of setup commands to send
 * @param  cmds_len: size of the setup command array
 * @param  sem: wait for response semaphore
 * @param  timeout: timeout of command
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_handler_setup_cmds(struct modem_iface *iface,
				 struct modem_cmd_handler *handler,
				 const struct setup_cmd *cmds, size_t cmds_len,
				 struct k_sem *sem, k_timeout_t timeout);

/**
 * @brief  send a series of AT commands w/o locking TX
 *
 * @param  iface: interface to use
 * @param  handler: command handler to use
 * @param  cmds: array of setup commands to send
 * @param  cmds_len: size of the setup command array
 * @param  sem: wait for response semaphore
 * @param  timeout: timeout of command
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_handler_setup_cmds_nolock(struct modem_iface *iface,
					struct modem_cmd_handler *handler,
					const struct setup_cmd *cmds,
					size_t cmds_len, struct k_sem *sem,
					k_timeout_t timeout);

/**
 * @brief Modem command handler configuration
 *
 * @details Contains user configuration which is used to set up
 * command handler data context. The struct is initialized and then passed
 * to modem_cmd_handler_init().
 *
 * @retval 0 if ok, < 0 if error.
 * @param match_buf Buffer used for matching commands
 * @param match_buf_len Length of buffer used for matching commands
 * @param buf_pool Initialized buffer pool used to store incoming data
 * @param alloc_timeout Timeout for allocating data in buffer pool
 * @param eol End of line represented as string
 * @param user_data Free to use data which can be retrieved from within command handlers
 * @param response_cmds Array of response command handlers
 * @param response_cmds_len Length of response command handlers array
 * @param unsol_cmds Array of unsolicitet command handlers
 * @param unsol_cmds_len Length of unsolicitet command handlers array
 */
struct modem_cmd_handler_config {
	char *match_buf;
	size_t match_buf_len;
	struct net_buf_pool *buf_pool;
	k_timeout_t alloc_timeout;
	const char *eol;
	void *user_data;
	const struct modem_cmd *response_cmds;
	size_t response_cmds_len;
	const struct modem_cmd *unsol_cmds;
	size_t unsol_cmds_len;
};

/**
 * @brief Initialize modem command handler
 *
 * @details This function is called once for each command handler, before any
 * incoming data is processed.
 *
 * @note All arguments passed to this function, including the referenced data
 * contained in the setup struct, must persist as long as the command handler itself.
 *
 * @param handler Command handler to initialize
 * @param data Command handler data to use
 * @param setup Command handler setup
 *
 * @return -EINVAL if any argument is invalid
 * @return 0 if successful
 */
int modem_cmd_handler_init(struct modem_cmd_handler *handler,
			   struct modem_cmd_handler_data *data,
			   const struct modem_cmd_handler_config *config);

/**
 * @brief  Lock the modem for sending cmds
 *
 * This is semaphore-based rather than mutex based, which means there's no
 * requirements of thread ownership for the user. This function is useful
 * when one needs to prevent threads from sending UART data to the modem for an
 * extended period of time (for example during modem reset).
 *
 * @param  handler: command handler to lock
 * @param  timeout: give up after timeout
 *
 * @retval 0 if ok, < 0 if error.
 */
int modem_cmd_handler_tx_lock(struct modem_cmd_handler *handler,
			      k_timeout_t timeout);

/**
 * @brief  Unlock the modem for sending cmds
 *
 * @param  handler: command handler to unlock
 */
void modem_cmd_handler_tx_unlock(struct modem_cmd_handler *handler);

/**
 * @brief Process incoming data
 *
 * @details This function will process any data available from the interface
 * using the command handler. The command handler will invoke any matching modem
 * command which has been registered using @ref modem_cmd_handler_init_cmds or
 * @ref modem_cmd_handler_update_cmds. Once handled, the function will return.
 *
 * @note This function should be invoked from a dedicated thread, which only handles
 * commands.
 *
 * @param handler The handler wich will handle the command when processed
 * @param iface The interface which receives incoming data
 */
static inline void modem_cmd_handler_process(struct modem_cmd_handler *handler,
					     struct modem_iface *iface)
{
	handler->process(handler, iface);
}

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_CMD_HANDLER_H_ */
