/*
 * Copyright (C) 2010 iptelorg GmbH
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/**
 * @brief counters/statistics rpcs and script functions
 * @file
 * @ingroup counters
 * Module: counters.
 * @author andrei
 */

/*!
 * \defgroup counters Counters/statistics
 *
 */

#include "../../core/modparam.h"
#include "../../core/dprint.h"
#include "../../core/compiler_opt.h"
#include "../../core/counters.h"
#include "../../core/kemi.h"

MODULE_VERSION

/* default script counter group name */
static char *cnt_script_grp = "script";

static int add_script_counter(modparam_t type, void *val);
static int cnt_inc_f(struct sip_msg *, char *, char *);
static int cnt_add_f(struct sip_msg *, char *, char *);
static int cnt_reset_f(struct sip_msg *, char *, char *);
static int cnt_fixup1(void **param, int param_no);
static int cnt_int_fixup(void **param, int param_no);


static cmd_export_t cmds[] = {
		{"cnt_inc", cnt_inc_f, 1, cnt_fixup1, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | ONSEND_ROUTE},
		{"cnt_add", cnt_add_f, 2, cnt_int_fixup, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | ONSEND_ROUTE},
		{"cnt_reset", cnt_reset_f, 1, cnt_fixup1, 0,
				REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE | ONSEND_ROUTE},
		{0, 0, 0, 0, 0, 0}};

static param_export_t params[] = {
		{"script_cnt_grp_name", PARAM_STRING, &cnt_script_grp},
		{"script_counter", PARAM_STRING | PARAM_USE_FUNC, add_script_counter},
		{0, 0, 0}};


static void cnt_get_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_get_doc[] = {
		"get counter value (takes group and counter name as parameters)", 0};

static void cnt_reset_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_reset_doc[] = {
		"reset counter (takes group and counter name as parameters)", 0};

static void cnt_get_raw_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_get_raw_doc[] = {
		"get raw counter value (debugging version)", 0};

static void cnt_grps_list_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_grps_list_doc[] = {
		"list all the counter group names", 0};

static void cnt_var_list_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_var_list_doc[] = {
		"list all the counters names in a specified group", 0};

static void cnt_grp_get_all_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_grp_get_all_doc[] = {
		"list all counter names and values in a specified group", 0};

static void cnt_help_rpc(rpc_t *rpc, void *ctx);
static const char *cnt_help_doc[] = {
		"print the description of a counter (group and counter name required).",
		0};


static rpc_export_t counters_rpc[] = {{"cnt.get", cnt_get_rpc, cnt_get_doc, 0},
		{"cnt.reset", cnt_reset_rpc, cnt_reset_doc, 0},
		{"cnt.get_raw", cnt_get_raw_rpc, cnt_get_raw_doc, 0},
		{"cnt.list_groups", cnt_grps_list_rpc, cnt_grps_list_doc, RET_ARRAY},
		{"cnt.grps_list", cnt_grps_list_rpc, cnt_grps_list_doc, RET_ARRAY},
		{"cnt.list_vars", cnt_var_list_rpc, cnt_var_list_doc, RET_ARRAY},
		{"cnt.var_list", cnt_var_list_rpc, cnt_var_list_doc, RET_ARRAY},
		{"cnt.get_vars", cnt_grp_get_all_rpc, cnt_grp_get_all_doc, 0},
		{"cnt.grp_get_all", cnt_grp_get_all_rpc, cnt_grp_get_all_doc, 0},
		{"cnt.help", cnt_help_rpc, cnt_help_doc, 0}, {0, 0, 0, 0}};


struct module_exports exports = {
		"counters",		 /* module name */
		DEFAULT_DLFLAGS, /* dlopen flags */
		cmds,			 /* cmd (cfg function) exports */
		params,			 /* param exports */
		counters_rpc,	 /* RPC method exports */
		0,				 /* pseudo-variables exports */
		0,				 /* response handling function */
		0,				 /* module init function */
		0,				 /* per-child init function */
		0				 /* module destroy function */
};


/** parse the script_counter modparam.
 *  Format:   [grp.]name[( |:)desc]
 *  E.g.:
 *           "name" => new counter: *cnt_script_grp."name"
 *           "grp.name" => new counter: "grp"."name"
 *           "name desc" => new counter "name", desc = "desc"
 *           "grp.name desc" => "grp"."name", desc = "desc".
 */
