/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
 * All Rights Reserved
 *
 * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <inttypes.h>

#include <osmocom/core/utils.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/application.h>
#include <osmocom/core/select.h>
#include <osmocom/core/tdef.h>
#include <osmocom/core/time_cc.h>

enum my_ctrs {
	CTR_CEIL,
	CTR_ROUND,
	CTR_FLOOR,
};

const struct rate_ctr_desc my_ctr_desc[] = {
	[CTR_CEIL] =	{"ceil", "testing round_threshold_usec = 1"},
	[CTR_ROUND] =	{"round", "testing round_threshold_usec = 0 = gran_usec/2"},
	[CTR_FLOOR] =	{"floor", "testing round_threshold_usec = gran_usec"},
};

const struct rate_ctr_group_desc my_ctrg_desc = {
	"time_cc_test",
	"Counters for osmo_time_cc test",
	0,
	ARRAY_SIZE(my_ctr_desc),
	my_ctr_desc,
};

struct rate_ctr_group *my_ctrg;


enum my_obj_timers {
	T_GRAN = -23,
	T_ROUND_THRESH = -24,
	T_FORGET_SUM = -25,
};

struct osmo_tdef g_my_obj_tdefs[] = {
	{ .T = T_GRAN, .default_val = 0, .unit = OSMO_TDEF_MS, .desc = "flag_cc granularity, or zero for 1 second" },
	{ .T = T_ROUND_THRESH, .default_val = 0, .unit = OSMO_TDEF_MS,
		.desc = "flag_cc rounding threshold, or zero for half a granularity" },
	{ .T = T_FORGET_SUM, .default_val = 0, .unit = OSMO_TDEF_MS,
		.desc = "flag_cc inactivity forget period, or zero to not forget any timings" },
	{}
};


struct my_obj {
	struct osmo_time_cc flag_cc_ceil;
	struct osmo_time_cc flag_cc_round;
	struct osmo_time_cc flag_cc_floor;
};

void my_obj_init(struct my_obj *my_obj)
{
	osmo_time_cc_init(&my_obj->flag_cc_ceil);
	my_obj->flag_cc_ceil.cfg = (struct osmo_time_cc_cfg){
		.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_CEIL),
		.round_threshold_usec = 1,
		.T_gran = T_GRAN,
		.T_forget_sum = T_FORGET_SUM,
		.T_defs = g_my_obj_tdefs,
	};

	osmo_time_cc_init(&my_obj->flag_cc_round);
	my_obj->flag_cc_round.cfg = (struct osmo_time_cc_cfg){
		.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_ROUND),
		.T_gran = T_GRAN,
		.T_round_threshold = T_ROUND_THRESH,
		.T_forget_sum = T_FORGET_SUM,
		.T_defs = g_my_obj_tdefs,
	};

	osmo_time_cc_init(&my_obj->flag_cc_floor);
	my_obj->flag_cc_floor.cfg = (struct osmo_time_cc_cfg){
		.rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_FLOOR),
		.round_threshold_usec = UINT64_MAX, /* always >= gran_usec */
		.T_gran = T_GRAN,
		.T_forget_sum = T_FORGET_SUM,
		.T_defs = g_my_obj_tdefs,
	};
}

void my_obj_event(struct my_obj *my_obj, bool flag)
{
	osmo_time_cc_set_flag(&my_obj->flag_cc_ceil, flag);
	osmo_time_cc_set_flag(&my_obj->flag_cc_round, flag);
	osmo_time_cc_set_flag(&my_obj->flag_cc_floor, flag);
}

void my_obj_destruct(struct my_obj *my_obj)
{
	osmo_time_cc_cleanup(&my_obj->flag_cc_ceil);
	osmo_time_cc_cleanup(&my_obj->flag_cc_round);
	osmo_time_cc_cleanup(&my_obj->flag_cc_floor);
}

static const struct log_info_cat log_categories[] = {
};

static const struct log_info log_info = {
	.cat = log_categories,
	.num_cat = ARRAY_SIZE(log_categories),
};

