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

#include <sys/atomic.h>
#include <sys/util.h>
#include <string.h>
#include <logging/log_msg2.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @brief Log message API
 * @defgroup log_msg Log message API
 * @ingroup logger
 * @{
 */

/** @brief Log argument type.
 *
 * Should preferably be equivalent to a native word size.
 */
typedef unsigned long log_arg_t;

/** @brief Maximum number of arguments in the standard log entry.
 *
 * It is limited by 4 bit nargs field in the log message.
 */
#define LOG_MAX_NARGS 15

/** @brief Number of arguments in the log entry which fits in one chunk.*/
#ifdef CONFIG_64BIT
#define LOG_MSG_NARGS_SINGLE_CHUNK 4U
#else
#define LOG_MSG_NARGS_SINGLE_CHUNK 3U
#endif

/** @brief Number of arguments in the head of extended standard log message..*/
#define LOG_MSG_NARGS_HEAD_CHUNK \
	(LOG_MSG_NARGS_SINGLE_CHUNK - (sizeof(void *)/sizeof(log_arg_t)))

/** @brief Maximal amount of bytes in the hexdump entry which fits in one chunk.
 */
#define LOG_MSG_HEXDUMP_BYTES_SINGLE_CHUNK \
	(LOG_MSG_NARGS_SINGLE_CHUNK * sizeof(log_arg_t))

/** @brief Number of bytes in the first chunk of hexdump message if message
 *         consists of more than one chunk.
 */
#define LOG_MSG_HEXDUMP_BYTES_HEAD_CHUNK \
	(LOG_MSG_HEXDUMP_BYTES_SINGLE_CHUNK - sizeof(void *))

/** @brief Number of bytes that can be stored in chunks following head chunk
 *         in hexdump log message.
 */
#define HEXDUMP_BYTES_CONT_MSG \
	(sizeof(struct log_msg) - sizeof(void *))

#define ARGS_CONT_MSG (HEXDUMP_BYTES_CONT_MSG / sizeof(log_arg_t))

/** @brief Flag indicating standard log message. */
#define LOG_MSG_TYPE_STD 0U

/** @brief Flag indicating hexdump log message. */
#define LOG_MSG_TYPE_HEXDUMP 1

/** @brief Common part of log message header. */
#define COMMON_PARAM_HDR() \
	uint16_t type : 1;	   \
	uint16_t ext : 1

/** @brief Number of bits used for storing length of hexdump log message. */
#define LOG_MSG_HEXDUMP_LENGTH_BITS 14

/** @brief Maximum length of log hexdump message. */
#define LOG_MSG_HEXDUMP_MAX_LENGTH (BIT(LOG_MSG_HEXDUMP_LENGTH_BITS) - 1)

/** @brief Part of log message header identifying source and level. */
struct log_msg_ids {
	uint16_t level     : 3;    /*!< Severity. */
	uint16_t domain_id : 3;    /*!< Originating domain. */
	uint16_t source_id : 10;   /*!< Source ID. */
};

/** Part of log message header common to standard and hexdump log message. */
struct log_msg_generic_hdr {
	COMMON_PARAM_HDR();
	uint16_t reserved : 14;
};

/** Part of log message header specific to standard log message. */
struct log_msg_std_hdr {
	COMMON_PARAM_HDR();
	uint16_t reserved : 10;
	uint16_t nargs    : 4;
};

/** Part of log message header specific to hexdump log message. */
struct log_msg_hexdump_hdr {
	COMMON_PARAM_HDR();
	uint16_t length     : LOG_MSG_HEXDUMP_LENGTH_BITS;
};

/** Log message header structure */
struct log_msg_hdr {
	atomic_t ref_cnt; /*!< Reference counter for tracking message users. */
	union log_msg_hdr_params {
		struct log_msg_generic_hdr generic;
		struct log_msg_std_hdr std;
		struct log_msg_hexdump_hdr hexdump;
		uint16_t raw;
	} params;
	struct log_msg_ids ids; /*!< Identification part of the message.*/
	uint32_t timestamp;        /*!< Timestamp. */
};

/** @brief Data part of log message. */
union log_msg_head_data {
	log_arg_t args[LOG_MSG_NARGS_SINGLE_CHUNK];
	uint8_t bytes[LOG_MSG_HEXDUMP_BYTES_SINGLE_CHUNK];
};

/** @brief Data part of extended log message. */
struct log_msg_ext_head_data {
	struct log_msg_cont *next;
	union log_msg_ext_head_data_data {
		log_arg_t args[LOG_MSG_NARGS_HEAD_CHUNK];
		uint8_t bytes[LOG_MSG_HEXDUMP_BYTES_HEAD_CHUNK];
	} data;
};

/** @brief Log message structure. */
struct log_msg {
	struct log_msg *next;   /*!< Used by logger core list.*/
	struct log_msg_hdr hdr; /*!< Message header. */
	const char *str;
	union log_msg_data {
		union log_msg_head_data single;
		struct log_msg_ext_head_data ext;
	} payload;                 /*!< Message data. */
};

/** @brief Chunks following message head when message is extended. */
struct log_msg_cont {
	struct log_msg_cont *next; /*!< Pointer to the next chunk. */
	union log_msg_cont_data {
		log_arg_t args[ARGS_CONT_MSG];
		uint8_t bytes[HEXDUMP_BYTES_CONT_MSG];
	} payload;
};

/** @brief Log message */
union log_msg_chunk {
	struct log_msg head;
	struct log_msg_cont cont;
};

/** @brief Function for initialization of the log message pool. */
void log_msg_pool_init(void);

/** @brief Function for indicating that message is in use.
 *
 *  @details Message can be used (read) by multiple users. Internal reference
 *           counter is atomically increased. See @ref log_msg_put.
 *
 *  @param msg Message.
 */
void log_msg_get(struct log_msg *msg);

/** @brief Function for indicating that message is no longer in use.
 *
 *  @details Internal reference counter is atomically decreased. If reference
 *           counter equals 0 message is freed.
 *
 *  @param msg Message.
 */
void log_msg_put(struct log_msg *msg);

/** @brief Get domain ID of the message.
 *
 * @param msg Message
 *
 * @return Domain ID.
 */
static inline uint32_t log_msg_domain_id_get(struct log_msg *msg)
{
	return msg->hdr.ids.domain_id;
}

/** @brief Get source ID (module or instance) of the message.
 *
 * @param msg Message
 *
 * @return Source ID.
 */
static inline uint32_t log_msg_source_id_get(struct log_msg *msg)
{
	return msg->hdr.ids.source_id;
}

/** @brief Get severity level of the message.
 *
 * @param msg Message
 *
 * @return Severity message.
 */
static inline uint32_t log_msg_level_get(struct log_msg *msg)
{
	return msg->hdr.ids.level;
}

/** @brief Get timestamp of the message.
 *
 * @param msg Message
 *
 * @return Timestamp value.
 */
static inline uint32_t log_msg_timestamp_get(struct log_msg *msg)
{
	return msg->hdr.timestamp;
}

/** @brief Check if message is of standard type.
 *
 * @param msg Message
 *
 * @retval true  Standard message.
 * @retval false Hexdump message.
 */
static inline bool log_msg_is_std(struct log_msg *msg)
{
	return  (msg->hdr.params.generic.type == LOG_MSG_TYPE_STD);
}

/** @brief Returns number of arguments in standard log message.
 *
 * @param msg Standard log message.
 *
 * @return Number of arguments.
 */
uint32_t log_msg_nargs_get(struct log_msg *msg);

/** @brief Gets argument from standard log message.
 *
 * @param msg		Standard log message.
 * @param arg_idx	Argument index.
 *
 * @return Argument value or 0 if arg_idx exceeds number of arguments in the
 *	   message.
 */
log_arg_t log_msg_arg_get(struct log_msg *msg, uint32_t arg_idx);


/** @brief Gets pointer to the unformatted string from standard log message.
 *
 * @param msg Standard log message.
 *
 * @return Pointer to the string.
 */
const char *log_msg_str_get(struct log_msg *msg);

/** @brief Allocates chunks for hexdump message and copies the data.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- length
 *
 *  @note Allocation and partial filling is combined for performance reasons.
 *
 * @param str		String.
 * @param data		Data.
 * @param length	Data length.
 *
 * @return Pointer to allocated head of the message or NULL
 */
struct log_msg *log_msg_hexdump_create(const char *str,
				       const uint8_t *data,
				       uint32_t length);

/** @brief Put data into hexdump log message.
 *
 * @param[in]		msg      Message.
 * @param[in]		data	 Data to be copied.
 * @param[in, out]	length   Input: requested amount. Output: actual amount.
 * @param[in]		offset   Offset.
 */
void log_msg_hexdump_data_put(struct log_msg *msg,
			      uint8_t *data,
			      size_t *length,
			      size_t offset);

/** @brief Get data from hexdump log message.
 *
 * @param[in]		msg      Message.
 * @param[in]		data	 Buffer for data.
 * @param[in, out]	length   Input: requested amount. Output: actual amount.
 * @param[in]		offset   Offset.
 */
void log_msg_hexdump_data_get(struct log_msg *msg,
			      uint8_t *data,
			      size_t *length,
			      size_t offset);

union log_msg_chunk *log_msg_no_space_handle(void);

/** @brief Allocate single chunk from the pool.
 *
 * @return Pointer to the allocated chunk or NULL if failed to allocate.
 */
union log_msg_chunk *log_msg_chunk_alloc(void);

/** @brief Allocate chunk for standard log message.
 *
 *  @return Allocated chunk of NULL.
 */
static inline struct log_msg *z_log_msg_std_alloc(void)
{
	struct  log_msg *msg = (struct  log_msg *)log_msg_chunk_alloc();

	if (msg != NULL) {
		/* all fields reset to 0, reference counter to 1 */
		msg->hdr.ref_cnt = 1;
		msg->hdr.params.raw = 0U;
		msg->hdr.params.std.type = LOG_MSG_TYPE_STD;

		if (IS_ENABLED(CONFIG_USERSPACE)) {
			/* it may be used in msg_free() function. */
			msg->hdr.ids.level = 0;
			msg->hdr.ids.domain_id = 0;
			msg->hdr.ids.source_id = 0;
		}
	}

	return msg;
}

/** @brief Create standard log message with no arguments.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- string pointer
 *
 *  @return Pointer to allocated head of the message or NULL.
 */
static inline struct log_msg *log_msg_create_0(const char *str)
{
	struct log_msg *msg = z_log_msg_std_alloc();

	if (msg != NULL) {
		msg->str = str;
	}

	return msg;
}

/** @brief Create standard log message with one argument.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- string pointer
 *		- number of arguments
 *		- argument
 *
 *  @param str  String.
 *  @param arg1 Argument.
 *
 *  @return Pointer to allocated head of the message or NULL.
 */
static inline struct log_msg *log_msg_create_1(const char *str,
					       log_arg_t arg1)
{
	struct  log_msg *msg = z_log_msg_std_alloc();

	if (msg != NULL) {
		msg->str = str;
		msg->hdr.params.std.nargs = 1U;
		msg->payload.single.args[0] = arg1;
	}

	return msg;
}

/** @brief Create standard log message with two arguments.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- string pointer
 *		- number of arguments
 *		- arguments
 *
 *  @param str  String.
 *  @param arg1 Argument 1.
 *  @param arg2 Argument 2.
 *
 *  @return Pointer to allocated head of the message or NULL.
 */
static inline struct log_msg *log_msg_create_2(const char *str,
					       log_arg_t arg1,
					       log_arg_t arg2)
{
	struct  log_msg *msg = z_log_msg_std_alloc();

	if (msg != NULL) {
		msg->str = str;
		msg->hdr.params.std.nargs = 2U;
		msg->payload.single.args[0] = arg1;
		msg->payload.single.args[1] = arg2;
	}

	return msg;
}

/** @brief Create standard log message with three arguments.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- string pointer
 *		- number of arguments
 *		- arguments
 *
 *  @param str  String.
 *  @param arg1 Argument 1.
 *  @param arg2 Argument 2.
 *  @param arg3 Argument 3.
 *
 *  @return Pointer to allocated head of the message or NULL.
 */
static inline struct log_msg *log_msg_create_3(const char *str,
					       log_arg_t arg1,
					       log_arg_t arg2,
					       log_arg_t arg3)
{
	struct  log_msg *msg = z_log_msg_std_alloc();

	if (msg != NULL) {
		msg->str = str;
		msg->hdr.params.std.nargs = 3U;
		msg->payload.single.args[0] = arg1;
		msg->payload.single.args[1] = arg2;
		msg->payload.single.args[2] = arg3;
	}

	return msg;
}

/** @brief Create standard log message with variable number of arguments.
 *
 *  @details Function resets header and sets following fields:
 *		- message type
 *		- string pointer
 *		- number of arguments
 *		- arguments
 *
 *  @param str   String.
 *  @param args  Array with arguments.
 *  @param nargs Number of arguments.
 *
 *  @return Pointer to allocated head of the message or NULL.
 */
struct log_msg *log_msg_create_n(const char *str,
				 log_arg_t *args,
				 uint32_t nargs);

/**
 * @brief Get number of free blocks from the log mem pool
 */
uint32_t log_msg_mem_get_free(void);

/**
 * @brief Get number of used blocks from the log mem pool
 */
uint32_t log_msg_mem_get_used(void);

/**
 * @brief Get max used blocks from the log mem pool
 */
uint32_t log_msg_mem_get_max_used(void);


/**
 * @}
 */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_LOGGING_LOG_MSG_H_ */
