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

#include "lib.h"
#include "ioloop.h"
#include "time-util.h"
#include "log-throttle.h"

struct log_throttle {
	struct log_throttle_settings set;
	log_throttle_callback_t *callback;
	void *context;

	struct timeval last_time;
	unsigned int last_count;

	struct timeout *to_throttled;
};

#undef log_throttle_init
struct log_throttle *
log_throttle_init(const struct log_throttle_settings *set,
		  log_throttle_callback_t *callback, void *context)
{
	struct log_throttle *throttle;

	i_assert(set->throttle_at_max_per_interval > 0);
	i_assert(set->unthrottle_at_max_per_interval > 0);

	throttle = i_new(struct log_throttle, 1);
	throttle->set = *set;
	if (throttle->set.interval_msecs == 0)
		throttle->set.interval_msecs = 1000;
	throttle->callback = callback;
	throttle->context = context;
	throttle->last_time = ioloop_timeval;
	return throttle;
}

void log_throttle_deinit(struct log_throttle **_throttle)
{
	struct log_throttle *throttle = *_throttle;

	*_throttle = NULL;
	timeout_remove(&throttle->to_throttled);
	i_free(throttle);
}

static void log_throttle_callback(struct log_throttle *throttle)
{
	if (throttle->last_count > 0)
		throttle->callback(throttle->last_count, throttle->context);
	if (throttle->last_count < throttle->set.unthrottle_at_max_per_interval)
		timeout_remove(&throttle->to_throttled);
	throttle->last_count = 0;
}

bool log_throttle_accept(struct log_throttle *throttle)
{
	if (throttle->to_throttled != NULL) {
		/* unthrottling and last_count resets are done only by
		   the callback */
		throttle->last_count++;
		return FALSE;
	} else if (timeval_diff_msecs(&ioloop_timeval, &throttle->last_time) >=
				(int)throttle->set.interval_msecs) {
		throttle->last_time = ioloop_timeval;
		throttle->last_count = 1;
		return TRUE;
	} else if (++throttle->last_count <= throttle->set.throttle_at_max_per_interval) {
		return TRUE;
	} else {
		throttle->last_count = 1;
		throttle->to_throttled =
			timeout_add(throttle->set.interval_msecs,
				    log_throttle_callback, throttle);
		return FALSE;
	}
}