int main()
{
	void *ctx = talloc_named_const(NULL, 0, "time_cc_test");
	struct timespec *now;
	struct my_obj my_obj = {0};

	osmo_init_logging2(ctx, &log_info);

	/* enable override for CLOCK_MONOTONIC */
	osmo_clock_override_enable(CLOCK_MONOTONIC, true);
	now = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
	now->tv_sec = 23000;
	now->tv_nsec = 0;

	/* enable override for osmo_gettimeofday(), for osmo_timer_schedule() */
	osmo_gettimeofday_override = true;
	osmo_gettimeofday_override_time = (struct timeval){23000, 0};

	my_ctrg = rate_ctr_group_alloc(ctx, &my_ctrg_desc, 0);

#define CHECK_RATE_CTRS(exp_ceil, exp_round, exp_floor) do { \
		printf("%d CHECK_RATE_CTRS(" #exp_ceil ", " #exp_round ", " #exp_floor ")", \
		       my_obj.flag_cc_round.flag_state); \
		while (osmo_select_main_ctx(1) > 0); \
		if (exp_ceil != my_obj.flag_cc_ceil.cfg.rate_ctr->current \
		    || exp_round != my_obj.flag_cc_round.cfg.rate_ctr->current \
		    || exp_floor != my_obj.flag_cc_floor.cfg.rate_ctr->current) \
			printf("\n     ERROR on line %d: ctr_ceil=%"PRIu64" ctr_round=%"PRIu64" ctr_floor=%"PRIu64"\n", \
			       __LINE__, \
			       my_obj.flag_cc_ceil.cfg.rate_ctr->current, \
			       my_obj.flag_cc_round.cfg.rate_ctr->current, \
			       my_obj.flag_cc_floor.cfg.rate_ctr->current); \
		else \
			printf(" ok\n"); \
	} while (0)

#define ADD_MILLISECS_NO_SELECT(ms) do { \
		osmo_clock_override_add(CLOCK_MONOTONIC, ms / 1000, (uint64_t)(ms % 1000) * 1000000); \
		osmo_gettimeofday_override_add(ms / 1000, (uint64_t)(ms % 1000) * 1000); \
		printf("%d ADD_MILLISECS(" #ms ") --> %ld.%03ld", my_obj.flag_cc_round.flag_state, \
		       now->tv_sec, now->tv_nsec/1000000); \
		printf("\n"); \
	} while (0)

#define ADD_MILLISECS(ms) do { \
		ADD_MILLISECS_NO_SELECT(ms); \
		while (osmo_select_main_ctx(1) > 0); \
	} while (0)

