/*
 * Copyright Runtime.io 2018. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @file
 * @brief Statistics.
 *
 * Statistics are per-module event counters for troubleshooting, maintenance,
 * and usage monitoring.  Statistics are organized into named "groups", with
 * each group consisting of a set of "entries".  An entry corresponds to an
 * individual counter.  Each entry can optionally be named if the STATS_NAMES
 * setting is enabled.  Statistics can be retrieved with the mcumgr management
 * subsystem.
 *
 * There are two, largely duplicated, statistics sections here, in order to
 * provide the optional ability to name statistics.
 *
 * STATS_SECT_START/END actually declare the statistics structure definition,
 * STATS_SECT_DECL() creates the structure declaration so you can declare
 * these statistics as a global structure, and STATS_NAME_START/END are how
 * you name the statistics themselves.
 *
 * Statistics entries can be declared as any of several integer types.
 * However, all statistics in a given structure must be of the same size, and
 * they are all unsigned.
 *
 * - STATS_SECT_ENTRY(): default statistic entry, 32-bits.
 *
 * - STATS_SECT_ENTRY16(): 16-bits.  Smaller statistics if you need to fit into
 *   specific RAM or code size numbers.
 *
 * - STATS_SECT_ENTRY32(): 32-bits.
 *
 * - STATS_SECT_ENTRY64(): 64-bits.  Useful for storing chunks of data.
 *
 * Following the static entry declaration is the statistic names declaration.
 * This is compiled out when the CONFIGURE_STATS_NAME setting is undefined.
 *
 * When CONFIG_STATS_NAMES is defined, the statistics names are stored and
 * returned to the management APIs.  When the setting is undefined, temporary
 * names are generated as needed with the following format:
 *
 *     s<stat-idx>
 *
 * E.g., "s0", "s1", etc.
 */

#ifndef ZEPHYR_INCLUDE_STATS_STATS_H_
#define ZEPHYR_INCLUDE_STATS_STATS_H_

#include <stddef.h>
#include <zephyr/types.h>

