/*
 * Copyright (C) 2012 VoIP Embedded, Inc.
 *
 * Copyright (C) 2019 Vicente Hernando (Sonoc)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Kamailio is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include <inttypes.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "../../core/sr_module.h"
#include "../../core/str.h"
#include "../../modules/xhttp/api.h"
#include "../../core/nonsip_hooks.h"
#include "../../core/mod_fix.h"
#include "../../core/kemi.h"
#include "../../core/rpc.h"
#include "../../core/rpc_lookup.h"

#include "xhttp_prom.h"
#include "prom.h"
#include "prom_metric.h"
#include "bind_prom.h"

/**
 * @file
 * @brief xHTTP_PROM :: main file for xhttp_prom module.
 *
 * This module provides a web interface for Prometheus server.
 * It is built on top of the xhttp API module.
 *
 * This is the main file of xhttp_prom module which contains all the functions
 * related to http processing, as well as the module interface.
 *
 * @addtogroup xhttp_prom
 */

MODULE_VERSION


/* Declaration of static functions. */

str XHTTP_PROM_REASON_OK = str_init("OK");
str XHTTP_PROM_CONTENT_TYPE_TEXT_HTML = str_init("text/plain; version=0.0.4");

static rpc_export_t rpc_cmds[];
static int mod_init(void);
static int child_init(int rank);
static void mod_destroy(void);
static int w_prom_check_uri(sip_msg_t *msg);
static int w_prom_dispatch(sip_msg_t *msg);
static int w_prom_counter_reset_l0(struct sip_msg *msg, char *pname);
static int w_prom_counter_reset_l1(struct sip_msg *msg, char *pname, char *l1);
static int w_prom_counter_reset_l2(
		struct sip_msg *msg, char *pname, char *l1, char *l2);
static int w_prom_counter_reset_l3(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3);
static int w_prom_gauge_reset_l0(struct sip_msg *msg, char *pname);
static int w_prom_gauge_reset_l1(struct sip_msg *msg, char *pname, char *l1);
static int w_prom_gauge_reset_l2(
		struct sip_msg *msg, char *pname, char *l1, char *l2);
static int w_prom_gauge_reset_l3(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3);
static int w_prom_counter_inc_l0(
		struct sip_msg *msg, char *pname, char *pnumber);
static int w_prom_counter_inc_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1);
static int w_prom_counter_inc_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2);
static int w_prom_counter_inc_l3(struct sip_msg *msg, char *pname,
		char *pnumber, char *l1, char *l2, char *l3);
static int w_prom_gauge_inc_l0(struct sip_msg *msg, char *pname, char *pnumber);
static int w_prom_gauge_inc_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1);
static int w_prom_gauge_inc_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2);
static int w_prom_gauge_inc_l3(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3);
static int w_prom_gauge_set_l0(struct sip_msg *msg, char *pname, char *pnumber);
static int w_prom_gauge_set_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1);
static int w_prom_gauge_set_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2);
static int w_prom_gauge_set_l3(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3);
static int w_prom_histogram_observe_l0(
		struct sip_msg *msg, char *pname, char *pnumber);
static int w_prom_histogram_observe_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1);
static int w_prom_histogram_observe_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2);
static int w_prom_histogram_observe_l3(struct sip_msg *msg, char *pname,
		char *pnumber, char *l1, char *l2, char *l3);
static int fixup_metric_reset(void **param, int param_no);
static int fixup_free_metric_reset(void **param, int param_no);
static int fixup_counter_inc(void **param, int param_no);
static int fixup_free_counter_inc(void **param, int param_no);

int prom_counter_param(modparam_t type, void *val);
int prom_gauge_param(modparam_t type, void *val);
int prom_histogram_param(modparam_t type, void *val);

/**
 * @brief The context of the xhttp_prom request being processed.
 *
 * This is a global variable that records the context of the xhttp_prom request
 * being currently processed.
 * @sa prom_ctx
 */
static prom_ctx_t _prom_ctx;

/**
 * @brief api interface for the xhttp module
 */
static xhttp_api_t xhttp_api;

/**
 * @brief api interface for the kex module
 */
static kex_api_t kex_api;

/**
 * @brief pointer to pkgmem statistics.
 */
pkg_proc_stats_t *pkg_proc_stats = NULL;

/**
 * @brief number of pkgmem statistics.
 */
int pkg_proc_stats_no = 0;

/**
 * @brief String to indicate which statistics to display.
 *
 * It does not show Kamailio statistics by default.
 */
str xhttp_prom_stats = str_init("");

/**
 * @brief string for beginning of metrics.
 */
str xhttp_prom_beginning = str_init("kamailio_");

/**
 * @brief string for metrics tags.
 */
str xhttp_prom_tags = str_init("");

/**
 * @brief helper string for metrics tags. Used for appending tag lists
 */
char *xhttp_prom_tags_comma = "";

/**
 * @brief helper string for metrics tags. Used for appending to metric
 */
char *xhttp_prom_tags_braces = "";

int buf_size = 0; /**< size of buffer that contains the reply. */

int timeout_minutes = 60; /**< timeout in minutes to delete old metrics. */

int uptime_stat_enabled = 0; /**< enable or disable uptime statistic. */

int pkgmem_stats_enabled = 0; /**< enable or disable pkgmem statistics. */

int metadata_flags = 0; /**< include metrics metadata in text output. */

char error_buf[ERROR_REASON_BUF_LEN];

/* clang-format off */
/* module commands */
static cmd_export_t cmds[] = {
	{"prom_check_uri", (cmd_function)w_prom_check_uri, 0, 0, 0, REQUEST_ROUTE | EVENT_ROUTE},
	{"prom_dispatch", (cmd_function)w_prom_dispatch, 0, 0, 0, REQUEST_ROUTE | EVENT_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l0, 1, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l1, 2, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l2, 3, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l3, 4, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l0, 1, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l1, 2, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l2, 3, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l3, 4, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l0, 2, fixup_counter_inc, fixup_free_counter_inc, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l1, 3, fixup_counter_inc, fixup_free_counter_inc, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l2, 4, fixup_counter_inc, fixup_free_counter_inc, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l3, 5, fixup_counter_inc, fixup_free_counter_inc, ANY_ROUTE},
	{"prom_gauge_inc", (cmd_function)w_prom_gauge_inc_l0, 2, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_inc", (cmd_function)w_prom_gauge_inc_l1, 3, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_inc", (cmd_function)w_prom_gauge_inc_l2, 4, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_inc", (cmd_function)w_prom_gauge_inc_l3, 5, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l0, 2, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l1, 3, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l2, 4, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l3, 5, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_histogram_observe", (cmd_function)w_prom_histogram_observe_l0, 2, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_histogram_observe", (cmd_function)w_prom_histogram_observe_l1, 3, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_histogram_observe", (cmd_function)w_prom_histogram_observe_l2, 4, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"prom_histogram_observe", (cmd_function)w_prom_histogram_observe_l3, 5, fixup_metric_reset, fixup_free_metric_reset, ANY_ROUTE},
	{"bind_prom", (cmd_function)bind_prom, NO_SCRIPT, 0, 0, 0},
	{0, 0, 0, 0, 0, 0}
};

static param_export_t params[] = {
	{"xhttp_prom_buf_size", PARAM_INT, &buf_size},
	{"xhttp_prom_stats", PARAM_STR, &xhttp_prom_stats},
	{"xhttp_prom_beginning", PARAM_STR, &xhttp_prom_beginning},
	{"xhttp_prom_tags", PARAM_STR, &xhttp_prom_tags},
	{"prom_counter", PARAM_STRING | PARAM_USE_FUNC, (void *)prom_counter_param},
	{"prom_gauge", PARAM_STRING | PARAM_USE_FUNC, (void *)prom_gauge_param},
	{"prom_histogram", PARAM_STRING | PARAM_USE_FUNC, (void *)prom_histogram_param},
	{"xhttp_prom_timeout", PARAM_INT, &timeout_minutes},
	{"xhttp_prom_uptime_stat", PARAM_INT, &uptime_stat_enabled},
	{"xhttp_prom_pkg_stats", PARAM_INT, &pkgmem_stats_enabled},
	{"xhttp_prom_metadata_flags", PARAM_INT, &metadata_flags},
	{0, 0, 0}
};

struct module_exports exports = {
	"xhttp_prom",    /* module name */
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,            /* cmd (cfg function) exports */
	params,          /* param exports */
	0,               /* RPC method exports */
	0,               /* pv exports */
	0,               /* response handling function */
	mod_init,        /* module init function */
	child_init,      /* per-child init function */
	mod_destroy      /* module destroy function */
};
/* clang-format on */