#define FLAG(VAL) do { \
		printf("  flag: %s -> %s\n", my_obj.flag_cc_round.flag_state ? "TRUE" : "FALSE", VAL ? "TRUE" : "FALSE"); \
		my_obj_event(&my_obj, VAL); \
	} while (0)

	/*
	 *                        sum ^
	 *                            |                                          ________
	 *                            |                                         /
	 *                            |                                        /
	 *                            |                                       /
	 *                   3*gran --+--------------------------------------+
	 *                            |                                     /:
	 *                            |                                    / :
	 *                            | - - - - - - - - - - - - - - - - - /  :
	 *                            |                                  /.  :
	 *                            |                                 / .  :
	 *                   2*gran --+--------------------------------+  .  :
	 *                            |                               /:  .  :
	 *                            |                              / :  .  :
	 *                            | - - - - - - - - - -_________/  :  .  :
	 *                            |                   /         .  :  .  :
	 *                            |                  /          .  :  .  :
	 *                   1*gran --+-----------------+           .  :  .  :
	 *                            |                /:           .  :  .  :
	 *                            |               / :           .  :  .  :
	 *                            | - - - - - - -/  :           .  :  .  :
	 *                            |             /.  :           .  :  .  :
	 *                            | ....-------' .  :           .  :  .  :
	 *                         0  +----------------------------------------------------------> elapsed time
	 *                                           .  :           .  :  .  :
	 *                               _   _      _______         ____________
	 *                   flag:    __| |_| |____| .  :  |_______|.  :  .  :  |__________
	 *                            f t f t f    t .  :  f       t.  :  .  :  f
	 *   round_threshold_usec       :            .  :           .  :  .  :
	 *                 = 1 usec:  0  1           .  :2          .  :3 .  :4  = "ceil()"
	 *       = 0 == gran_usec/2:  0              1  :           2  :  3  :   = "round()"
	 *              = gran_usec:  0                 1              2     3   = "floor()"
	 */

	printf("\n----------- cumulating time, without forget_sum\n\n");

	my_obj_init(&my_obj);
	CHECK_RATE_CTRS(0, 0, 0);

	ADD_MILLISECS(100);
	CHECK_RATE_CTRS(0, 0, 0);

	FLAG(true);
	/* flag has just turned true the first time */
	CHECK_RATE_CTRS(0, 0, 0);
	ADD_MILLISECS(1);
	/* flag has been true for 0.001s */
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(99);
	/* flag has been true for 0.1s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS(100);

	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(100);
	/* flag has been true for 0.2s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS(300);

	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(299);
	/* flag has been true for 0.499s */
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(1);
	/* flag has been true for 0.5s */
	CHECK_RATE_CTRS(1, 1, 0);
	ADD_MILLISECS(499);
	/* flag has been true for 0.999s */
	CHECK_RATE_CTRS(1, 1, 0);
	ADD_MILLISECS(1);
	/* flag has been true for 1.0s */
	CHECK_RATE_CTRS(1, 1, 1);
	ADD_MILLISECS(1);
	/* flag has been true for 1.001s */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(299);
	/* flag has been true for 1.3s */
	CHECK_RATE_CTRS(2, 1, 1);
	FLAG(false);
	CHECK_RATE_CTRS(2, 1, 1);

	ADD_MILLISECS(400);

	CHECK_RATE_CTRS(2, 1, 1);
	FLAG(true);
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(199);
	/* flag has been true for 1.499s */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(2);
	/* flag has been true for 1.501s */
	CHECK_RATE_CTRS(2, 2, 1);
	ADD_MILLISECS(498);
	/* flag has been true for 1.999s */
	CHECK_RATE_CTRS(2, 2, 1);
	ADD_MILLISECS(2);
	/* flag has been true for 2.001s */
	CHECK_RATE_CTRS(3, 2, 2);
	ADD_MILLISECS(500);
	/* flag has been true for 2.501s */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS(498);
	/* flag has been true for 2.999s */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS(3);
	/* flag has been true for 3.003s */
	CHECK_RATE_CTRS(4, 3, 3);
	ADD_MILLISECS(200);
	/* flag has been true for 3.203s */
	CHECK_RATE_CTRS(4, 3, 3);
	FLAG(false);
	CHECK_RATE_CTRS(4, 3, 3);

	ADD_MILLISECS(4321);
	CHECK_RATE_CTRS(4, 3, 3);

	FLAG(true);
	CHECK_RATE_CTRS(4, 3, 3);
	ADD_MILLISECS(5678);
	CHECK_RATE_CTRS(9, 9, 8);
	FLAG(false);
	CHECK_RATE_CTRS(9, 9, 8);

	my_obj_destruct(&my_obj);
	rate_ctr_group_reset(my_ctrg);

	printf("\n----------- test forget_sum_usec\n\n");
	osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);

	now->tv_sec = 23000;
	now->tv_nsec = 0;
	osmo_gettimeofday_override_time = (struct timeval){23000, 0};

	my_obj_init(&my_obj);

	CHECK_RATE_CTRS(0, 0, 0);

	FLAG(true);
	/* flag has just turned true the first time */
	CHECK_RATE_CTRS(0, 0, 0);
	ADD_MILLISECS(100);
	/* flag has been true for 0.1s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS(1000);
	/* 1 s of being false, forget_sum_usec has not yet occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS(8999);
	/* 9.999 s of being false, forget_sum_usec has not yet occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS(1);
	/* 10 s of being false, forget_sum_usec has occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(1);
	/* Since previous sums were forgotton, ceil() triggers again */
	CHECK_RATE_CTRS(2, 0, 0);
	/* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
	 * that this does not occur, since now full 500 ms are required */
	ADD_MILLISECS(399);
	CHECK_RATE_CTRS(2, 0, 0);
	/* Adding another 100 ms will trigger round() */
	ADD_MILLISECS(99);
	CHECK_RATE_CTRS(2, 0, 0);
	ADD_MILLISECS(1);
	CHECK_RATE_CTRS(2, 1, 0);
	/* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
	 * that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
	ADD_MILLISECS(400);
	CHECK_RATE_CTRS(2, 1, 0);
	/* Adding another 100 ms will trigger floor() */
	ADD_MILLISECS(99);
	CHECK_RATE_CTRS(2, 1, 0);
	ADD_MILLISECS(1);
	CHECK_RATE_CTRS(2, 1, 1);

	/* Test that durations of false below forget_sum_usec never trigger a forget */
	ADD_MILLISECS(300);
	CHECK_RATE_CTRS(3, 1, 1);
	/* internal counter is now at 0.3s above the last reported rate counter */
	FLAG(false);
	ADD_MILLISECS(9999);
	FLAG(true);
	ADD_MILLISECS(25);
	FLAG(false);
	ADD_MILLISECS(9999);
	FLAG(true);
	ADD_MILLISECS(25);
	FLAG(false);
	ADD_MILLISECS(9999);
	FLAG(true);
	ADD_MILLISECS(25);
	FLAG(false);
	ADD_MILLISECS(9999);
	FLAG(true);
	ADD_MILLISECS(25);
	/* internal counter is now at 0.4s above the last reported rate counter */
	CHECK_RATE_CTRS(3, 1, 1);
	ADD_MILLISECS(100);
	CHECK_RATE_CTRS(3, 2, 1);
	ADD_MILLISECS(500);
	CHECK_RATE_CTRS(3, 2, 2);

	/* Test that repeated osmo_time_cc_set_flag(false) does not cancel a forget_sum_usec */
	ADD_MILLISECS(300);
	/* internal counter is now at 0.3s above the last reported rate counter */
	CHECK_RATE_CTRS(4, 2, 2);
	FLAG(false);
	ADD_MILLISECS(5000);
	/* Repeat 'false', must not affect forget_sum_usec */
	FLAG(false);
	ADD_MILLISECS(5000);
	CHECK_RATE_CTRS(4, 2, 2);
	/* 10 s have passed, forget_sum_usec has occurred.
	 * Hence ceil() will trigger again right away: */
	FLAG(true);
	ADD_MILLISECS(1);
	CHECK_RATE_CTRS(5, 2, 2);
	/* Adding 200 ms to the initial 300 ms would have triggered round(), but no more after forget_sum_usec */
	ADD_MILLISECS(199);
	CHECK_RATE_CTRS(5, 2, 2);
	/* Adding another 300 ms will trigger round() */
	ADD_MILLISECS(299);
	CHECK_RATE_CTRS(5, 2, 2);
	ADD_MILLISECS(1);
	CHECK_RATE_CTRS(5, 3, 2);
	/* Adding 700 ms to the initial 300 ms would have triggered ceil(), but no more after forget_sum_usec */
	ADD_MILLISECS(200);
	CHECK_RATE_CTRS(5, 3, 2);
	/* Adding another 300 ms will trigger ceil() */
	ADD_MILLISECS(299);
	CHECK_RATE_CTRS(5, 3, 2);
	ADD_MILLISECS(1);
	CHECK_RATE_CTRS(5, 3, 3);

	my_obj_destruct(&my_obj);
	rate_ctr_group_reset(my_ctrg);


	/* Verify correctness when select() lags and runs timer callbacks too late */
	printf("\n----------- cumulating time, without forget_sum, when timer cb are invoked late\n\n");
	osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 0, OSMO_TDEF_S);
	now->tv_sec = 23000;
	now->tv_nsec = 0;
	osmo_gettimeofday_override_time = (struct timeval){23000, 0};

	my_obj_init(&my_obj);
	CHECK_RATE_CTRS(0, 0, 0);

	ADD_MILLISECS_NO_SELECT(100);
	CHECK_RATE_CTRS(0, 0, 0);

	FLAG(true);
	/* flag has just turned true the first time */
	CHECK_RATE_CTRS(0, 0, 0);
	ADD_MILLISECS_NO_SELECT(100);
	/* flag has been true for 0.1s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS_NO_SELECT(100);

	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS_NO_SELECT(100);
	/* flag has been true for 0.2s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS_NO_SELECT(300);

	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS_NO_SELECT(799);
	/* flag has been true for 0.999s */
	CHECK_RATE_CTRS(1, 1, 0);
	ADD_MILLISECS_NO_SELECT(1);
	/* flag has been true for 1.0s */
	CHECK_RATE_CTRS(1, 1, 1);
	ADD_MILLISECS_NO_SELECT(300);
	/* flag has been true for 1.3s */
	CHECK_RATE_CTRS(2, 1, 1);
	FLAG(false);
	CHECK_RATE_CTRS(2, 1, 1);

	ADD_MILLISECS_NO_SELECT(400);

	CHECK_RATE_CTRS(2, 1, 1);
	FLAG(true);
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS_NO_SELECT(699);
	/* flag has been true for 1.999s */
	CHECK_RATE_CTRS(2, 2, 1);
	ADD_MILLISECS_NO_SELECT(1);
	/* flag has been true for 2.0s */
	CHECK_RATE_CTRS(2, 2, 2);
	ADD_MILLISECS_NO_SELECT(1);
	/* flag has been true for 2.001s */
	CHECK_RATE_CTRS(3, 2, 2);
	ADD_MILLISECS_NO_SELECT(499);
	/* flag has been true for 2.5s */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS_NO_SELECT(499);
	/* flag has been true for 2.999s */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS_NO_SELECT(1);
	/* flag has been true for 3.0s */
	CHECK_RATE_CTRS(3, 3, 3);
	ADD_MILLISECS_NO_SELECT(200);
	/* flag has been true for 3.2s */
	CHECK_RATE_CTRS(4, 3, 3);
	FLAG(false);
	CHECK_RATE_CTRS(4, 3, 3);

	ADD_MILLISECS_NO_SELECT(4321);
	CHECK_RATE_CTRS(4, 3, 3);

	FLAG(true);
	CHECK_RATE_CTRS(4, 3, 3);
	ADD_MILLISECS_NO_SELECT(5678);
	CHECK_RATE_CTRS(9, 9, 8);
	FLAG(false);
	CHECK_RATE_CTRS(9, 9, 8);

	my_obj_destruct(&my_obj);
	rate_ctr_group_reset(my_ctrg);


	printf("\n----------- test forget_sum, when timer cb are invoked late\n\n");
	osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);

	now->tv_sec = 23000;
	now->tv_nsec = 0;
	osmo_gettimeofday_override_time = (struct timeval){23000, 0};

	my_obj_init(&my_obj);

	CHECK_RATE_CTRS(0, 0, 0);

	FLAG(true);
	/* flag has just turned true the first time */
	CHECK_RATE_CTRS(0, 0, 0);
	ADD_MILLISECS_NO_SELECT(100);
	/* flag has been true for 0.1s */
	CHECK_RATE_CTRS(1, 0, 0);
	FLAG(false);
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS_NO_SELECT(1000);
	/* 1 s of being false, forget_sum_usec has not yet occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS_NO_SELECT(8999);
	/* 9.999 s of being false, forget_sum_usec has not yet occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	ADD_MILLISECS_NO_SELECT(1);
	/* 10 s of being false, forget_sum_usec has occurred */
	CHECK_RATE_CTRS(1, 0, 0);

	FLAG(true);
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS_NO_SELECT(1);
	/* Since previous sums were forgotton, ceil() triggers again */
	CHECK_RATE_CTRS(2, 0, 0);
	/* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
	 * that this does not occur, since now full 500 ms are required */
	ADD_MILLISECS_NO_SELECT(399);
	CHECK_RATE_CTRS(2, 0, 0);
	/* Adding another 100 ms will trigger round() */
	ADD_MILLISECS_NO_SELECT(99);
	CHECK_RATE_CTRS(2, 0, 0);
	ADD_MILLISECS_NO_SELECT(1);
	CHECK_RATE_CTRS(2, 1, 0);
	/* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
	 * that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
	ADD_MILLISECS_NO_SELECT(400);
	CHECK_RATE_CTRS(2, 1, 0);
	/* Adding another 100 ms will trigger floor() */
	ADD_MILLISECS_NO_SELECT(99);
	CHECK_RATE_CTRS(2, 1, 0);
	ADD_MILLISECS_NO_SELECT(1);
	CHECK_RATE_CTRS(2, 1, 1);

	/* Test that durations of false below forget_sum_usec never trigger a forget */
	ADD_MILLISECS_NO_SELECT(300);
	CHECK_RATE_CTRS(3, 1, 1);
	/* internal counter is now at 0.3s above the last reported rate counter */
	FLAG(false);
	ADD_MILLISECS_NO_SELECT(9999);
	FLAG(true);
	ADD_MILLISECS_NO_SELECT(25);
	FLAG(false);
	ADD_MILLISECS_NO_SELECT(9999);
	FLAG(true);
	ADD_MILLISECS_NO_SELECT(25);
	FLAG(false);
	ADD_MILLISECS_NO_SELECT(9999);
	FLAG(true);
	ADD_MILLISECS_NO_SELECT(25);
	FLAG(false);
	ADD_MILLISECS_NO_SELECT(9999);
	FLAG(true);
	ADD_MILLISECS_NO_SELECT(25);
	/* internal counter is now at 0.4s above the last reported rate counter */
	CHECK_RATE_CTRS(3, 1, 1);
	ADD_MILLISECS_NO_SELECT(100);
	CHECK_RATE_CTRS(3, 2, 1);
	ADD_MILLISECS_NO_SELECT(500);
	CHECK_RATE_CTRS(3, 2, 2);

	my_obj_destruct(&my_obj);
	rate_ctr_group_reset(my_ctrg);


