/*
 * Copyright (C) 2012 VoIP Embedded, Inc.
 *
 * Copyright (C) 2019 Vicente Hernando (Sonoc)
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * 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"

/** @addtogroup xhttp_prom
 * @ingroup modules
 * @{
 *
 * <h1>Overview of Operation</h1>
 * This module provides a web interface for Prometheus server.
 * It is built on top of the xhttp API module.
 */

/** @file
 *
 * This is the main file of xhttp_prom module which contains all the functions
 * related to http processing, as well as the module interface.
 */

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 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_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 fixup_metric_reset(void** param, int param_no);
static int fixup_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);

/** 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 ctx;

static xhttp_api_t xhttp_api;

/* It does not show Kamailio statistics by default. */
str xhttp_prom_stats = str_init("");

int buf_size = 0;
int timeout_minutes = 0;
char error_buf[ERROR_REASON_BUF_LEN];

/* 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,
	 0, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l1, 2, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l2, 3, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_counter_reset", (cmd_function)w_prom_counter_reset_l3, 4, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l0, 1, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l1, 2, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l2, 3, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_reset", (cmd_function)w_prom_gauge_reset_l3, 4, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l0, 2, fixup_counter_inc,
	 0, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l1, 3, fixup_counter_inc,
	 0, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l2, 4, fixup_counter_inc,
	 0, ANY_ROUTE},
	{"prom_counter_inc", (cmd_function)w_prom_counter_inc_l3, 5, fixup_counter_inc,
	 0, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l0, 2, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l1, 3, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l2, 4, fixup_metric_reset,
	 0, ANY_ROUTE},
	{"prom_gauge_set", (cmd_function)w_prom_gauge_set_l3, 5, fixup_metric_reset,
	 0, ANY_ROUTE},
	{ 0, 0, 0, 0, 0, 0}
};

static param_export_t params[]={
	{"xhttp_prom_buf_size",	INT_PARAM,	&buf_size},
	{"xhttp_prom_stats",	PARAM_STR,	&xhttp_prom_stats},
	{"prom_counter",        PARAM_STRING|USE_FUNC_PARAM, (void*)prom_counter_param},
	{"prom_gauge",          PARAM_STRING|USE_FUNC_PARAM, (void*)prom_gauge_param},
	{"xhttp_prom_timeout",	INT_PARAM,	&timeout_minutes},
	{0, 0, 0}
};

struct module_exports exports = {
	"xhttp_prom",
	DEFAULT_DLFLAGS, /* dlopen flags */
	cmds,
	params,
	0,              /* exported RPC methods */
	0,         	/* exported pseudo-variables */
	0,              /* response function */
	mod_init,       /* module initialization function */
	0,		/* per child init function */
	mod_destroy     /* destroy function */
};

/** Implementation of prom_fault function required by the management API.
 *
 * This function will be called whenever a management function
 * indicates that an error ocurred 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;
	}

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

	/* Check xhttp_prom_timeout param */
	if (timeout_minutes == 0) {
		timeout_minutes = 60;
	}

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

	return 0;
}

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

	prom_metric_close();
}

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

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

#define PROMETHEUS_URI "/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;
}

/** 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;
}

/**
 * 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 (ctx.reply.buf.s) {
		LM_ERR("Unexpected buf value [%p][%d]\n",
			   ctx.reply.buf.s, ctx.reply.buf.len);

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

send_reply:
	if (!ctx.reply_sent) {
		ret = prom_send(&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); */
/* } */

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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); */
/* 	} */
/* } */

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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;
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * 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);
}

/**
 * Parse a string and convert to double.
 *
 * /param s_number pointer to number string.
 * /param number double passed as reference.
 *
 * /return 0 on success.
 * On error value pointed by pnumber is undefined.
 */
static int double_parse_str(str *s_number, double *pnumber)
{
	char *s = NULL;
	
	if (!s_number || !s_number->s || s_number->len == 0) {
		LM_ERR("Bad s_number to convert to double\n");
		goto error;
	}

	if (!pnumber) {
		LM_ERR("No double passed by reference\n");
		goto error;
	}

	/* We generate a zero terminated string. */

	/* We set last character to zero to get a zero terminated string. */
	int len = s_number->len;
	s = pkg_malloc(len + 1);
	if (!s) {
		PKG_MEM_ERROR;
		goto error;
	}
	memcpy(s, s_number->s, len);
	s[len] = '\0'; /* Zero terminated string. */

	/* atof function does not check for errors. */
	double num = atof(s);
	LM_DBG("double number (%.*s) -> %f\n", len, s, num);

	*pnumber = num;
	pkg_free(s);
	return 0;

error:
	if (s) {
		pkg_free(s);
	}
	return -1;
}