/**
 * @brief Implementation of prom_fault function required by the management API.
 *
 * This function will be called whenever a management function
 * indicates that an error occurred while it was processing the request. The
 * function takes the reply code and reason phrase as parameters, these will
 * be put in the body of the reply.
 *
 * @param ctx A pointer to the context structure of the request being
 *            processed.
 * @param code Reason code.
 * @param fmt Formatting string used to build the reason phrase.
 */
static void prom_fault(prom_ctx_t *ctx, int code, char *fmt, ...)
{
	va_list ap;
	struct xhttp_prom_reply *reply = &ctx->reply;

	reply->code = code;
	va_start(ap, fmt);
	vsnprintf(error_buf, ERROR_REASON_BUF_LEN, fmt, ap);
	va_end(ap);
	reply->reason.len = strlen(error_buf);
	reply->reason.s = error_buf;
	/* reset body so we can print the error */
	reply->body.len = 0;

	return;
}

static int mod_init(void)
{
	/* Register RPC commands. */
	if(rpc_register_array(rpc_cmds) != 0) {
		LM_ERR("failed to register RPC commands\n");
		return -1;
	}

	/* bind the XHTTP API */
	if(xhttp_load_api(&xhttp_api) < 0) {
		LM_ERR("cannot bind to XHTTP API\n");
		return -1;
	}

	/* bind the kex API */
	if(pkgmem_stats_enabled) {
		if(load_kex_api(&kex_api) < 0) {
			LM_ERR("cannot bind to kex API - required for pkgmem stats\n");
			return -1;
		}
	}

	/* Check xhttp_prom_buf_size param */
	if(buf_size == 0)
		buf_size = pkg_mem_size / 3;

	memset(&_prom_ctx, 0, sizeof(prom_ctx_t));

	/* Initialize Prometheus metrics. */
	if(prom_metric_init()) {
		LM_ERR("Cannot initialize Prometheus metrics\n");
		return -1;
	}

	/* Write helper variables used to make handling additional metrics easier*/
	if(xhttp_prom_tags.len > 0) {
		/* 4 characters more for comma, space and terminating null */
		size_t len_comma = xhttp_prom_tags.len + 4;
		xhttp_prom_tags_comma = shm_malloc(len_comma);
		if(xhttp_prom_tags_comma == NULL) {
			SHM_MEM_ERROR;
			return -1;
		}
		memset(xhttp_prom_tags_comma, 0, len_comma);
		snprintf(xhttp_prom_tags_comma, len_comma - 1, ", %.*s",
				xhttp_prom_tags.len, xhttp_prom_tags.s);
		/* 4 characters more for curly braces and  terminating null */
		size_t len_braces = xhttp_prom_tags.len + 4;
		xhttp_prom_tags_braces = shm_malloc(len_braces);
		if(xhttp_prom_tags_braces == NULL) {
			SHM_MEM_ERROR;
			shm_free(xhttp_prom_tags_comma);
			return -1;
		}
		memset(xhttp_prom_tags_braces, 0, len_braces);
		snprintf(xhttp_prom_tags_braces, len_braces - 1, "{%.*s}",
				xhttp_prom_tags.len, xhttp_prom_tags.s);
		LM_DBG("mod_init initalized helper variables to '%s' and '%s'\n",
				xhttp_prom_tags_comma, xhttp_prom_tags_braces);
	}

	return 0;
}

static int child_init(int rank)
{
	if(pkgmem_stats_enabled) {
		pkg_proc_stats_no = kex_api.get_pkmem_stats(&pkg_proc_stats);
	}

	return 0;
}

static void mod_destroy(void)
{
	LM_DBG("cleaning up\n");

	/* Remove helper variables for tags */
	if(xhttp_prom_tags.len > 0) {
		if(xhttp_prom_tags_comma != NULL)
			shm_free(xhttp_prom_tags_comma);
		if(xhttp_prom_tags_braces != NULL)
			shm_free(xhttp_prom_tags_braces);
	}

	prom_metric_close();
}

/**
 * @brief Parse parameters to create a counter.
 */
int prom_counter_param(modparam_t type, void *val)
{
	return prom_counter_create((char *)val);
}

/**
 * @brief Parse parameters to create a gauge.
 */
int prom_gauge_param(modparam_t type, void *val)
{
	return prom_gauge_create((char *)val);
}

/**
 * @brief Parse parameters to create a histogram.
 */
int prom_histogram_param(modparam_t type, void *val)
{
	return prom_histogram_create((char *)val);
}

#define PROMETHEUS_URI "/metrics" /**< URI to get Prometheus metrics. */
static str prom_uri = str_init(PROMETHEUS_URI);

static int ki_xhttp_prom_check_uri(sip_msg_t *msg)
{
	if(msg == NULL) {
		LM_ERR("No message\n");
		return -1;
	}

	str *uri = &msg->first_line.u.request.uri;
	LM_DBG("URI: %.*s\n", uri->len, uri->s);

	if(STR_EQ(*uri, prom_uri)) {
		LM_DBG("URI matches: %.*s\n", uri->len, uri->s);
		/* Return True */
		return 1;
	}

	/* Return False */
	LM_DBG("URI does not match: %.*s (%.*s)\n", uri->len, uri->s, prom_uri.len,
			prom_uri.s);
	return 0;
}

static int w_prom_check_uri(sip_msg_t *msg)
{
	if(msg == NULL) {
		LM_ERR("No message\n");
		return -1;
	}

	str *uri = &msg->first_line.u.request.uri;
	LM_DBG("URI: %.*s\n", uri->len, uri->s);

	if(STR_EQ(*uri, prom_uri)) {
		LM_DBG("URI matches: %.*s\n", uri->len, uri->s);
		/* Return True */
		return 1;
	}

	/* Return False */
	LM_DBG("URI does not match: %.*s (%.*s)\n", uri->len, uri->s, prom_uri.len,
			prom_uri.s);
	return -1;
}

/**
 * @brief Initialize xhttp_prom reply data structure.
 *
 * This function initializes the data structure that contains all data related
 * to the xhttp_prom reply being created. The function must be called before any
 * other function that adds data to the reply.
 *
 * @param ctx prom_ctx_t structure to be initialized.
 * @return 0 on success, a negative number on error.
 */
static int init_xhttp_prom_reply(prom_ctx_t *ctx)
{
	struct xhttp_prom_reply *reply = &ctx->reply;

	reply->code = 200;
	reply->reason = XHTTP_PROM_REASON_OK;
	reply->buf.s = pkg_malloc(buf_size);
	if(!reply->buf.s) {
		PKG_MEM_ERROR;
		prom_fault(ctx, 500, "Internal Server Error (No memory left)");
		return -1;
	}
	reply->buf.len = buf_size;
	reply->body.s = reply->buf.s;
	reply->body.len = 0;
	return 0;
}

/**
 * @brief Free buffer in reply.
 */
static void xhttp_prom_reply_free(prom_ctx_t *ctx)
{
	struct xhttp_prom_reply *reply;
	reply = &ctx->reply;

	if(reply->buf.s) {
		pkg_free(reply->buf.s);
		reply->buf.s = NULL;
		reply->buf.len = 0;
	}

	/* if (ctx->arg.s) { */
	/* 	pkg_free(ctx->arg.s); */
	/* 	ctx->arg.s = NULL; */
	/* 	ctx->arg.len = 0; */
	/* } */
	/* if (ctx->data_structs) { */
	/* 	free_data_struct(ctx->data_structs); */
	/* 	ctx->data_structs = NULL; */
	/* } */
}

static int prom_send(prom_ctx_t *ctx)
{
	struct xhttp_prom_reply *reply;

	if(ctx->reply_sent)
		return 1;

	reply = &ctx->reply;

	if(prom_stats_get(ctx, &xhttp_prom_stats)) {
		LM_DBG("prom_fault(500,\"Internal Server Error\"\n");
		prom_fault(ctx, 500, "Internal Server Error");
	}

	ctx->reply_sent = 1;
	if(reply->body.len)
		xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
				&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML, &reply->body);
	else {
		LM_DBG("xhttp_api.reply(%p, %d, %.*s, %.*s, %.*s)\n", ctx->msg,
				reply->code, (&reply->reason)->len, (&reply->reason)->s,
				(&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML)->len,
				(&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML)->s, (&reply->reason)->len,
				(&reply->reason)->s);

		xhttp_api.reply(ctx->msg, reply->code, &reply->reason,
				&XHTTP_PROM_CONTENT_TYPE_TEXT_HTML, &reply->reason);
	}

	xhttp_prom_reply_free(ctx);

	return 0;
}