static int add_script_counter(modparam_t type, void *val)
{
	char *name;
	counter_handle_t h;
	int ret;
	char *grp;
	char *desc;
	char *p;

	if((type & PARAM_STRING) == 0) {
		BUG("bad parameter type %d\n", type);
		goto error;
	}
	name = (char *)val;
	grp = cnt_script_grp;			 /* default group */
	desc = "custom script counter."; /* default desc. */
	if((p = strchr(name, ':')) != 0 || (p = strchr(name, ' ')) != 0) {
		/* found desc. */
		*p = 0;
		for(p = p + 1; *p && (*p == ' ' || *p == '\t'); p++)
			;
		if(*p)
			desc = p;
	}
	if((p = strchr(name, '.')) != 0) {
		/* found group */
		grp = name;
		*p = 0;
		name = p + 1;
	}
	ret = counter_register(&h, grp, name, 0, 0, 0, desc, 0);
	if(ret < 0) {
		if(ret == -2) {
			ERR("counter %s.%s already registered\n", grp, name);
			return 0;
		}
		ERR("failed to register counter %s.%s\n", grp, name);
		goto error;
	}
	return 0;
error:
	return -1;
}


static int cnt_fixup1(void **param, int param_no)
{
	char *name;
	char *grp;
	char *p;
	counter_handle_t h;

	name = (char *)*param;
	grp = cnt_script_grp; /* default group */
	if((p = strchr(name, '.')) != 0) {
		/* found group */
		grp = name;
		name = p + 1;
		*p = 0;
	}
	if(counter_lookup(&h, grp, name) < 0) {
		ERR("counter %s.%s does not exist (forgot to define it?)\n", grp, name);
		return -1;
	}
	*param = (void *)(long)h.id;
	return 0;
}


static int cnt_int_fixup(void **param, int param_no)
{
	char *name;
	char *grp;
	char *p;
	counter_handle_t h;

	if(param_no == 1) {
		name = (char *)*param;
		grp = cnt_script_grp; /* default group */
		if((p = strchr(name, '.')) != 0) {
			/* found group */
			grp = name;
			name = p + 1;
			*p = 0;
		}
		if(counter_lookup(&h, grp, name) < 0) {
			ERR("counter %s.%s does not exist (forgot to define it?)\n", grp,
					name);
			return -1;
		}
		*param = (void *)(long)h.id;
	} else
		return fixup_var_int_2(param, param_no);
	return 0;
}


/**
 *
 */
static int cnt_inc_f(struct sip_msg *msg, char *handle, char *bar)
{
	counter_handle_t h;

	h.id = (long)(void *)handle;
	counter_inc(h);
	return 1;
}

#define cnt_op_handle_get()                                                   \
	do {                                                                      \
		name = sname->s;                                                      \
		grp = cnt_script_grp; /* default group */                             \
		if((p = strchr(name, '.')) != 0) {                                    \
			/* found group */                                                 \
			grp = name;                                                       \
			name = p + 1;                                                     \
			*p = 0;                                                           \
		}                                                                     \
		if(counter_lookup(&h, grp, name) < 0) {                               \
			ERR("counter %s.%s does not exist (forgot to define it?)\n", grp, \
					name);                                                    \
			return -1;                                                        \
		}                                                                     \
	} while(0)

static int ki_cnt_inc(sip_msg_t *msg, str *sname)
{
	char *name;
	char *grp;
	char *p;
	counter_handle_t h;

	cnt_op_handle_get();

	counter_inc(h);
	return 1;
}

/**
 *
 */
static int cnt_add_f(struct sip_msg *msg, char *handle, char *val)
{
	counter_handle_t h;
	int v;

	h.id = (long)(void *)handle;
	if(unlikely(get_int_fparam(&v, msg, (fparam_t *)val) < 0)) {
		ERR("non integer parameter\n");
		return -1;
	}
	counter_add(h, v);
	return 1;
}

static int ki_cnt_add(sip_msg_t *msg, str *sname, int v)
{
	char *name;
	char *grp;
	char *p;
	counter_handle_t h;

	cnt_op_handle_get();

	counter_add(h, v);
	return 1;
}

/**
 *
 */
static int cnt_reset_f(struct sip_msg *msg, char *handle, char *bar)
{
	counter_handle_t h;

	h.id = (long)(void *)handle;
	counter_reset(h);
	return 1;
}


static int ki_cnt_reset(sip_msg_t *msg, str *sname)
{
	char *name;
	char *grp;
	char *p;
	counter_handle_t h;

	cnt_op_handle_get();

	counter_reset(h);
	return 1;
}


static void cnt_grp_get_all(rpc_t *rpc, void *c, char *group);


static void cnt_get_rpc(rpc_t *rpc, void *c)
{
	char *group;
	char *name;
	counter_val_t v;
	counter_handle_t h;

	if(rpc->scan(c, "s", &group) < 1)
		return;
	if(rpc->scan(c, "*s", &name) < 1) {
		cnt_grp_get_all(rpc, c, group);
		return;
	}
	/* group & name read */
	if(counter_lookup(&h, group, name) < 0) {
		rpc->fault(c, 400, "non-existent counter %s.%s\n", group, name);
		return;
	}
	v = counter_get_val(h);
	rpc->add(c, "d", (int)v);
	return;
}