#define SET_TDEFS(gran, round_thresh, forget_sum) do { \
		osmo_tdef_set(g_my_obj_tdefs, T_GRAN, gran, OSMO_TDEF_MS); \
		osmo_tdef_set(g_my_obj_tdefs, T_ROUND_THRESH, round_thresh, OSMO_TDEF_MS); \
		osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, forget_sum, OSMO_TDEF_S); \
		printf("T_defs: T_gran=%luusec T_round_threshold=%luusec T_forget_sum=%luusec\n", \
		       osmo_tdef_get(g_my_obj_tdefs, T_GRAN, OSMO_TDEF_US, -1), \
		       osmo_tdef_get(g_my_obj_tdefs, T_ROUND_THRESH, OSMO_TDEF_US, -1), \
		       osmo_tdef_get(g_my_obj_tdefs, T_FORGET_SUM, OSMO_TDEF_US, -1)); \
	} while (0)

	printf("\n----------- test T_defs\n\n");
	now->tv_sec = 23000;
	now->tv_nsec = 0;
	osmo_gettimeofday_override_time = (struct timeval){23000, 0};

	SET_TDEFS(100, 10, 0);

	my_obj_init(&my_obj);
	CHECK_RATE_CTRS(0, 0, 0);

	ADD_MILLISECS(100);
	CHECK_RATE_CTRS(0, 0, 0);

	FLAG(true);
	/* flag has just turned true the first time */
	CHECK_RATE_CTRS(0, 0, 0);
	ADD_MILLISECS(9);
	/* flag has been true for 0.009s */
	CHECK_RATE_CTRS(1, 0, 0);
	ADD_MILLISECS(1);
	/* flag has been true for 0.010s */
	CHECK_RATE_CTRS(1, 1, 0);
	ADD_MILLISECS(90);
	/* flag has been true for 0.1s */
	CHECK_RATE_CTRS(1, 1, 1);

	SET_TDEFS(200, 190, 1);
	/* gran is changed to 200ms, but still continues until the next scheduled event until the change is picked up.
	 * For ceil(), it is 1 ms ahead.
	 * For round(), it is 10 ms ahead.
	 * For floor(), it is at the next full (previous) gran 100 ms ahead.
	 * When T_defs change, all internal sums are reset to zero without reporting.
	 */
	CHECK_RATE_CTRS(1, 1, 1);
	ADD_MILLISECS(1);
	/* 1ms elapsed: ceil() picks up the T_gran change, starts anew. */
	/* elapsed: ceil 0 ms */
	CHECK_RATE_CTRS(1, 1, 1);
	ADD_MILLISECS(1);
	/* elapsed: ceil 1 ms */
	/* ceil() increments because flag has been true for more than 1 us after reset */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(8);
	/* 10 ms elapsed: round() picks up the T_gran change, starts anew */
	/* elapsed: ceil 9 ms, round 0 ms */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(90);
	/* 100 ms elapsed: floor() picks up the T_gran change, starts anew */
	/* elapsed: ceil 99 ms, round 90 ms, floor 0 ms */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(99);
	/* elapsed: ceil 198 ms, round 189 ms, floor 99 ms */
	CHECK_RATE_CTRS(2, 1, 1);
	ADD_MILLISECS(1);
	/* elapsed: ceil 199 ms, round 190 ms, floor 100 ms */
	CHECK_RATE_CTRS(2, 2, 1);
	ADD_MILLISECS(1);
	/* elapsed: ceil 200 ms, round 191 ms, floor 101 ms */
	CHECK_RATE_CTRS(2, 2, 1);
	ADD_MILLISECS(1);
	/* elapsed: ceil 201 ms, round 192 ms, floor 102 ms */
	CHECK_RATE_CTRS(3, 2, 1);
	ADD_MILLISECS(98);
	/* elapsed: ceil 299 ms, round 290 ms, floor 200 ms */
	CHECK_RATE_CTRS(3, 2, 2);
	ADD_MILLISECS(99);
	/* elapsed: ceil 398 ms, round 389 ms, floor 299 ms */
	CHECK_RATE_CTRS(3, 2, 2);
	ADD_MILLISECS(1);
	/* elapsed: ceil 399 ms, round 390 ms, floor 300 ms */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS(1);
	/* elapsed: ceil 400 ms, round 391 ms, floor 301 ms */
	CHECK_RATE_CTRS(3, 3, 2);
	ADD_MILLISECS(1);
	/* elapsed: ceil 401 ms, round 392 ms, floor 302 ms */
	CHECK_RATE_CTRS(4, 3, 2);
	ADD_MILLISECS(98);
	/* elapsed: ceil 499 ms, round 490 ms, floor 400 ms */
	CHECK_RATE_CTRS(4, 3, 3);


	SET_TDEFS(100, 0, 0);
	/* T_defs change, but they only get picked up upon the next event:
	 * For ceil(), it is 102 ms ahead.
	 * For round(), it is 100 ms ahead (thresh is still 190, currently at 90).
	 * For floor(), it is 200 ms ahead.
	 * When T_defs change, all internal sums are reset to zero without reporting.
	 */
	CHECK_RATE_CTRS(4, 3, 3);
	ADD_MILLISECS(100);
	CHECK_RATE_CTRS(4, 3, 3);
	/* round() picks up the new T_defs. Internal sum resets, nothing else happens yet.
	 * round() schedules the next event 50 ms ahead. */
	ADD_MILLISECS(2);
	CHECK_RATE_CTRS(4, 3, 3);
	/* ceil() picks up the change, its next event is 1 ms ahead. */
	ADD_MILLISECS(1);
	/* ceil: 0.001
	 * round: 0.003
	 * floor: still 97 ms until it picks up the change */
	CHECK_RATE_CTRS(5, 3, 3);
	ADD_MILLISECS(46);
	CHECK_RATE_CTRS(5, 3, 3);
	ADD_MILLISECS(1);
	/* round() has first counter trigger after T_defs change. */
	CHECK_RATE_CTRS(5, 4, 3);
	/* ceil: 0.048
	 * round: 0.050
	 * floor: still 50 ms until it picks up the change */
	ADD_MILLISECS(50);
	/* floor() picks up the change. nothing happens yet. */
	/* ceil: 0.098
	 * round: 0.100
	 * floor: 0.0 */
	ADD_MILLISECS(2);
	/* ceil: 0.100
	 * round: 0.102
	 * floor: 0.002 */
	CHECK_RATE_CTRS(5, 4, 3);
	ADD_MILLISECS(1);
	/* ceil: 0.101
	 * round: 0.103
	 * floor: 0.003 */
	CHECK_RATE_CTRS(6, 4, 3);
	ADD_MILLISECS(46);
	/* ceil: 0.147
	 * round: 0.149
	 * floor: 0.049 */
	CHECK_RATE_CTRS(6, 4, 3);
	ADD_MILLISECS(1);
	/* ceil: 0.148
	 * round: 0.150
	 * floor: 0.050 */
	CHECK_RATE_CTRS(6, 5, 3);

	my_obj_destruct(&my_obj);
	rate_ctr_group_reset(my_ctrg);

	return 0;
}
