/* Copyright (c) 2015-2016 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "stats.h"

struct stats_item {
	struct stats_vfuncs v;
	size_t pos;
};

static ARRAY(struct stats_item *) stats_items = ARRAY_INIT;
static unsigned int stats_total_size = 0;
static bool stats_allocated = FALSE;

struct stats_item *stats_register(const struct stats_vfuncs *vfuncs)
{
	struct stats_item *item;

	if (stats_allocated)
		i_panic("stats_register() called after stats_alloc_size() was already called - this will break existing allocations");

	if (!array_is_created(&stats_items))
		i_array_init(&stats_items, 8);

	item = i_new(struct stats_item, 1);
	item->v = *vfuncs;
	item->pos = stats_total_size;
	array_append(&stats_items, &item, 1);

	stats_total_size += vfuncs->alloc_size();
	return item;
}

static bool stats_item_find(struct stats_item *item, unsigned int *idx_r)
{
	struct stats_item *const *itemp;

	array_foreach(&stats_items, itemp) {
		if (*itemp == item) {
			*idx_r = array_foreach_idx(&stats_items, itemp);
			return TRUE;
		}
	}
	return FALSE;
}

static struct stats_item *stats_item_find_by_name(const char *name)
{
	struct stats_item *const *itemp;

	array_foreach(&stats_items, itemp) {
		if (strcmp((*itemp)->v.short_name, name) == 0)
			return *itemp;
	}
	return NULL;
}

void stats_unregister(struct stats_item **_item)
{
	struct stats_item *item = *_item;
	unsigned int idx;

	*_item = NULL;

	if (!stats_item_find(item, &idx))
		i_unreached();
	array_delete(&stats_items, idx, 1);

	i_free(item);
	if (array_count(&stats_items) == 0)
		array_free(&stats_items);
}

struct stats *stats_alloc(pool_t pool)
{
	return p_malloc(pool, stats_alloc_size());
}

size_t stats_alloc_size(void)
{
	stats_allocated = TRUE;
	return stats_total_size;
}

void stats_copy(struct stats *dest, const struct stats *src)
{
	memcpy(dest, src, stats_total_size);
}

unsigned int stats_field_count(void)
{
	struct stats_item *const *itemp;
	unsigned int count = 0;

	array_foreach(&stats_items, itemp)
		count += (*itemp)->v.field_count();
	return count;
}

const char *stats_field_name(unsigned int n)
{
	struct stats_item *const *itemp;
	unsigned int i = 0, count;

	array_foreach(&stats_items, itemp) {
		count = (*itemp)->v.field_count();
		if (i + count > n)
			return (*itemp)->v.field_name(n - i);
		i += count;
	}
	i_unreached();
}

void stats_field_value(string_t *str, const struct stats *stats,
		       unsigned int n)
{
	struct stats_item *const *itemp;
	unsigned int i = 0, count;

	array_foreach(&stats_items, itemp) {
		count = (*itemp)->v.field_count();
		if (i + count > n) {
			const void *item_stats
				= CONST_PTR_OFFSET(stats, (*itemp)->pos);
			(*itemp)->v.field_value(str, item_stats, n - i);
			return;
		}
		i += count;
	}
	i_unreached();
}

bool stats_diff(const struct stats *stats1, const struct stats *stats2,
		struct stats *diff_stats_r, const char **error_r)
{
	struct stats_item *const *itemp;
	bool ret = TRUE;

	array_foreach(&stats_items, itemp) {
		if (!(*itemp)->v.diff(CONST_PTR_OFFSET(stats1, (*itemp)->pos),
				      CONST_PTR_OFFSET(stats2, (*itemp)->pos),
				      PTR_OFFSET(diff_stats_r, (*itemp)->pos),
				      error_r))
			ret = FALSE;
	}
	return ret;
}

void stats_add(struct stats *dest, const struct stats *src)
{
	struct stats_item *const *itemp;

	array_foreach(&stats_items, itemp) {
		(*itemp)->v.add(PTR_OFFSET(dest, (*itemp)->pos),
				CONST_PTR_OFFSET(src, (*itemp)->pos));
	}
}

bool stats_have_changed(const struct stats *prev, const struct stats *cur)
{
	struct stats_item *const *itemp;

	array_foreach(&stats_items, itemp) {
		if ((*itemp)->v.have_changed(CONST_PTR_OFFSET(prev, (*itemp)->pos),
					     CONST_PTR_OFFSET(cur, (*itemp)->pos)))
			return TRUE;
	}
	return FALSE;
}

void stats_export(buffer_t *buf, const struct stats *stats)
{
	struct stats_item *const *itemp;

	array_foreach(&stats_items, itemp) {
		buffer_append(buf, (*itemp)->v.short_name,
			      strlen((*itemp)->v.short_name)+1);
		(*itemp)->v.export(buf, CONST_PTR_OFFSET(stats, (*itemp)->pos));
	}
}

bool stats_import(const unsigned char *data, size_t size,
		  const struct stats *old_stats, struct stats *stats,
		  const char **error_r)
{
	struct stats_item *item;
	const unsigned char *p;
	size_t pos;

	memcpy(stats, old_stats, stats_total_size);
	while (size > 0) {
		const char *next_name = (const void *)data;

		p = memchr(data, '\0', size);
		if (p == NULL) {
			*error_r = "Expected name, but NUL is missing";
			return FALSE;
		}
		item = stats_item_find_by_name(next_name);
		if (item == NULL) {
			*error_r = t_strdup_printf("Unknown stats name: '%s'", next_name);
			return FALSE;
		}
		size -= (p+1) - data;
		data = p+1;
		if (!item->v.import(data, size, &pos,
				    PTR_OFFSET(stats, item->pos), error_r))
			return FALSE;
		i_assert(pos <= size);
		data += pos;
		size -= pos;
	}
	return TRUE;
}

void *stats_fill_ptr(struct stats *stats, struct stats_item *item)
{
	return PTR_OFFSET(stats, item->pos);
}

void stats_reset(struct stats *stats)
{
	memset(stats, 0, stats_total_size);
}