static void cnt_get_raw_rpc(rpc_t *rpc, void *c)
{
	char *group;
	char *name;
	counter_val_t v;
	counter_handle_t h;

	if(rpc->scan(c, "ss", &group, &name) < 2) {
		/* rpc->fault(c, 400, "group and counter name required"); */
		return;
	}
	if(counter_lookup(&h, group, name) < 0) {
		rpc->fault(c, 400, "non-existent counter %s.%s\n", group, name);
		return;
	}
	v = counter_get_raw_val(h);
	rpc->add(c, "d", (int)v);
	return;
}


static void cnt_reset_rpc(rpc_t *rpc, void *c)
{
	char *group;
	char *name;
	counter_handle_t h;

	if(rpc->scan(c, "ss", &group, &name) < 2) {
		/* rpc->fault(c, 400, "group and counter name required"); */
		return;
	}
	if(counter_lookup(&h, group, name) < 0) {
		rpc->fault(c, 400, "non-existent counter %s.%s\n", group, name);
		return;
	}
	counter_reset(h);
	return;
}


struct rpc_list_params
{
	rpc_t *rpc;
	void *ctx;
};


/* helper callback for iterating groups or names */
static void rpc_print_name(void *param, str *n)
{
	struct rpc_list_params *p;
	rpc_t *rpc;
	void *ctx;

	p = param;
	rpc = p->rpc;
	ctx = p->ctx;
	rpc->add(ctx, "S", n);
}


/* helper callback for iterating on variable names & values*/
static void rpc_print_name_val(void *param, str *g, str *n, counter_handle_t h)
{
	struct rpc_list_params *p;
	rpc_t *rpc;
	void *s;

	p = param;
	rpc = p->rpc;
	s = p->ctx;
	rpc->struct_add(s, "d", n->s, (int)counter_get_val(h));
}


static void cnt_grps_list_rpc(rpc_t *rpc, void *c)
{
	struct rpc_list_params packed_params;

	packed_params.rpc = rpc;
	packed_params.ctx = c;
	counter_iterate_grp_names(rpc_print_name, &packed_params);
}


static void cnt_var_list_rpc(rpc_t *rpc, void *c)
{
	char *group;
	struct rpc_list_params packed_params;

	if(rpc->scan(c, "s", &group) < 1) {
		/* rpc->fault(c, 400, "group name required"); */
		return;
	}
	packed_params.rpc = rpc;
	packed_params.ctx = c;
	counter_iterate_grp_var_names(group, rpc_print_name, &packed_params);
}


static void cnt_grp_get_all(rpc_t *rpc, void *c, char *group)
{
	void *s;
	struct rpc_list_params packed_params;

	if(rpc->add(c, "{", &s) < 0)
		return;
	packed_params.rpc = rpc;
	packed_params.ctx = s;
	counter_iterate_grp_vars(group, rpc_print_name_val, &packed_params);
}


static void cnt_grp_get_all_rpc(rpc_t *rpc, void *c)
{
	char *group;

	if(rpc->scan(c, "s", &group) < 1) {
		/* rpc->fault(c, 400, "group name required"); */
		return;
	}
	cnt_grp_get_all(rpc, c, group);
}


static void cnt_help_rpc(rpc_t *rpc, void *ctx)
{
	char *group;
	char *name;
	char *desc;
	counter_handle_t h;

	if(rpc->scan(ctx, "ss", &group, &name) < 2) {
		/* rpc->fault(c, 400, "group and counter name required"); */
		return;
	}
	if(counter_lookup(&h, group, name) < 0) {
		rpc->fault(ctx, 400, "non-existent counter %s.%s\n", group, name);
		return;
	}
	desc = counter_get_doc(h);
	if(desc)
		rpc->add(ctx, "s", desc);
	else
		rpc->fault(ctx, 400, "no description for counter %s.%s\n", group, name);
	return;
}

/**
 *
 */
/* clang-format off */
static sr_kemi_t sr_kemi_counters_exports[] = {
	{ str_init("counters"), str_init("inc"),
		SR_KEMIP_INT, ki_cnt_inc,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("counters"), str_init("add"),
		SR_KEMIP_INT, ki_cnt_add,
		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
	},
	{ str_init("counters"), str_init("reset"),
		SR_KEMIP_INT, ki_cnt_reset,
		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
			SR_KEMIP_NONE, SR_KEMIP_NONE, 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_counters_exports);
	return 0;
}

/* vi: set ts=4 sw=4 tw=79:ai:cindent: */