static int ki_xhttp_prom_dispatch(sip_msg_t *msg)
{
	int ret = 0;

	if(msg == NULL) {
		LM_ERR("No message\n");
		return -1;
	}

	if(!IS_HTTP(msg)) {
		LM_DBG("Got non HTTP msg\n");
		return NONSIP_MSG_PASS;
	}

	/* Init xhttp_prom context */
	if(_prom_ctx.reply.buf.s) {
		LM_ERR("Unexpected buf value [%p][%d]\n", _prom_ctx.reply.buf.s,
				_prom_ctx.reply.buf.len);

		/* Something happened and this memory was not freed. */
		xhttp_prom_reply_free(&_prom_ctx);
	}
	memset(&_prom_ctx, 0, sizeof(prom_ctx_t));
	_prom_ctx.msg = msg;
	if(init_xhttp_prom_reply(&_prom_ctx) < 0) {
		goto send_reply;
	}

send_reply:
	if(!_prom_ctx.reply_sent) {
		ret = prom_send(&_prom_ctx);
	}
	if(ret < 0) {
		return -1;
	}
	return 0;
}

static int w_prom_dispatch(sip_msg_t *msg)
{
	return ki_xhttp_prom_dispatch(msg);
}

static int fixup_metric_reset(void **param, int param_no)
{
	return fixup_spve_null(param, 1);
}

static int fixup_free_metric_reset(void **param, int param_no)
{
	return fixup_free_spve_null(param, 1);
}

/**
 * @brief Reset a counter (No labels)
 */
static int ki_xhttp_prom_counter_reset_l0(struct sip_msg *msg, str *s_name)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(prom_counter_reset(s_name, NULL, NULL, NULL)) {
		LM_ERR("Cannot reset counter: %.*s\n", s_name->len, s_name->s);
		return -1;
	}

	LM_DBG("Counter %.*s reset\n", s_name->len, s_name->s);
	return 1;
}

/**
 * @brief Reset a counter (1 label)
 */
static int ki_xhttp_prom_counter_reset_l1(
		struct sip_msg *msg, str *s_name, str *l1)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(prom_counter_reset(s_name, l1, NULL, NULL)) {
		LM_ERR("Cannot reset counter: %.*s (%.*s)\n", s_name->len, s_name->s,
				l1->len, l1->s);
		return -1;
	}

	LM_DBG("Counter %.*s (%.*s) reset\n", s_name->len, s_name->s, l1->len,
			l1->s);

	return 1;
}

/**
 * @brief Reset a counter (2 labels)
 */
static int ki_xhttp_prom_counter_reset_l2(
		struct sip_msg *msg, str *s_name, str *l1, str *l2)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(prom_counter_reset(s_name, l1, l2, NULL)) {
		LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s)\n", s_name->len,
				s_name->s, l1->len, l1->s, l2->len, l2->s);
		return -1;
	}

	LM_DBG("Counter %.*s (%.*s, %.*s) reset\n", s_name->len, s_name->s, l1->len,
			l1->s, l2->len, l2->s);

	return 1;
}

/**
 * @brief Reset a counter (3 labels)
 */
static int ki_xhttp_prom_counter_reset_l3(
		struct sip_msg *msg, str *s_name, str *l1, str *l2, str *l3)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(l3 == NULL || l3->s == NULL || l3->len == 0) {
		LM_ERR("Invalid l3 string\n");
		return -1;
	}

	if(prom_counter_reset(s_name, l1, l2, l3)) {
		LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s, %.*s)\n", s_name->len,
				s_name->s, l1->len, l1->s, l2->len, l2->s, l3->len, l3->s);
		return -1;
	}

	LM_DBG("Counter %.*s (%.*s, %.*s, %.*s) reset\n", s_name->len, s_name->s,
			l1->len, l1->s, l2->len, l2->s, l3->len, l3->s);

	return 1;
}

/**
 * @brief Reset a counter.
 */
static int w_prom_counter_reset(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3)
{
	str s_name;

	if(pname == NULL) {
		LM_ERR("Invalid parameter\n");
		return -1;
	}

	if(get_str_fparam(&s_name, msg, (gparam_t *)pname) != 0) {
		LM_ERR("No counter name\n");
		return -1;
	}
	if(s_name.s == NULL || s_name.len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	str l1_str, l2_str, l3_str;
	if(l1 != NULL) {
		if(get_str_fparam(&l1_str, msg, (gparam_t *)l1) != 0) {
			LM_ERR("No label l1 in counter\n");
			return -1;
		}
		if(l1_str.s == NULL || l1_str.len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}

		if(l2 != NULL) {
			if(get_str_fparam(&l2_str, msg, (gparam_t *)l2) != 0) {
				LM_ERR("No label l2 in counter\n");
				return -1;
			}
			if(l2_str.s == NULL || l2_str.len == 0) {
				LM_ERR("Invalid l2 string\n");
				return -1;
			}

			if(l3 != NULL) {
				if(get_str_fparam(&l3_str, msg, (gparam_t *)l3) != 0) {
					LM_ERR("No label l3 in counter\n");
					return -1;
				}
				if(l3_str.s == NULL || l3_str.len == 0) {
					LM_ERR("Invalid l3 string\n");
					return -1;
				}
			} /* if l3 != NULL */

		} else {
			l3 = NULL;
		} /* if l2 != NULL */

	} else {
		l2 = NULL;
		l3 = NULL;
	} /* if l1 != NULL */

	if(prom_counter_reset(&s_name, (l1 != NULL) ? &l1_str : NULL,
			   (l2 != NULL) ? &l2_str : NULL, (l3 != NULL) ? &l3_str : NULL)) {
		LM_ERR("Cannot reset counter: %.*s\n", s_name.len, s_name.s);
		return -1;
	}

	LM_DBG("Counter %.*s reset\n", s_name.len, s_name.s);
	return 1;
}

/**
 * @brief Reset a counter (no labels)
 */
static int w_prom_counter_reset_l0(struct sip_msg *msg, char *pname)
{
	return w_prom_counter_reset(msg, pname, NULL, NULL, NULL);
}

/**
 * @brief Reset a counter (one label)
 */
static int w_prom_counter_reset_l1(struct sip_msg *msg, char *pname, char *l1)
{
	return w_prom_counter_reset(msg, pname, l1, NULL, NULL);
}

/**
 * @brief Reset a counter (two labels)
 */
static int w_prom_counter_reset_l2(
		struct sip_msg *msg, char *pname, char *l1, char *l2)
{
	return w_prom_counter_reset(msg, pname, l1, l2, NULL);
}

/**
 * @brief Reset a counter (three labels)
 */
static int w_prom_counter_reset_l3(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3)
{
	return w_prom_counter_reset(msg, pname, l1, l2, l3);
}

/**
 * @brief Reset a gauge (No labels)
 */
static int ki_xhttp_prom_gauge_reset_l0(struct sip_msg *msg, str *s_name)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(prom_gauge_reset(s_name, NULL, NULL, NULL)) {
		LM_ERR("Cannot reset gauge: %.*s\n", s_name->len, s_name->s);
		return -1;
	}

	LM_DBG("Gauge %.*s reset\n", s_name->len, s_name->s);
	return 1;
}

/**
 * @brief Reset a gauge (1 label)
 */
static int ki_xhttp_prom_gauge_reset_l1(
		struct sip_msg *msg, str *s_name, str *l1)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(prom_gauge_reset(s_name, l1, NULL, NULL)) {
		LM_ERR("Cannot reset gauge: %.*s (%.*s)\n", s_name->len, s_name->s,
				l1->len, l1->s);
		return -1;
	}

	LM_DBG("Gauge %.*s (%.*s) reset\n", s_name->len, s_name->s, l1->len, l1->s);

	return 1;
}

/**
 * @brief Reset a gauge (2 labels)
 */
static int ki_xhttp_prom_gauge_reset_l2(
		struct sip_msg *msg, str *s_name, str *l1, str *l2)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(prom_gauge_reset(s_name, l1, l2, NULL)) {
		LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s)\n", s_name->len,
				s_name->s, l1->len, l1->s, l2->len, l2->s);
		return -1;
	}

	LM_DBG("Gauge %.*s (%.*s, %.*s) reset\n", s_name->len, s_name->s, l1->len,
			l1->s, l2->len, l2->s);

	return 1;
}

/**
 * @brief Reset a gauge (3 labels)
 */
static int ki_xhttp_prom_gauge_reset_l3(
		struct sip_msg *msg, str *s_name, str *l1, str *l2, str *l3)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(l3 == NULL || l3->s == NULL || l3->len == 0) {
		LM_ERR("Invalid l3 string\n");
		return -1;
	}

	if(prom_gauge_reset(s_name, l1, l2, l3)) {
		LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s, %.*s)\n", s_name->len,
				s_name->s, l1->len, l1->s, l2->len, l2->s, l3->len, l3->s);
		return -1;
	}

	LM_DBG("Gauge %.*s (%.*s, %.*s, %.*s) reset\n", s_name->len, s_name->s,
			l1->len, l1->s, l2->len, l2->s, l3->len, l3->s);

	return 1;
}

/**
 * @brief Reset a gauge.
 */
