/*
 * Copyright (c) 2022 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#ifndef ZEPHYR_INCLUDE_LOGGING_LOG_LINK_H_
#define ZEPHYR_INCLUDE_LOGGING_LOG_LINK_H_

#include <zephyr/types.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/logging/log_msg.h>
#include <zephyr/logging/log_internal.h>
#include <zephyr/sys/iterable_sections.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Log link API
 * @defgroup log_link Log link API
 * @ingroup logger
 * @{
 */

struct log_link;

typedef void (*log_link_callback_t)(const struct log_link *link,
				    union log_msg_generic *msg);

typedef void (*log_link_dropped_cb_t)(const struct log_link *link,
				      uint32_t dropped);

struct log_link_config {
	log_link_callback_t msg_cb;
	log_link_dropped_cb_t dropped_cb;
};

struct log_link_api {
	int (*initiate)(const struct log_link *link, struct log_link_config *config);
	int (*activate)(const struct log_link *link);
	int (*get_domain_name)(const struct log_link *link, uint32_t domain_id,
				char *buf, size_t *length);
	int (*get_source_name)(const struct log_link *link, uint32_t domain_id,
				uint16_t source_id, char *buf, size_t *length);
	int (*get_levels)(const struct log_link *link, uint32_t domain_id,
				uint16_t source_id, uint8_t *level,
				uint8_t *runtime_level);
	int (*set_runtime_level)(const struct log_link *link, uint32_t domain_id,
				uint16_t source_id, uint8_t level);
};

struct log_link_ctrl_blk {
	uint32_t domain_cnt;
	uint16_t source_cnt[1 + COND_CODE_1(CONFIG_LOG_MULTIDOMAIN,
					    (CONFIG_LOG_REMOTE_DOMAIN_MAX_COUNT),
					    (0))];
	uint32_t domain_offset;
	uint32_t *filters;
};

struct log_link {
	const struct log_link_api *api;
	const char *name;
	struct log_link_ctrl_blk *ctrl_blk;
	void *ctx;
	struct mpsc_pbuf_buffer *mpsc_pbuf;
	const struct mpsc_pbuf_buffer_config *mpsc_pbuf_config;
};

/** @brief Create instance of a log link.
 *
 * Link can have dedicated buffer for messages if @p _buf_len is positive. In
 * that case messages will be processed in an order since logging core will
 * attempt to fetch message from all available buffers (default and links) and
 * process the one with the earliest timestamp. If strict ordering is not needed
 * then dedicated buffer may be omitted (@p _buf_len set to 0). That results in
 * better memory utilization but unordered messages passed to backends.
 *
 * @param _name     Instance name.
 * @param _api      API list. See @ref log_link_api.
 * @param _buf_wlen Size (in words) of dedicated buffer for messages from this buffer.
 *		    If 0 default buffer is used.
 * @param _ctx      Context (void *) associated with the link.
 */
