/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "test-lib.h"
#include "stats-dist.h"
#include "sort.h"
#include "math.h"

#define DBL_EQ(a, b) (fabs((a)-(b)) < 0.001)

static void
test_stats_dist_verify(struct stats_dist *t, const int64_t *input,
		       unsigned int input_size)
{
	uint64_t min = INT_MAX, max = 0, sum = 0;
	uint64_t *copy;
	unsigned int i;

	i_assert(input_size > 0);

	copy = i_new(uint64_t, input_size);
	for (i = 0; i < input_size; i++) {
		uint64_t value = input[i];

		if (min > value)
			min = value;
		if (max < value)
			max = value;
		sum += value;
		copy[i] = value;
	}
	i_qsort(copy, input_size, sizeof(*copy), uint64_cmp);

	test_assert_idx(stats_dist_get_count(t) == input_size, input_size);
	test_assert_idx(stats_dist_get_sum(t) == sum, input_size);
	test_assert_idx(stats_dist_get_min(t)  == min, input_size);
	test_assert_idx(stats_dist_get_max(t) == max, input_size);
	test_assert_idx(DBL_EQ(stats_dist_get_avg(t), (double)sum/input_size),
			input_size);

	/* these aren't always fully accurate: */
	test_assert_idx(stats_dist_get_median(t) >= copy[(input_size-1)/2] &&
			stats_dist_get_median(t) <= copy[input_size/2],
			input_size);
	/* when we have 20 elements, [19] is the max, not the 95th %ile, so subtract 1 */
	test_assert_idx(stats_dist_get_95th(t) == copy[input_size*95/100 - ((input_size%20) == 0 ? 1 : 0)],
			input_size);

	i_free(copy);
}

static void test_stats_dist_get_variance(void)
{
	static const struct {
		int64_t in[10];
		double out;
	} tests[] = {
		{ .in = { 2, 2, 2, -1 }, .out = 0.0 },
		{ .in = { -1 }, .out = 0.0 },
		{ .in = { 1, 2, 3, 4, 5, 6, 7, 8, -1 }, .out = 5.25 },
	};

	struct stats_dist *t;
	unsigned int i, j;

	test_begin("stats_dists_get_variance");

	for (i = 0; i < N_ELEMENTS(tests); i++) {
		t = stats_dist_init();
		for (j = 0; tests[i].in[j] >= 0; j++) {
			stats_dist_add(t, tests[i].in[j]);
			test_stats_dist_verify(t, tests[i].in, j+1);
		}
		test_assert_idx(DBL_EQ(stats_dist_get_variance(t),
				       tests[i].out), i);

		stats_dist_deinit(&t);
	}

	test_end();
}

void test_stats_dist(void)
{
	static int64_t test_input1[] = {
		20, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8,
		9, 10, 11, 12, 13, 14, 15, 16, 17, -1
	};
	static int64_t test_input2[] = {
		20, 21, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8,
		9, 10, 11, 12, 13, 14, 15, 16, 17, -1
	};
	static int64_t test_input3[] = {
		20, 21, 19, 18, 1, 2, 3, 4, 5, 6, 7, 8,
		9, 10, 11, 12, 13, 14, 15, 16, 17, 22, -1
	};
	static int64_t *test_inputs[] = {
		test_input1, test_input2, test_input3
	};
	struct stats_dist *t;
	unsigned int i, j;

	for (i = 0; i < N_ELEMENTS(test_inputs); i++) {
		test_begin(t_strdup_printf("stats_dists %u", i));
		t = stats_dist_init();
		for (j = 0; test_inputs[i][j] >= 0; j++) {
			stats_dist_add(t, test_inputs[i][j]);
			test_stats_dist_verify(t, test_inputs[i], j+1);
		}
		stats_dist_reset(t);
		test_assert(stats_dist_get_count(t) == 0);
		test_assert(stats_dist_get_max(t) == 0);
		stats_dist_deinit(&t);
		test_end();
	}

	test_begin("stats_dists large");
	t = stats_dist_init();
	for (i = 0; i < 10000; i++)
		stats_dist_add(t, i);
	test_assert(stats_dist_get_count(t) == i);
	test_assert(stats_dist_get_sum(t) == (i-1)*i/2);
	test_assert(stats_dist_get_min(t) == 0);
	test_assert(stats_dist_get_max(t) == i-1);
	test_assert(DBL_EQ(stats_dist_get_avg(t), 4999.500000));
	/* just test that these work: */
	test_assert(stats_dist_get_median(t) > 0 && stats_dist_get_median(t) < i-1);
	test_assert(stats_dist_get_95th(t) > 0 && stats_dist_get_95th(t) < i-1);
	stats_dist_deinit(&t);
	test_end();

	test_stats_dist_get_variance();
}