static int w_prom_gauge_reset(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3)
{
	str s_name;

	if(pname == NULL) {
		LM_ERR("Invalid parameter\n");
		return -1;
	}

	if(get_str_fparam(&s_name, msg, (gparam_t *)pname) != 0) {
		LM_ERR("No gauge name\n");
		return -1;
	}
	if(s_name.s == NULL || s_name.len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	str l1_str, l2_str, l3_str;
	if(l1 != NULL) {
		if(get_str_fparam(&l1_str, msg, (gparam_t *)l1) != 0) {
			LM_ERR("No label l1 in gauge\n");
			return -1;
		}
		if(l1_str.s == NULL || l1_str.len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}

		if(l2 != NULL) {
			if(get_str_fparam(&l2_str, msg, (gparam_t *)l2) != 0) {
				LM_ERR("No label l2 in gauge\n");
				return -1;
			}
			if(l2_str.s == NULL || l2_str.len == 0) {
				LM_ERR("Invalid l2 string\n");
				return -1;
			}

			if(l3 != NULL) {
				if(get_str_fparam(&l3_str, msg, (gparam_t *)l3) != 0) {
					LM_ERR("No label l3 in gauge\n");
					return -1;
				}
				if(l3_str.s == NULL || l3_str.len == 0) {
					LM_ERR("Invalid l3 string\n");
					return -1;
				}
			} /* if l3 != NULL */

		} else {
			l3 = NULL;
		} /* if l2 != NULL */

	} else {
		l2 = NULL;
		l3 = NULL;
	} /* if l1 != NULL */

	if(prom_gauge_reset(&s_name, (l1 != NULL) ? &l1_str : NULL,
			   (l2 != NULL) ? &l2_str : NULL, (l3 != NULL) ? &l3_str : NULL)) {
		LM_ERR("Cannot reset gauge: %.*s\n", s_name.len, s_name.s);
		return -1;
	}

	LM_DBG("Gauge %.*s reset\n", s_name.len, s_name.s);
	return 1;
}

/**
 * @brief Reset a gauge (no labels)
 */
static int w_prom_gauge_reset_l0(struct sip_msg *msg, char *pname)
{
	return w_prom_gauge_reset(msg, pname, NULL, NULL, NULL);
}

/**
 * @brief Reset a gauge (one label)
 */
static int w_prom_gauge_reset_l1(struct sip_msg *msg, char *pname, char *l1)
{
	return w_prom_gauge_reset(msg, pname, l1, NULL, NULL);
}

/**
 * @brief Reset a gauge (two labels)
 */
static int w_prom_gauge_reset_l2(
		struct sip_msg *msg, char *pname, char *l1, char *l2)
{
	return w_prom_gauge_reset(msg, pname, l1, l2, NULL);
}

/**
 * @brief Reset a gauge (three labels)
 */
static int w_prom_gauge_reset_l3(
		struct sip_msg *msg, char *pname, char *l1, char *l2, char *l3)
{
	return w_prom_gauge_reset(msg, pname, l1, l2, l3);
}

static int fixup_counter_inc(void **param, int param_no)
{
	if(param_no == 1 || param_no == 2) {
		return fixup_spve_igp(param, param_no);
	} else {
		return fixup_spve_null(param, 1);
	}
}

static int fixup_free_counter_inc(void **param, int param_no)
{
	if(param_no == 1 || param_no == 2) {
		return fixup_free_spve_igp(param, param_no);
	} else {
		return fixup_free_spve_null(param, 1);
	}
}

/**
 * @brief Add an integer to a counter (No labels).
 */
static int ki_xhttp_prom_counter_inc_l0(
		struct sip_msg *msg, str *s_name, int number)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return -1;
	}

	if(prom_counter_inc(s_name, number, NULL, NULL, NULL)) {
		LM_ERR("Cannot add number: %d to counter: %.*s\n", number, s_name->len,
				s_name->s);
		return -1;
	}

	LM_DBG("Added %d to counter %.*s\n", number, s_name->len, s_name->s);
	return 1;
}

/**
 * @brief Add an integer to a counter (1 label).
 */
static int ki_xhttp_prom_counter_inc_l1(
		struct sip_msg *msg, str *s_name, int number, str *l1)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(prom_counter_inc(s_name, number, l1, NULL, NULL)) {
		LM_ERR("Cannot add number: %d to counter: %.*s (%.*s)\n", number,
				s_name->len, s_name->s, l1->len, l1->s);
		return -1;
	}

	LM_DBG("Added %d to counter %.*s (%.*s)\n", number, s_name->len, s_name->s,
			l1->len, l1->s);

	return 1;
}

/**
 * @brief Add an integer to a counter (2 labels).
 */
static int ki_xhttp_prom_counter_inc_l2(
		struct sip_msg *msg, str *s_name, int number, str *l1, str *l2)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(prom_counter_inc(s_name, number, l1, l2, NULL)) {
		LM_ERR("Cannot add number: %d to counter: %.*s (%.*s, %.*s)\n", number,
				s_name->len, s_name->s, l1->len, l1->s, l2->len, l2->s);
		return -1;
	}

	LM_DBG("Added %d to counter %.*s (%.*s, %.*s)\n", number, s_name->len,
			s_name->s, l1->len, l1->s, l2->len, l2->s);

	return 1;
}

/**
 * @brief Add an integer to a counter (3 labels).
 */
static int ki_xhttp_prom_counter_inc_l3(
		struct sip_msg *msg, str *s_name, int number, str *l1, str *l2, str *l3)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(l3 == NULL || l3->s == NULL || l3->len == 0) {
		LM_ERR("Invalid l3 string\n");
		return -1;
	}

	if(prom_counter_inc(s_name, number, l1, l2, l3)) {
		LM_ERR("Cannot add number: %d to counter: %.*s (%.*s, %.*s, %.*s)\n",
				number, s_name->len, s_name->s, l1->len, l1->s, l2->len, l2->s,
				l3->len, l3->s);
		return -1;
	}

	LM_DBG("Added %d to counter %.*s (%.*s, %.*s, %.*s)\n", number, s_name->len,
			s_name->s, l1->len, l1->s, l2->len, l2->s, l3->len, l3->s);

	return 1;
}

/**
 * @brief Add an integer to a counter.
 */
static int w_prom_counter_inc(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3)
{
	int number;
	str s_name;

	if(pname == NULL || pnumber == 0) {
		LM_ERR("Invalid parameters\n");
		return -1;
	}

	if(get_str_fparam(&s_name, msg, (gparam_t *)pname) != 0) {
		LM_ERR("No counter name\n");
		return -1;
	}
	if(s_name.s == NULL || s_name.len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(get_int_fparam(&number, msg, (gparam_p)pnumber) != 0) {
		LM_ERR("no number\n");
		return -1;
	}
	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return -1;
	}

	str l1_str, l2_str, l3_str;
	if(l1 != NULL) {
		if(get_str_fparam(&l1_str, msg, (gparam_t *)l1) != 0) {
			LM_ERR("No label l1 in counter\n");
			return -1;
		}
		if(l1_str.s == NULL || l1_str.len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}

		if(l2 != NULL) {
			if(get_str_fparam(&l2_str, msg, (gparam_t *)l2) != 0) {
				LM_ERR("No label l2 in counter\n");
				return -1;
			}
			if(l2_str.s == NULL || l2_str.len == 0) {
				LM_ERR("Invalid l2 string\n");
				return -1;
			}

			if(l3 != NULL) {
				if(get_str_fparam(&l3_str, msg, (gparam_t *)l3) != 0) {
					LM_ERR("No label l3 in counter\n");
					return -1;
				}
				if(l3_str.s == NULL || l3_str.len == 0) {
					LM_ERR("Invalid l3 string\n");
					return -1;
				}
			} /* if l3 != NULL */

		} else {
			l3 = NULL;
		} /* if l2 != NULL */

	} else {
		l2 = NULL;
		l3 = NULL;
	} /* if l1 != NULL */

	if(prom_counter_inc(&s_name, number, (l1 != NULL) ? &l1_str : NULL,
			   (l2 != NULL) ? &l2_str : NULL, (l3 != NULL) ? &l3_str : NULL)) {
		LM_ERR("Cannot add number: %d to counter: %.*s\n", number, s_name.len,
				s_name.s);
		return -1;
	}

	LM_DBG("Added %d to counter %.*s\n", number, s_name.len, s_name.s);
	return 1;
}

/**
 * @brief Add an integer to a counter (no labels)
 */
static int w_prom_counter_inc_l0(
		struct sip_msg *msg, char *pname, char *pnumber)
{
	return w_prom_counter_inc(msg, pname, pnumber, NULL, NULL, NULL);
}

/**
 * @brief Add an integer to a counter (1 labels)
 */
static int w_prom_counter_inc_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1)
{
	return w_prom_counter_inc(msg, pname, pnumber, l1, NULL, NULL);
}

/**
 * @brief Add an integer to a counter (2 labels)
 */