/**
 * 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)
{
	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_gauge_set(s_name, number, NULL, NULL, NULL)) {
		LM_ERR("Cannot assign number: %f to gauge: %.*s\n", number, s_name->len, s_name->s);
		return -1;
	}

	LM_DBG("Assigned %f to gauge %.*s\n", number, s_name->len, s_name->s);
	return 1;
}

/**
 * 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)
{
	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_gauge_set(s_name, number, l1, NULL, NULL)) {
		LM_ERR("Cannot assign number: %f to gauge: %.*s (%.*s)\n",
			   number, s_name->len, s_name->s,
			   l1->len, l1->s
			);
		return -1;
	}

	LM_DBG("Assign %f to gauge %.*s (%.*s)\n", number,
		   s_name->len, s_name->s,
		   l1->len, l1->s
		);
	return 1;
}

/**
 * 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)
{
	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_gauge_set(s_name, number, l1, l2, NULL)) {
		LM_ERR("Cannot assign number: %f to gauge: %.*s (%.*s, %.*s)\n",
			   number, s_name->len, s_name->s,
			   l1->len, l1->s,
			   l2->len, l2->s
			);
		return -1;
	}

	LM_DBG("Assign %f to gauge %.*s (%.*s, %.*s)\n", number,
		   s_name->len, s_name->s,
		   l1->len, l1->s,
		   l2->len, l2->s
		);

	return 1;
}

/**
 * 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)
{
	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_gauge_set(s_name, number, l1, l2, l3)) {
		LM_ERR("Cannot assign number: %f to gauge: %.*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("Assign %f to gauge %.*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;
}

/**
 * Assign a number to a gauge.
 */
static int w_prom_gauge_set(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 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 */

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

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

/**
 * 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_set(msg, pname, pnumber, NULL, NULL, NULL);
}

/**
 * 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_set(msg, pname, pnumber, l1, NULL, NULL);
}

/**
 * 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_set(msg, pname, pnumber, l1, l2, NULL);
}

/**
 * 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_set(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_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 }
	},

	{ {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;
}

static void rpc_prom_gauge_set(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;
	}

	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_gauge_set(&s_name, number, NULL, NULL, NULL)) {
			LM_ERR("Cannot assign %f to gauge %.*s\n", number, s_name.len, s_name.s);
			rpc->fault(ct, 500, "Failed to assign %f gauge: %.*s", number,
					   s_name.len, s_name.s);
			return;
		}
		LM_DBG("Assigned %f to gauge (%.*s)\n", number, s_name.len, s_name.s);
		
	} else if (res == 1) {
		if (prom_gauge_set(&s_name, number, &l1, NULL, NULL)) {
			LM_ERR("Cannot assign %f to gauge %.*s (%.*s)\n", number, s_name.len, s_name.s,
				   l1.len, l1.s);
			rpc->fault(ct, 500, "Failed to assign %f to gauge: %.*s (%.*s)",
					   number, s_name.len, s_name.s,
					   l1.len, l1.s);
			return;
		}
		LM_DBG("Assigned %f to gauge: %.*s (%.*s)\n", number, s_name.len, s_name.s,
			   l1.len, l1.s);

	} else if (res == 2) {
		if (prom_gauge_set(&s_name, number, &l1, &l2, NULL)) {
			LM_ERR("Cannot assign %f to gauge: %.*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 assign %f to gauge: %.*s (%.*s, %.*s)",
					   number, s_name.len, s_name.s,
					   l1.len, l1.s,
					   l2.len, l2.s
				);
			return;
		}
		LM_DBG("Assigned %f to gauge: %.*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_gauge_set(&s_name, number, &l1, &l2, &l3)) {
			LM_ERR("Cannot assign %f to gauge: %.*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 assign %f to gauge: %.*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("Assigned %f to gauge: %.*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(&ctx) < 0) {
		goto clean;
	}

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

	/* Convert to zero terminated string. */
	struct xhttp_prom_reply* reply;
	reply = &(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(&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_set_doc[2] = {
	"Set a gauge to a number based on its identifier",
	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_set", rpc_prom_gauge_set, rpc_prom_gauge_set_doc, 0},
	{"xhttp_prom.metric_list_print", rpc_prom_metric_list_print, rpc_prom_metric_list_print_doc, 0},
	{0, 0, 0, 0}
};