/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, 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 General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <mailutils/mailutils.h>
#include "libmf.h"
#include "mfdb.h"
#include "filenames.h"

int predict_next_option;//FIXME
double predict_rate;//FIXME

static void rate_print_item(struct mu_dbm_datum const *key,
			    struct mu_dbm_datum const *val);
static int rate_expire_item(struct mu_dbm_datum const *content);

static struct db_format rate_format_struct = {
	"rate",
	DEFAULT_RATE_DATABASE,
	1,
	0,
	DEFAULT_EXPIRE_INTERVAL,
	rate_print_item,
	rate_expire_item
};

struct db_format *rate_format = &rate_format_struct;


struct rate_result {
	time_t timestamp;
	time_t interval;
	size_t count;
};

mf_status
get_rate(char *email, long *ret, unsigned long interval, size_t mincount,
	 size_t threshold)
{
	mu_dbm_file_t db;
	struct mu_dbm_datum key;
	struct mu_dbm_datum contents;
	struct rate_result *rp, rate;
	long result;
	time_t t;
	int res;
	int obsolete;
	
	mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5,
		 ("getting rate info for %s", email));
	db = mf_dbm_open(rate_format->dbname, MU_STREAM_RDWR);
	if (!db)
		return mf_failure;

	memset(&key, 0, sizeof key);
	memset(&contents, 0, sizeof contents);
	key.mu_dptr = email;
	key.mu_dsize = strlen (email) + 1;
	t = time(NULL);
	if ((res = mu_dbm_fetch(db, &key, &contents)) == 0) {
		rp = (struct rate_result *) contents.mu_dptr;
		mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5,
			 ("found time: %lu, interval: %lu, count: %lu, "
			  "rate: %g",
			  rp->timestamp, rp->interval,
			  (unsigned long) rp->count,
			  rp->interval == 0 ? -1 :
			  (double)rp->count/rp->interval));
	} else {
		if (res != MU_ERR_NOENT)
			mu_error(_("cannot fetch `%s' from `%s': %s"),
				 email, rate_format->dbname,
				 mu_dbm_strerror(db));	
		/* Initialize the structure */
		rate.timestamp = t;
		rate.interval = 0;
		rate.count = 0;
		rp = &rate;
	}

	/* Update the structure */
	obsolete = t - rp->timestamp > interval;
	rp->interval += t - rp->timestamp;
	rp->timestamp = t;
	rp->count++;

	if (rp->interval == 0 || rp->count < mincount)
		result = 0;
	else
		result = ((double) rp->count / rp->interval) * interval;
	mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5, 
		 ("rate for %s is %ld/%lu", email, result, interval));

	/* Update the db */
	if (threshold && result > threshold)
		mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5,
			 ("not updating %s rates", email));
	else {
		struct mu_dbm_datum dat;

		if (obsolete
		    || (rate_format->expire_interval
			&& rp->interval > rate_format->expire_interval)) {
			mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5,
				 ("expiring %s rates", email));
			rp->interval = 0;
			rp->count = 1;
		} else 
			mu_debug(rate_format->debug_handle, MU_DEBUG_TRACE5,
			      ("updating %s rates", email));

		dat.mu_dptr = (void*)rp;
		dat.mu_dsize = sizeof(*rp);
		res = mu_dbm_store(db, &key, &dat, 1);
		if (res)
			mu_error (_("cannot insert datum `%s' into `%s': %s"),
				  email, rate_format->dbname,
				  res == MU_ERR_FAILURE ?
				   mu_dbm_strerror(db) :
				   mu_strerror(res));
	}
	
	mu_dbm_datum_free(&contents);

	mu_dbm_destroy(&db);
	*ret = result;
	return mf_success;
}

static void
rate_print_item(struct mu_dbm_datum const *key,
		struct mu_dbm_datum const *val)
{
	const struct rate_result *res = (const struct rate_result *)
		                            val->mu_dptr;
	double rate, exp_rate;
	int size = key->mu_dsize - 1; /* Size includes the trailing nul */

	printf("%*.*s ", size, size, key->mu_dptr);
	if (res->interval) {
		rate = (double)res->count / res->interval;
		exp_rate = (double)(res->count + 1) /
			   (time(NULL) - res->timestamp + res->interval);
	} else 
		rate = exp_rate = -1;

	format_time_str(stdout, res->timestamp);
	
	printf(" %6lu %6lu ",
	       res->interval,
	       (unsigned long) res->count);
	if (rate > 0)
		printf("%8.3g ", rate);
	else
		printf("%8.8s ", "N/A");
	if (exp_rate > 0)
		printf("%8.3g", exp_rate);
	else
		printf("%8.8s", "N/A");
	
	if (predict_next_option) {
		time_t next_time;
		time_t now = time(NULL);
		
		if (res->interval == 0) {
			next_time = 0;
		} else {
			double interval = (double) (res->count + 1) /
				                     predict_rate
				                     - res->interval;
			next_time = res->timestamp + interval;
		}
		if (next_time < now)
			printf("; free to send\n");
		else {
			printf("; in %lu sec. on ",
			       (next_time - now));
			format_time_str(stdout, next_time);
			putchar('\n');
		}
	} else
		printf("\n");
}

static int
rate_expire_item(struct mu_dbm_datum const *content)
{
	struct rate_result const *res =
		(struct rate_result const *) content->mu_dptr;
	return rate_format->expire_interval &&
  	   time(NULL) - res->timestamp + res->interval >
		 rate_format->expire_interval;
}