#define LOG_LINK_DEF(_name, _api, _buf_wlen, _ctx) \
	static uint32_t __aligned(Z_LOG_MSG_ALIGNMENT) _name##_buf32[_buf_wlen]; \
	static const struct mpsc_pbuf_buffer_config _name##_mpsc_pbuf_config = { \
		.buf = (uint32_t *)_name##_buf32, \
		.size = _buf_wlen, \
		.notify_drop = z_log_notify_drop, \
		.get_wlen = log_msg_generic_get_wlen, \
		.flags = IS_ENABLED(CONFIG_LOG_MODE_OVERFLOW) ? \
			MPSC_PBUF_MODE_OVERWRITE : 0 \
	}; \
	COND_CODE_0(_buf_wlen, (), (static STRUCT_SECTION_ITERABLE(log_msg_ptr, \
								   _name##_log_msg_ptr);)) \
	static STRUCT_SECTION_ITERABLE_ALTERNATE(log_mpsc_pbuf, \
						 mpsc_pbuf_buffer, \
						 _name##_log_mpsc_pbuf); \
	static struct log_link_ctrl_blk _name##_ctrl_blk; \
	static const STRUCT_SECTION_ITERABLE(log_link, _name) = \
	{ \
		.api = &_api, \
		.name = STRINGIFY(_name), \
		.ctrl_blk = &_name##_ctrl_blk, \
		.ctx = _ctx, \
		.mpsc_pbuf = _buf_wlen ? &_name##_log_mpsc_pbuf : NULL, \
		.mpsc_pbuf_config = _buf_wlen ? &_name##_mpsc_pbuf_config : NULL \
	}

/** @brief Initiate log link.
 *
 * Function initiates the link. Since initialization procedure may be time
 * consuming, function returns before link is ready to not block logging
 * initialization. @ref log_link_activate is called to complete link initialization.
 *
 * @param link		Log link instance.
 * @param config	Configuration.
 *
 * @return 0 on success or error code.
 */
static inline int log_link_initiate(const struct log_link *link,
				   struct log_link_config *config)
{
	__ASSERT_NO_MSG(link);

	return link->api->initiate(link, config);
}

/** @brief Activate log link.
 *
 * Function checks if link is initilized and completes initialization process.
 * When successfully returns, link is ready with domain and sources count fetched
 * and timestamp details updated.
 *
 * @param link		Log link instance.
 *
 * @retval 0 When successfully activated.
 * @retval -EINPROGRESS Activation in progress.
 */
static inline int log_link_activate(const struct log_link *link)
{
	__ASSERT_NO_MSG(link);

	return link->api->activate(link);
}

/** @brief Check if link is activated.
 *
 * @param link		Log link instance.
 *
 * @retval 0 When successfully activated.
 * @retval -EINPROGRESS Activation in progress.
 */
static inline int log_link_is_active(const struct log_link *link)
{
	return link->ctrl_blk->domain_offset > 0 ? 0 : -EINPROGRESS;
}

/** @brief Get number of domains in the link.
 *
 * @param[in] link	Log link instance.
 *
 * @return Number of domains.
 */
static inline uint8_t log_link_domains_count(const struct log_link *link)
{
	__ASSERT_NO_MSG(link);

	return link->ctrl_blk->domain_cnt;
}

/** @brief Get number of sources in the domain.
 *
 * @param[in] link		Log link instance.
 * @param[in] domain_id		Relative domain ID.
 *
 * @return Source count.
 */
static inline uint16_t log_link_sources_count(const struct log_link *link,
					      uint32_t domain_id)
{
	__ASSERT_NO_MSG(link);

	return link->ctrl_blk->source_cnt[domain_id];
}

/** @brief Get domain name.
 *
 * @param[in] link		Log link instance.
 * @param[in] domain_id		Relative domain ID.
 * @param[out] buf		Output buffer filled with domain name. If NULL
 *				then name length is returned.
 * @param[in,out] length	Buffer size. Name is trimmed if it does not fit
 *				in the buffer and field is set to actual name
 *				length.
 *
 * @return 0 on success or error code.
 */
static inline int log_link_get_domain_name(const struct log_link *link,
					   uint32_t domain_id, char *buf,
					   size_t *length)
{
	__ASSERT_NO_MSG(link);

	return link->api->get_domain_name(link, domain_id, buf, length);
}

/** @brief Get source name.
 *
 * @param[in] link	Log link instance.
 * @param[in] domain_id	Relative domain ID.
 * @param[in] source_id	Source ID.
 * @param[out] buf	Output buffer filled with source name.
 * @param[in,out] length	Buffer size. Name is trimmed if it does not fit
 *				in the buffer and field is set to actual name
 *				length.
 *
 * @return 0 on success or error code.
 */
static inline int log_link_get_source_name(const struct log_link *link,
					   uint32_t domain_id, uint16_t source_id,
					   char *buf, size_t *length)
{
	__ASSERT_NO_MSG(link);
	__ASSERT_NO_MSG(buf);

	return link->api->get_source_name(link, domain_id, source_id,
					buf, length);
}

/** @brief Get level settings of the given source.
 *
 * @param[in] link	Log link instance.
 * @param[in] domain_id	Relative domain ID.
 * @param[in] source_id	Source ID.
 * @param[out] level	Location to store requested compile time level.
 * @param[out] runtime_level Location to store requested runtime time level.
 *
 * @return 0 on success or error code.
 */
static inline int log_link_get_levels(const struct log_link *link,
				      uint32_t domain_id, uint16_t source_id,
				      uint8_t *level, uint8_t *runtime_level)
{
	__ASSERT_NO_MSG(link);

	return link->api->get_levels(link, domain_id, source_id,
				     level, runtime_level);
}

/** @brief Set runtime level of the given source.
 *
 * @param[in] link	Log link instance.
 * @param[in] domain_id	Relative domain ID.
 * @param[in] source_id	Source ID.
 * @param[out] level	Requested level.
 *
 * @return 0 on success or error code.
 */
static inline int log_link_set_runtime_level(const struct log_link *link,
					     uint32_t domain_id, uint16_t source_id,
					     uint8_t level)
{
	__ASSERT_NO_MSG(link);
	__ASSERT_NO_MSG(level);

	return link->api->set_runtime_level(link, domain_id, source_id, level);
}

/**
 * @brief Enqueue external log message.
 *
 * Add log message to processing queue. Log message is created outside local
 * core. For example it maybe coming from external domain.
 *
 * @param link Log link instance.
 * @param data Message from remote domain.
 * @param len  Length in bytes.
 */
void z_log_msg_enqueue(const struct log_link *link, const void *data, size_t len);

/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_LOGGING_LOG_LINK_H_ */