static int w_prom_counter_inc_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2)
{
	return w_prom_counter_inc(msg, pname, pnumber, l1, l2, NULL);
}

/**
 * @brief Add an integer to a counter (3 labels)
 */
static int w_prom_counter_inc_l3(struct sip_msg *msg, char *pname,
		char *pnumber, char *l1, char *l2, char *l3)
{
	return w_prom_counter_inc(msg, pname, pnumber, l1, l2, l3);
}

/**
 * @brief Auxiliary function to be used by gauge set and gauge
 * increase KEMI functions.
 */
static int ki_xhttp_prom_gauge_apply(struct sip_msg *msg, str *s_name,
		str *s_number, str *l1, str *l2, str *l3, int label_no,
		int (*update_func)(str *, double, str *, str *, str *))
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(s_number == NULL || s_number->s == NULL || s_number->len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	if(label_no > 0) {
		if(l1 == NULL || l1->s == NULL || l1->len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}
	} else {
		l1 = NULL;
	}

	if(label_no > 1) {
		if(l2 == NULL || l2->s == NULL || l2->len == 0) {
			LM_ERR("Invalid l2 string\n");
			return -1;
		}
	} else {
		l2 = NULL;
	}

	if(label_no > 2) {
		if(l3 == NULL || l3->s == NULL || l3->len == 0) {
			LM_ERR("Invalid l3 string\n");
			return -1;
		}
	} else {
		l3 = NULL;
	}

	const char *action_descr = (update_func == prom_gauge_set	? "assign"
								: update_func == prom_gauge_inc ? "add"
																: "apply");

	if(update_func(s_name, number, l1, l2, l3)) {
		LM_ERR("Cannot %s number: %f to gauge: %.*s (%.*s, %.*s, %.*s)\n",
				action_descr, number, s_name->len, s_name->s,
				(l1) ? l1->len : 0, (l1) ? l1->s : "", (l2) ? l2->len : 0,
				(l2) ? l2->s : "", (l3) ? l3->len : 0, (l3) ? l3->s : "");
		return -1;
	}

	LM_DBG("Update gauge %.*s (%.*s, %.*s, %.*s): %s %f\n", s_name->len,
			s_name->s, (l1) ? l1->len : 0, (l1) ? l1->s : "",
			(l2) ? l2->len : 0, (l2) ? l2->s : "", (l3) ? l3->len : 0,
			(l3) ? l3->s : "", action_descr, number);

	return 1;
}

/**
 * @brief Increase gauge by a given number (no labels).
 */
static int ki_xhttp_prom_gauge_inc_l0(
		struct sip_msg *msg, str *s_name, str *s_number)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, NULL, NULL, NULL, 0, prom_gauge_inc);
}

/**
 * @brief Increase gauge by a given number (1 label).
 */
static int ki_xhttp_prom_gauge_inc_l1(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, NULL, NULL, 1, prom_gauge_inc);
}

/**
 * @brief Increase gauge by a given number (2 labels).
 */
static int ki_xhttp_prom_gauge_inc_l2(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1, str *l2)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, l2, NULL, 2, prom_gauge_inc);
}

/**
 * @brief Increase gauge by a given number (3 labels).
 */
static int ki_xhttp_prom_gauge_inc_l3(struct sip_msg *msg, str *s_name,
		str *s_number, str *l1, str *l2, str *l3)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, l2, l3, 3, prom_gauge_inc);
}

/**
 * @brief Set a number to a gauge (No labels).
 */
static int ki_xhttp_prom_gauge_set_l0(
		struct sip_msg *msg, str *s_name, str *s_number)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, NULL, NULL, NULL, 0, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (1 label).
 */
static int ki_xhttp_prom_gauge_set_l1(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, NULL, NULL, 1, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (2 labels).
 */
static int ki_xhttp_prom_gauge_set_l2(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1, str *l2)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, l2, NULL, 2, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (3 labels).
 */
static int ki_xhttp_prom_gauge_set_l3(struct sip_msg *msg, str *s_name,
		str *s_number, str *l1, str *l2, str *l3)
{
	return ki_xhttp_prom_gauge_apply(
			msg, s_name, s_number, l1, l2, l3, 3, prom_gauge_set);
}

/**
 * @brief Update gauge value.
 */
static int w_prom_gauge_apply(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3,
		int (*update_func)(str *, double, str *, str *, str *))
{
	str s_number;
	str s_name;

	if(pname == NULL || pnumber == 0) {
		LM_ERR("Invalid parameters\n");
		return -1;
	}

	if(get_str_fparam(&s_name, msg, (gparam_t *)pname) != 0) {
		LM_ERR("No gauge name\n");
		return -1;
	}
	if(s_name.s == NULL || s_name.len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(get_str_fparam(&s_number, msg, (gparam_t *)pnumber) != 0) {
		LM_ERR("No gauge number\n");
		return -1;
	}
	if(s_number.s == NULL || s_number.len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(&s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	str l1_str, l2_str, l3_str;
	if(l1 != NULL) {
		if(get_str_fparam(&l1_str, msg, (gparam_t *)l1) != 0) {
			LM_ERR("No label l1 in counter\n");
			return -1;
		}
		if(l1_str.s == NULL || l1_str.len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}

		if(l2 != NULL) {
			if(get_str_fparam(&l2_str, msg, (gparam_t *)l2) != 0) {
				LM_ERR("No label l2 in counter\n");
				return -1;
			}
			if(l2_str.s == NULL || l2_str.len == 0) {
				LM_ERR("Invalid l2 string\n");
				return -1;
			}

			if(l3 != NULL) {
				if(get_str_fparam(&l3_str, msg, (gparam_t *)l3) != 0) {
					LM_ERR("No label l3 in counter\n");
					return -1;
				}
				if(l3_str.s == NULL || l3_str.len == 0) {
					LM_ERR("Invalid l3 string\n");
					return -1;
				}
			} /* if l3 != NULL */

		} else {
			l3 = NULL;
		} /* if l2 != NULL */

	} else {
		l2 = NULL;
		l3 = NULL;
	} /* if l1 != NULL */

	const char *action_descr = (update_func == prom_gauge_set	? "assign"
								: update_func == prom_gauge_inc ? "add"
																: "apply");

	if(update_func(&s_name, number, (l1 != NULL) ? &l1_str : NULL,
			   (l2 != NULL) ? &l2_str : NULL, (l3 != NULL) ? &l3_str : NULL)) {
		LM_ERR("Cannot %s number: %f to gauge: %.*s\n", action_descr, number,
				s_name.len, s_name.s);
		return -1;
	}

	LM_DBG("Update gauge %.*s: %s %f\n", s_name.len, s_name.s, action_descr,
			number);
	return 1;
}

/**
 * @brief Increase/decrease a gauge (no labels)
 */
static int w_prom_gauge_inc_l0(struct sip_msg *msg, char *pname, char *pnumber)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, NULL, NULL, NULL, prom_gauge_inc);
}

/**
 * @brief Increase/decrease a gauge (1 label)
 */
static int w_prom_gauge_inc_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, l1, NULL, NULL, prom_gauge_inc);
}

/**
 * @brief Increase/decrease a gauge (2 labels)
 */
static int w_prom_gauge_inc_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, l1, l2, NULL, prom_gauge_inc);
}

/**
 * @brief Increase/decrease a gauge (3 labels)
 */
static int w_prom_gauge_inc_l3(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3)
{
	return w_prom_gauge_apply(msg, pname, pnumber, l1, l2, l3, prom_gauge_inc);
}

/**
 * @brief Assign a number to a gauge (no labels)
 */
static int w_prom_gauge_set_l0(struct sip_msg *msg, char *pname, char *pnumber)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, NULL, NULL, NULL, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (1 labels)
 */
static int w_prom_gauge_set_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, l1, NULL, NULL, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (2 labels)
 */
static int w_prom_gauge_set_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2)
{
	return w_prom_gauge_apply(
			msg, pname, pnumber, l1, l2, NULL, prom_gauge_set);
}

/**
 * @brief Assign a number to a gauge (3 labels)
 */
static int w_prom_gauge_set_l3(struct sip_msg *msg, char *pname, char *pnumber,
		char *l1, char *l2, char *l3)
{
	return w_prom_gauge_apply(msg, pname, pnumber, l1, l2, l3, prom_gauge_set);
}

/**
 * @brief Observe a number in a histogram (No labels).
 */
static int ki_xhttp_prom_histogram_observe_l0(
		struct sip_msg *msg, str *s_name, str *s_number)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(s_number == NULL || s_number->s == NULL || s_number->len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	if(prom_histogram_observe(s_name, number, NULL, NULL, NULL)) {
		LM_ERR("Cannot observe number: %f in histogram: %.*s\n", number,
				s_name->len, s_name->s);
		return -1;
	}

	LM_DBG("Observed %f in histogram %.*s\n", number, s_name->len, s_name->s);
	return 1;
}