#ifdef __cplusplus
extern "C" {
#endif

struct stats_name_map {
	uint16_t snm_off;
	const char *snm_name;
} __attribute__((packed));

struct stats_hdr {
	const char *s_name;
	uint8_t s_size;
	uint16_t s_cnt;
	uint8_t s_pad1;
#ifdef CONFIG_STATS_NAMES
	const struct stats_name_map *s_map;
	int s_map_cnt;
#endif
	struct stats_hdr *s_next;
};

/**
 * @brief Declares a stat group struct.
 *
 * @param group__               The name to assign to the structure tag.
 */
#define STATS_SECT_DECL(group__) \
	struct stats_ ## group__

/**
 * @brief Ends a stats group struct definition.
 */
#define STATS_SECT_END }

/* The following macros depend on whether CONFIG_STATS is defined.  If it is
 * not defined, then invocations of these macros get compiled out.
 */
#ifdef CONFIG_STATS

/**
 * @brief Begins a stats group struct definition.
 *
 * @param group__               The stats group struct name.
 */
#define STATS_SECT_START(group__)  \
	STATS_SECT_DECL(group__) { \
		struct stats_hdr s_hdr;

/**
 * @brief Declares a 32-bit stat entry inside a group struct.
 *
 * @param var__                 The name to assign to the entry.
 */
#define STATS_SECT_ENTRY(var__) uint32_t var__;

/**
 * @brief Declares a 16-bit stat entry inside a group struct.
 *
 * @param var__                 The name to assign to the entry.
 */
#define STATS_SECT_ENTRY16(var__) uint16_t var__;

/**
 * @brief Declares a 32-bit stat entry inside a group struct.
 *
 * @param var__                 The name to assign to the entry.
 */
#define STATS_SECT_ENTRY32(var__) uint32_t var__;

/**
 * @brief Declares a 64-bit stat entry inside a group struct.
 *
 * @param var__                 The name to assign to the entry.
 */
#define STATS_SECT_ENTRY64(var__) uint64_t var__;

/**
 * @brief Increases a statistic entry by the specified amount.
 *
 * Increases a statistic entry by the specified amount.  Compiled out if
 * CONFIG_STATS is not defined.
 *
 * @param group__               The group containing the entry to increase.
 * @param var__                 The statistic entry to increase.
 * @param n__                   The amount to increase the statistic entry by.
 */
#define STATS_INCN(group__, var__, n__)	\
	((group__).var__ += (n__))

/**
 * @brief Increments a statistic entry.
 *
 * Increments a statistic entry by one.  Compiled out if CONFIG_STATS is not
 * defined.
 *
 * @param group__               The group containing the entry to increase.
 * @param var__                 The statistic entry to increase.
 */
#define STATS_INC(group__, var__) \
	STATS_INCN(group__, var__, 1)

/**
 * @brief Set a statistic entry to the specified amount.
 *
 * Set a statistic entry to the specified amount.  Compiled out if
 * CONFIG_STATS is not defined.
 *
 * @param group__               The group containing the entry to increase.
 * @param var__                 The statistic entry to increase.
 * @param n__                   The amount to set the statistic entry to.
 */
#define STATS_SET(group__, var__, n__)	\
	((group__).var__ = (n__))

/**
 * @brief Sets a statistic entry to zero.
 *
 * Sets a statistic entry to zero.  Compiled out if CONFIG_STATS is not
 * defined.
 *
 * @param group__               The group containing the entry to clear.
 * @param var__                 The statistic entry to clear.
 */
#define STATS_CLEAR(group__, var__) \
	((group__).var__ = 0)

#define STATS_SIZE_16 (sizeof(uint16_t))
#define STATS_SIZE_32 (sizeof(uint32_t))
#define STATS_SIZE_64 (sizeof(uint64_t))

#define STATS_SIZE_INIT_PARMS(group__, size__) \
	(size__),			       \
	((sizeof(group__)) - sizeof(struct stats_hdr)) / (size__)

/**
 * @brief Initializes and registers a statistics group.
 *
 * @param group__               The statistics group to initialize and
 *                                  register.
 * @param size__                The size of each entry in the statistics group,
 *                                  in bytes.  Must be one of: 2 (16-bits), 4
 *                                  (32-bits) or 8 (64-bits).
 * @param name__                The name of the statistics group to register.
 *                                  This name must be unique among all
 *                                  statistics groups.
 *
 * @return                      0 on success; negative error code on failure.
 */
#define STATS_INIT_AND_REG(group__, size__, name__)			 \
	stats_init_and_reg(						 \
		&(group__).s_hdr,					 \
		(size__),						 \
		(sizeof(group__) - sizeof(struct stats_hdr)) / (size__), \
		STATS_NAME_INIT_PARMS(group__),				 \
		(name__))

/**
 * @brief Initializes a statistics group.
 *
 * @param hdr                   The header of the statistics structure,
 *                                  contains things like statistic section
 *                                  name, size of statistics entries, number of
 *                                  statistics, etc.
 * @param size                  The size of each individual statistics
 *                                  element, in bytes.  Must be one of: 2
 *                                  (16-bits), 4 (32-bits) or 8 (64-bits).
 * @param cnt                   The number of elements in the stats group.
 * @param map                   The mapping of stat offset to name.
 * @param map_cnt               The number of items in the statistics map
 *
 * @param group__               The group containing the entry to clear.
 * @param var__                 The statistic entry to clear.
 */
void stats_init(struct stats_hdr *shdr, uint8_t size, uint16_t cnt,
		const struct stats_name_map *map, uint16_t map_cnt);

/**
 * @brief Registers a statistics group to be managed.
 *
 * @param name                  The name of the statistics group to register.
 *                                 This name must be unique among all
 *                                 statistics groups.  If the name is a
 *                                 duplicate, this function will return
 *                                 -EALREADY.
 * @param shdr                  The statistics group to register.
 *
 * @return 0 on success, non-zero error code on failure.
 */
int stats_register(const char *name, struct stats_hdr *shdr);

/**
 * @brief Initializes and registers a statistics group.
 *
 * Initializes and registers a statistics group.  Note: it is recommended to
 * use the STATS_INIT_AND_REG macro instead of this function.
 *
 * @param hdr                   The header of the statistics group to
 *                                  initialize and register.
 * @param size                  The size of each individual statistics
 *                                  element, in bytes.  Must be one of: 2
 *                                  (16-bits), 4 (32-bits) or 8 (64-bits).
 * @param cnt                   The number of elements in the stats group.
 * @param map                   The mapping of stat offset to name.
 * @param map_cnt               The number of items in the statistics map
 * @param name                  The name of the statistics group to register.
 *                                  This name must be unique among all
 *                                  statistics groups.  If the name is a
 *                                  duplicate, this function will return
 *                                  -EALREADY.
 *
 * @return                      0 on success; negative error code on failure.
 *
 * @see STATS_INIT_AND_REG
 */
int stats_init_and_reg(struct stats_hdr *hdr, uint8_t size, uint16_t cnt,
		       const struct stats_name_map *map, uint16_t map_cnt,
		       const char *name);

/**
 * Zeroes the specified statistics group.
 *
 * @param shdr                  The statistics group to clear.
 */
void stats_reset(struct stats_hdr *shdr);

/** @typedef stats_walk_fn
 * @brief Function that gets applied to every stat entry during a walk.
 *
 * @param hdr                   The group containing the stat entry being
 *                                  walked.
 * @param arg                   Optional argument.
 * @param name                  The name of the statistic entry to process
 * @param off                   The offset of the entry, from `hdr`.
 *
 * @return                      0 if the walk should proceed;
 *                              nonzero to abort the walk.
 */
typedef int stats_walk_fn(struct stats_hdr *hdr, void *arg,
			  const char *name, uint16_t off);

/**
 * @brief Applies a function to every stat entry in a group.
 *
 * @param hdr                   The stats group to operate on.
 * @param walk_cb               The function to apply to each stat entry.
 * @param arg                   Optional argument to pass to the callback.
 *
 * @return                      0 if the walk completed;
 *                              nonzero if the walk was aborted.
 */
int stats_walk(struct stats_hdr *hdr, stats_walk_fn *walk_cb, void *arg);

/** @typedef stats_group_walk_fn
 * @brief Function that gets applied to every registered stats group.
 *
 * @param hdr                   The stats group being walked.
 * @param arg                   Optional argument.
 *
 * @return                      0 if the walk should proceed;
 *                              nonzero to abort the walk.
 */
typedef int stats_group_walk_fn(struct stats_hdr *hdr, void *arg);

/**
 * @brief Applies a function every registered statistics group.
 *
 * @param walk_cb               The function to apply to each stat group.
 * @param arg                   Optional argument to pass to the callback.
 *
 * @return                      0 if the walk completed;
 *                              nonzero if the walk was aborted.
 */
int stats_group_walk(stats_group_walk_fn *walk_cb, void *arg);

/**
 * @brief Retrieves the next registered statistics group.
 *
 * @param cur                   The group whose successor is being retrieved, or
 *                                  NULL to retrieve the first group.
 *
 * @return                      Pointer to the retrieved group on success;
 *                              NULL if no more groups remain.
 */
struct stats_hdr *stats_group_get_next(const struct stats_hdr *cur);

/**
 * @brief Retrieves the statistics group with the specified name.
 *
 * @param name                  The name of the statistics group to look up.
 *
 * @return                      Pointer to the retrieved group on success;
 *                              NULL if there is no matching registered group.
 */
struct stats_hdr *stats_group_find(const char *name);

#else /* CONFIG_STATS */

#define STATS_SECT_START(group__) \
	STATS_SECT_DECL(group__) {

#define STATS_SECT_ENTRY(var__)
#define STATS_SECT_ENTRY16(var__)
#define STATS_SECT_ENTRY32(var__)
#define STATS_SECT_ENTRY64(var__)
#define STATS_RESET(var__)
#define STATS_SIZE_INIT_PARMS(group__, size__)
#define STATS_INCN(group__, var__, n__)
#define STATS_INC(group__, var__)
#define STATS_SET(group__, var__)
#define STATS_CLEAR(group__, var__)
#define STATS_INIT_AND_REG(group__, size__, name__) (0)

#endif /* !CONFIG_STATS */

#ifdef CONFIG_STATS_NAMES

#define STATS_NAME_MAP_NAME(sectname__) stats_map_ ## sectname__

#define STATS_NAME_START(sectname__) \
	static const struct stats_name_map STATS_NAME_MAP_NAME(sectname__)[] = {

#define STATS_NAME(sectname__, entry__)	\
	{ offsetof(STATS_SECT_DECL(sectname__), entry__), #entry__ },

#define STATS_NAME_END(sectname__) }

#define STATS_NAME_INIT_PARMS(name__)	    \
	&(STATS_NAME_MAP_NAME(name__)[0]), \
	(sizeof(STATS_NAME_MAP_NAME(name__)) / sizeof(struct stats_name_map))

#else /* CONFIG_STATS_NAMES */

#define STATS_NAME_START(name__)
#define STATS_NAME(name__, entry__)
#define STATS_NAME_END(name__)
#define STATS_NAME_INIT_PARMS(name__) NULL, 0

#endif /* CONFIG_STATS_NAMES */

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_STATS_STATS_H_ */