/**
 * @brief Observe a number in a histogram (1 label).
 */
static int ki_xhttp_prom_histogram_observe_l1(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(s_number == NULL || s_number->s == NULL || s_number->len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(prom_histogram_observe(s_name, number, l1, NULL, NULL)) {
		LM_ERR("Cannot observe number: %f in histogram: %.*s (%.*s)\n", number,
				s_name->len, s_name->s, l1->len, l1->s);
		return -1;
	}

	LM_DBG("Observed %f in histogram %.*s (%.*s)\n", number, s_name->len,
			s_name->s, l1->len, l1->s);
	return 1;
}

/**
 * @brief Observe a number in a histogram (2 labels).
 */
static int ki_xhttp_prom_histogram_observe_l2(
		struct sip_msg *msg, str *s_name, str *s_number, str *l1, str *l2)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(s_number == NULL || s_number->s == NULL || s_number->len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(prom_histogram_observe(s_name, number, l1, l2, NULL)) {
		LM_ERR("Cannot observe number: %f in histogram: %.*s (%.*s, %.*s)\n",
				number, s_name->len, s_name->s, l1->len, l1->s, l2->len, l2->s);
		return -1;
	}

	LM_DBG("Observed %f in histogram %.*s (%.*s, %.*s)\n", number, s_name->len,
			s_name->s, l1->len, l1->s, l2->len, l2->s);

	return 1;
}

/**
 * @brief Observe a number in a histogram (3 labels).
 */
static int ki_xhttp_prom_histogram_observe_l3(struct sip_msg *msg, str *s_name,
		str *s_number, str *l1, str *l2, str *l3)
{
	if(s_name == NULL || s_name->s == NULL || s_name->len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(s_number == NULL || s_number->s == NULL || s_number->len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	if(l1 == NULL || l1->s == NULL || l1->len == 0) {
		LM_ERR("Invalid l1 string\n");
		return -1;
	}

	if(l2 == NULL || l2->s == NULL || l2->len == 0) {
		LM_ERR("Invalid l2 string\n");
		return -1;
	}

	if(l3 == NULL || l3->s == NULL || l3->len == 0) {
		LM_ERR("Invalid l3 string\n");
		return -1;
	}

	if(prom_histogram_observe(s_name, number, l1, l2, l3)) {
		LM_ERR("Cannot observe number: %f in histogram: %.*s (%.*s, %.*s, "
			   "%.*s)\n",
				number, s_name->len, s_name->s, l1->len, l1->s, l2->len, l2->s,
				l3->len, l3->s);
		return -1;
	}

	LM_DBG("Observed %f in histogram %.*s (%.*s, %.*s, %.*s)\n", number,
			s_name->len, s_name->s, l1->len, l1->s, l2->len, l2->s, l3->len,
			l3->s);

	return 1;
}

/**
 * @brief Observe function for a histogram.
 */
static int w_prom_histogram_observe(struct sip_msg *msg, char *pname,
		char *pnumber, char *l1, char *l2, char *l3)
{
	str s_number;
	str s_name;

	if(pname == NULL || pnumber == 0) {
		LM_ERR("Invalid parameters\n");
		return -1;
	}

	if(get_str_fparam(&s_name, msg, (gparam_t *)pname) != 0) {
		LM_ERR("No histogram name\n");
		return -1;
	}
	if(s_name.s == NULL || s_name.len == 0) {
		LM_ERR("Invalid name string\n");
		return -1;
	}

	if(get_str_fparam(&s_number, msg, (gparam_t *)pnumber) != 0) {
		LM_ERR("No histogram number to observe\n");
		return -1;
	}
	if(s_number.s == NULL || s_number.len == 0) {
		LM_ERR("Invalid number string\n");
		return -1;
	}

	double number;
	if(double_parse_str(&s_number, &number)) {
		LM_ERR("Cannot parse double\n");
		return -1;
	}

	str l1_str, l2_str, l3_str;
	if(l1 != NULL) {
		if(get_str_fparam(&l1_str, msg, (gparam_t *)l1) != 0) {
			LM_ERR("No label l1 in counter\n");
			return -1;
		}
		if(l1_str.s == NULL || l1_str.len == 0) {
			LM_ERR("Invalid l1 string\n");
			return -1;
		}

		if(l2 != NULL) {
			if(get_str_fparam(&l2_str, msg, (gparam_t *)l2) != 0) {
				LM_ERR("No label l2 in counter\n");
				return -1;
			}
			if(l2_str.s == NULL || l2_str.len == 0) {
				LM_ERR("Invalid l2 string\n");
				return -1;
			}

			if(l3 != NULL) {
				if(get_str_fparam(&l3_str, msg, (gparam_t *)l3) != 0) {
					LM_ERR("No label l3 in counter\n");
					return -1;
				}
				if(l3_str.s == NULL || l3_str.len == 0) {
					LM_ERR("Invalid l3 string\n");
					return -1;
				}
			} /* if l3 != NULL */

		} else {
			l3 = NULL;
		} /* if l2 != NULL */

	} else {
		l2 = NULL;
		l3 = NULL;
	} /* if l1 != NULL */

	if(prom_histogram_observe(&s_name, number, (l1 != NULL) ? &l1_str : NULL,
			   (l2 != NULL) ? &l2_str : NULL, (l3 != NULL) ? &l3_str : NULL)) {
		LM_ERR("Cannot observe number: %f in histogram : %.*s\n", number,
				s_name.len, s_name.s);
		return -1;
	}

	LM_DBG("Observed %f in histogram %.*s\n", number, s_name.len, s_name.s);
	return 1;
}

/**
 * @brief Observe a number in a histogram (no labels)
 */
static int w_prom_histogram_observe_l0(
		struct sip_msg *msg, char *pname, char *pnumber)
{
	return w_prom_histogram_observe(msg, pname, pnumber, NULL, NULL, NULL);
}

/**
 * @brief Observe a number in a histogram (1 labels)
 */
static int w_prom_histogram_observe_l1(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1)
{
	return w_prom_histogram_observe(msg, pname, pnumber, l1, NULL, NULL);
}

/**
 * @brief Observe a number in a histogram (2 labels)
 */
static int w_prom_histogram_observe_l2(
		struct sip_msg *msg, char *pname, char *pnumber, char *l1, char *l2)
{
	return w_prom_histogram_observe(msg, pname, pnumber, l1, l2, NULL);
}

/**
 * @brief Observe a number in a histogram (3 labels)
 */
static int w_prom_histogram_observe_l3(struct sip_msg *msg, char *pname,
		char *pnumber, char *l1, char *l2, char *l3)
{
	return w_prom_histogram_observe(msg, pname, pnumber, l1, l2, l3);
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_xhttp_prom_exports[] = {
	{ str_init("xhttp_prom"), str_init("dispatch"),
		SR_KEMIP_INT, ki_xhttp_prom_dispatch,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("check_uri"),
		SR_KEMIP_INT, ki_xhttp_prom_check_uri,
		{ SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_reset_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l0,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_reset_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l1,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_reset_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l2,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_reset_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_reset_l3,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_reset_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l0,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_reset_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l1,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_reset_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l2,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_reset_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_reset_l3,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_inc_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l0,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_inc_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l1,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_inc_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l2,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("counter_inc_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_counter_inc_l3,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_inc_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_inc_l0,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_inc_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_inc_l1,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_inc_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_inc_l2,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_inc_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_inc_l3,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_set_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l0,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_set_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l1,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_set_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l2,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("gauge_set_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_gauge_set_l3,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("histogram_observe_l0"),
	    SR_KEMIP_INT, ki_xhttp_prom_histogram_observe_l0,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("histogram_observe_l1"),
	    SR_KEMIP_INT, ki_xhttp_prom_histogram_observe_l1,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("histogram_observe_l2"),
	    SR_KEMIP_INT, ki_xhttp_prom_histogram_observe_l2,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("xhttp_prom"), str_init("histogram_observe_l3"),
	    SR_KEMIP_INT, ki_xhttp_prom_histogram_observe_l3,
		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
			SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE }
	},

	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
};
/* clang-format on */

/**
 *
 */
int mod_register(char *path, int *dlflags, void *p1, void *p2)
{
	sr_kemi_modules_add(sr_kemi_xhttp_prom_exports);
	return 0;
}

/* RPC commands. */

/* NOTE: I rename ctx variable in rpc functions to avoid collision with global ctx. */

static void rpc_prom_counter_reset(rpc_t *rpc, void *ct)
{
	str s_name;

	if(rpc->scan(ct, "S", &s_name) < 1) {
		rpc->fault(ct, 400, "required counter identifier");
		return;
	}

	if(s_name.len == 0 || s_name.s == NULL) {
		rpc->fault(ct, 400, "invalid counter identifier");
		return;
	}

	str l1, l2, l3;
	int res;
	res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
	if(res == 0) {
		/* No labels */
		if(prom_counter_reset(&s_name, NULL, NULL, NULL)) {
			LM_ERR("Cannot reset counter: %.*s\n", s_name.len, s_name.s);
			rpc->fault(ct, 500, "Failed to reset counter: %.*s", s_name.len,
					s_name.s);
			return;
		}
		LM_DBG("Counter reset: (%.*s)\n", s_name.len, s_name.s);

	} else if(res == 1) {
		if(prom_counter_reset(&s_name, &l1, NULL, NULL)) {
			LM_ERR("Cannot reset counter: %.*s (%.*s)\n", s_name.len, s_name.s,
					l1.len, l1.s);
			rpc->fault(ct, 500, "Failed to reset counter: %.*s (%.*s)",
					s_name.len, s_name.s, l1.len, l1.s);
			return;
		}
		LM_DBG("Counter reset: %.*s (%.*s)\n", s_name.len, s_name.s, l1.len,
				l1.s);

	} else if(res == 2) {
		if(prom_counter_reset(&s_name, &l1, &l2, NULL)) {
			LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s)\n", s_name.len,
					s_name.s, l1.len, l1.s, l2.len, l2.s);
			rpc->fault(ct, 500, "Failed to reset counter: %.*s (%.*s, %.*s)",
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			return;
		}
		LM_DBG("Counter reset: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s,
				l1.len, l1.s, l2.len, l2.s);

	} else if(res == 3) {
		if(prom_counter_reset(&s_name, &l1, &l2, &l3)) {
			LM_ERR("Cannot reset counter: %.*s (%.*s, %.*s, %.*s)\n",
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len,
					l3.s);
			rpc->fault(ct, 500,
					"Failed to reset counter: %.*s (%.*s, %.*s, %.*s)",
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len,
					l3.s);
			return;
		}
		LM_DBG("Counter reset: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
				l1.len, l1.s, l2.len, l2.s, l3.len, l3.s);

	} else {
		LM_ERR("Strange return value: %d\n", res);
		rpc->fault(ct, 500, "Strange return value: %d", res);

	} /* if res == 0 */

	return;
}

static void rpc_prom_counter_inc(rpc_t *rpc, void *ct)
{
	str s_name;

	if(rpc->scan(ct, "S", &s_name) < 1) {
		rpc->fault(ct, 400, "required counter identifier");
		return;
	}

	if(s_name.len == 0 || s_name.s == NULL) {
		rpc->fault(ct, 400, "invalid counter identifier");
		return;
	}

	int number;
	if(rpc->scan(ct, "d", &number) < 1) {
		rpc->fault(ct, 400, "required number argument");
		return;
	}
	if(number < 0) {
		LM_ERR("invalid negative number parameter\n");
		return;
	}

	str l1, l2, l3;
	int res;
	res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
	if(res == 0) {
		/* No labels */
		if(prom_counter_inc(&s_name, number, NULL, NULL, NULL)) {
			LM_ERR("Cannot add %d to counter: %.*s\n", number, s_name.len,
					s_name.s);
			rpc->fault(ct, 500, "Failed to add %d to counter: %.*s", number,
					s_name.len, s_name.s);
			return;
		}
		LM_DBG("Added %d to counter: (%.*s)\n", number, s_name.len, s_name.s);

	} else if(res == 1) {
		if(prom_counter_inc(&s_name, number, &l1, NULL, NULL)) {
			LM_ERR("Cannot add %d to counter: %.*s (%.*s)\n", number,
					s_name.len, s_name.s, l1.len, l1.s);
			rpc->fault(ct, 500, "Failed to add %d to counter: %.*s (%.*s)",
					number, s_name.len, s_name.s, l1.len, l1.s);
			return;
		}
		LM_DBG("Added %d to counter: %.*s (%.*s)\n", number, s_name.len,
				s_name.s, l1.len, l1.s);

	} else if(res == 2) {
		if(prom_counter_inc(&s_name, number, &l1, &l2, NULL)) {
			LM_ERR("Cannot add %d to counter: %.*s (%.*s, %.*s)\n", number,
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			rpc->fault(ct, 500,
					"Failed to add %d to counter: %.*s (%.*s, %.*s)", number,
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			return;
		}
		LM_DBG("Added %d to counter: %.*s (%.*s, %.*s)\n", number, s_name.len,
				s_name.s, l1.len, l1.s, l2.len, l2.s);

	} else if(res == 3) {
		if(prom_counter_inc(&s_name, number, &l1, &l2, &l3)) {
			LM_ERR("Cannot add %d to counter: %.*s (%.*s, %.*s, %.*s)\n",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s,
					l3.len, l3.s);
			rpc->fault(ct, 500,
					"Failed to add %d to counter: %.*s (%.*s, %.*s, %.*s)",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s,
					l3.len, l3.s);
			return;
		}
		LM_DBG("Added %d to counter: %.*s (%.*s, %.*s, %.*s)\n", number,
				s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len, l3.s);

	} else {
		LM_ERR("Strange return value: %d\n", res);
		rpc->fault(ct, 500, "Strange return value: %d", res);

	} /* if res == 0 */

	return;
}

static void rpc_prom_gauge_reset(rpc_t *rpc, void *ct)
{
	str s_name;

	if(rpc->scan(ct, "S", &s_name) < 1) {
		rpc->fault(ct, 400, "required gauge identifier");
		return;
	}

	if(s_name.len == 0 || s_name.s == NULL) {
		rpc->fault(ct, 400, "invalid gauge identifier");
		return;
	}

	str l1, l2, l3;
	int res;
	res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
	if(res == 0) {
		/* No labels */
		if(prom_gauge_reset(&s_name, NULL, NULL, NULL)) {
			LM_ERR("Cannot reset gauge: %.*s\n", s_name.len, s_name.s);
			rpc->fault(ct, 500, "Failed to reset gauge: %.*s", s_name.len,
					s_name.s);
			return;
		}
		LM_DBG("Gauge reset: (%.*s)\n", s_name.len, s_name.s);

	} else if(res == 1) {
		if(prom_gauge_reset(&s_name, &l1, NULL, NULL)) {
			LM_ERR("Cannot reset gauge: %.*s (%.*s)\n", s_name.len, s_name.s,
					l1.len, l1.s);
			rpc->fault(ct, 500, "Failed to reset gauge: %.*s (%.*s)",
					s_name.len, s_name.s, l1.len, l1.s);
			return;
		}
		LM_DBG("Gauge reset: %.*s (%.*s)\n", s_name.len, s_name.s, l1.len,
				l1.s);

	} else if(res == 2) {
		if(prom_gauge_reset(&s_name, &l1, &l2, NULL)) {
			LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s)\n", s_name.len,
					s_name.s, l1.len, l1.s, l2.len, l2.s);
			rpc->fault(ct, 500, "Failed to reset gauge: %.*s (%.*s, %.*s)",
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			return;
		}
		LM_DBG("Gauge reset: %.*s (%.*s, %.*s)\n", s_name.len, s_name.s, l1.len,
				l1.s, l2.len, l2.s);

	} else if(res == 3) {
		if(prom_gauge_reset(&s_name, &l1, &l2, &l3)) {
			LM_ERR("Cannot reset gauge: %.*s (%.*s, %.*s, %.*s)\n", s_name.len,
					s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len, l3.s);
			rpc->fault(ct, 500,
					"Failed to reset gauge: %.*s (%.*s, %.*s, %.*s)",
					s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len,
					l3.s);
			return;
		}
		LM_DBG("Gauge reset: %.*s (%.*s, %.*s, %.*s)\n", s_name.len, s_name.s,
				l1.len, l1.s, l2.len, l2.s, l3.len, l3.s);

	} else {
		LM_ERR("Strange return value: %d\n", res);
		rpc->fault(ct, 500, "Strange return value: %d", res);

	} /* if res == 0 */

	return;
}

/**
 * @brief Auxiliary function to be used by gauge set and gauge
 * increase RPC commands.
 */
static void rpc_prom_gauge_apply(rpc_t *rpc, void *ct,
		int (*update_func)(str *, double, str *, str *, str *))
{
	str s_name;

	if(rpc->scan(ct, "S", &s_name) < 1) {
		rpc->fault(ct, 400, "required gauge identifier");
		return;
	}

	if(s_name.len == 0 || s_name.s == NULL) {
		rpc->fault(ct, 400, "invalid gauge identifier");
		return;
	}

	double number;
	if(rpc->scan(ct, "f", &number) < 1) {
		rpc->fault(ct, 400, "required number argument");
		return;
	}

	const char *action_descr = (update_func == prom_gauge_set	? "assign"
								: update_func == prom_gauge_inc ? "add"
																: "apply");

	str l1, l2, l3;
	int res;
	res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
	if(res == 0) {
		/* No labels */
		if(update_func(&s_name, number, NULL, NULL, NULL)) {
			LM_ERR("Cannot %s %f to gauge %.*s\n", action_descr, number,
					s_name.len, s_name.s);
			rpc->fault(ct, 500, "Failed to %s %f gauge: %.*s", action_descr,
					number, s_name.len, s_name.s);
			return;
		}
		LM_DBG("Update gauge (%.*s): %s %f\n", s_name.len, s_name.s,
				action_descr, number);

	} else if(res == 1) {
		if(update_func(&s_name, number, &l1, NULL, NULL)) {
			LM_ERR("Cannot %s %f to gauge %.*s (%.*s)\n", action_descr, number,
					s_name.len, s_name.s, l1.len, l1.s);
			rpc->fault(ct, 500, "Failed to %s %f to gauge: %.*s (%.*s)",
					action_descr, number, s_name.len, s_name.s, l1.len, l1.s);
			return;
		}
		LM_DBG("Update gauge %.*s (%.*s): %s %f\n", s_name.len, s_name.s,
				l1.len, l1.s, action_descr, number);

	} else if(res == 2) {
		if(update_func(&s_name, number, &l1, &l2, NULL)) {
			LM_ERR("Cannot %s %f to gauge: %.*s (%.*s, %.*s)\n", action_descr,
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			rpc->fault(ct, 500, "Failed to %s %f to gauge: %.*s (%.*s, %.*s)",
					action_descr, number, s_name.len, s_name.s, l1.len, l1.s,
					l2.len, l2.s);
			return;
		}
		LM_DBG("Update gauge %.*s (%.*s, %.*s): %s %f\n", s_name.len, s_name.s,
				l1.len, l1.s, l2.len, l2.s, action_descr, number);

	} else if(res == 3) {
		if(update_func(&s_name, number, &l1, &l2, &l3)) {
			LM_ERR("Cannot %s %f to gauge: %.*s (%.*s, %.*s, %.*s)\n",
					action_descr, number, s_name.len, s_name.s, l1.len, l1.s,
					l2.len, l2.s, l3.len, l3.s);
			rpc->fault(ct, 500,
					"Failed to %s %f to gauge: %.*s (%.*s, %.*s, %.*s)",
					action_descr, number, s_name.len, s_name.s, l1.len, l1.s,
					l2.len, l2.s, l3.len, l3.s);
			return;
		}
		LM_DBG("Update gauge %.*s (%.*s, %.*s, %.*s): %s %f\n", s_name.len,
				s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len, l3.s,
				action_descr, number);

	} else {
		LM_ERR("Strange return value: %d\n", res);
		rpc->fault(ct, 500, "Strange return value: %d", res);

	} /* if res == 0 */

	return;
}

static void rpc_prom_gauge_inc(rpc_t *rpc, void *ct)
{
	rpc_prom_gauge_apply(rpc, ct, prom_gauge_inc);
}

static void rpc_prom_gauge_set(rpc_t *rpc, void *ct)
{
	rpc_prom_gauge_apply(rpc, ct, prom_gauge_set);
}

/**
 * @brief Observe a number in a histogram via RPC.
 */
static void rpc_prom_histogram_observe(rpc_t *rpc, void *ct)
{
	str s_name;

	if(rpc->scan(ct, "S", &s_name) < 1) {
		rpc->fault(ct, 400, "required histogram identifier");
		return;
	}

	if(s_name.len == 0 || s_name.s == NULL) {
		rpc->fault(ct, 400, "invalid histogram identifier");
		return;
	}

	double number;
	if(rpc->scan(ct, "f", &number) < 1) {
		rpc->fault(ct, 400, "required number argument");
		return;
	}

	str l1, l2, l3;
	int res;
	res = rpc->scan(ct, "*SSS", &l1, &l2, &l3);
	if(res == 0) {
		/* No labels */
		if(prom_histogram_observe(&s_name, number, NULL, NULL, NULL)) {
			LM_ERR("Cannot observe %f in histogram %.*s\n", number, s_name.len,
					s_name.s);
			rpc->fault(ct, 500, "Failed to observe %f in histogram: %.*s",
					number, s_name.len, s_name.s);
			return;
		}
		LM_DBG("Observed %f in histogram (%.*s)\n", number, s_name.len,
				s_name.s);

	} else if(res == 1) {
		if(prom_histogram_observe(&s_name, number, &l1, NULL, NULL)) {
			LM_ERR("Cannot observe %f in histogram %.*s (%.*s)\n", number,
					s_name.len, s_name.s, l1.len, l1.s);
			rpc->fault(ct, 500,
					"Failed to observe %f in histogram: %.*s (%.*s)", number,
					s_name.len, s_name.s, l1.len, l1.s);
			return;
		}
		LM_DBG("Observed %f in histogram: %.*s (%.*s)\n", number, s_name.len,
				s_name.s, l1.len, l1.s);

	} else if(res == 2) {
		if(prom_histogram_observe(&s_name, number, &l1, &l2, NULL)) {
			LM_ERR("Cannot observe %f in histogram: %.*s (%.*s, %.*s)\n",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			rpc->fault(ct, 500,
					"Failed to observe %f in histogram: %.*s (%.*s, %.*s)",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);
			return;
		}
		LM_DBG("Observed %f in histogram: %.*s (%.*s, %.*s)\n", number,
				s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s);

	} else if(res == 3) {
		if(prom_histogram_observe(&s_name, number, &l1, &l2, &l3)) {
			LM_ERR("Cannot observe %f in histogram: %.*s (%.*s, %.*s, %.*s)\n",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s,
					l3.len, l3.s);
			rpc->fault(ct, 500,
					"Failed to observe %f in histogram: %.*s (%.*s, %.*s, "
					"%.*s)",
					number, s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s,
					l3.len, l3.s);
			return;
		}
		LM_DBG("Observed %f in histogram: %.*s (%.*s, %.*s, %.*s)\n", number,
				s_name.len, s_name.s, l1.len, l1.s, l2.len, l2.s, l3.len, l3.s);

	} else {
		LM_ERR("Strange return value: %d\n", res);
		rpc->fault(ct, 500, "Strange return value: %d", res);

	} /* if res == 0 */

	return;
}

static void rpc_prom_metric_list_print(rpc_t *rpc, void *ct)
{
	/* We reuse ctx->reply for the occasion. */
	if(init_xhttp_prom_reply(&_prom_ctx) < 0) {
		goto clean;
	}

	if(prom_metric_list_print(&_prom_ctx)) {
		LM_ERR("Cannot print a list of metrics\n");
		goto clean;
	}

	/* Convert to zero terminated string. */
	struct xhttp_prom_reply *reply;
	reply = &(_prom_ctx.reply);
	reply->body.s[reply->body.len] = '\0';

	/* Print content of reply buffer. */
	if(rpc->rpl_printf(ct, reply->body.s) < 0) {
		LM_ERR("Error printing RPC response\n");
		goto clean;
	}

clean:

	xhttp_prom_reply_free(&_prom_ctx);

	return;
}

static const char *rpc_prom_counter_reset_doc[2] = {
		"Reset a counter based on its identifier", 0};

static const char *rpc_prom_counter_inc_doc[2] = {
		"Add a number (greater or equal to zero) to a counter based on its "
		"identifier",
		0};

static const char *rpc_prom_gauge_reset_doc[2] = {
		"Reset a gauge based on its identifier", 0};

static const char *rpc_prom_gauge_inc_doc[2] = {
		"Increase or decrease a gauge by a given number based on its "
		"identifier",
		0};

static const char *rpc_prom_gauge_set_doc[2] = {
		"Set a gauge to a number based on its identifier", 0};

static const char *rpc_prom_histogram_observe_doc[2] = {
		"Observe a number in a histogram", 0};

static const char *rpc_prom_metric_list_print_doc[2] = {
		"Print a list showing all user defined metrics", 0};

static rpc_export_t rpc_cmds[] = {
		{"xhttp_prom.counter_reset", rpc_prom_counter_reset,
				rpc_prom_counter_reset_doc, 0},
		{"xhttp_prom.counter_inc", rpc_prom_counter_inc,
				rpc_prom_counter_inc_doc, 0},
		{"xhttp_prom.gauge_reset", rpc_prom_gauge_reset,
				rpc_prom_gauge_reset_doc, 0},
		{"xhttp_prom.gauge_inc", rpc_prom_gauge_inc, rpc_prom_gauge_inc_doc, 0},
		{"xhttp_prom.gauge_set", rpc_prom_gauge_set, rpc_prom_gauge_set_doc, 0},
		{"xhttp_prom.histogram_observe", rpc_prom_histogram_observe,
				rpc_prom_histogram_observe_doc, 0},
		{"xhttp_prom.metric_list_print", rpc_prom_metric_list_print,
				rpc_prom_metric_list_print_doc, 0},
		{0, 0, 0, 0}};
