/*
** SellaNMS
** $Id: rate.cxx,v 1.4 2005/11/14 04:56:53 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#include "rate.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

Rate::Rate():debug(false), interval(RATE_INTERVAL_DEF), clean(RATE_CLEAN_DEF), increments(0) {
	pthread_mutex_init(&hashMutex, NULL);

	setInterval(interval);
}

Rate::Rate(unsigned int interval) {
	pthread_mutex_init(&hashMutex, NULL);

	setInterval(interval);
}

Rate::Rate(Rate const &src) {
	*this = src;
}

Rate::~Rate() {
	clear();

	pthread_mutex_destroy(&hashMutex);
}

Rate const& Rate::operator=(Rate const &src) {
	/* Need to do a deep copy of the hash. */
	abort();

	return *this;
}

void Rate::setDebug(bool flag) {
	debug = flag;
}

bool Rate::getDebug() {
	return debug;
}

void Rate::setInterval(unsigned int interval) {
	/* Erase current set of data. */
	clear();

	/* Set new interval */
	this->interval = (interval < 1) ? 1 : interval; /* Must be greater than 0. */

	/* Set sample rate */
	this->samples = this->interval + 1; /* So we don't count the current second. */

	if (debug) {
		LMSG("Data cleared and rate interval set to %lu.", this->interval);
	}
}

unsigned int Rate::getInterval() {
	return interval;
}

void Rate::setClean(unsigned int clean) {
	/* Set new clean */
	this->clean = (clean < 1) ? (unsigned int) -1 : clean; /* 0 disables */

	if (debug) {
		LMSG("Clean count set to %lu.", this->clean);
	}
}

unsigned int Rate::getClean() {
	return clean;
}

unsigned int Rate::getRate(string &source, unsigned int sub_interval) {
	unsigned int span;
	unsigned long sum = 0;
	rate_entry_t *entry;
	time_t now = time(NULL);

	pthread_mutex_lock(&hashMutex);

	if (data.find(source) == data.end()) {
		pthread_mutex_unlock(&hashMutex);

		return 0;
	}

	if (sub_interval < interval && sub_interval > 0) {
		span = sub_interval;
	} else {
		span = interval;
	}

	/* Grab correct entry. */
	entry = data[source];

	/* Walk though all samples for this entry. */
	for (unsigned int i = 0; i < samples; i++) {
		/* Use samples from the last interval period. */
		if (entry->last_update[i] + span >= now && entry->last_update[i] != now) {
			sum += entry->samples[i];
		}
	}

	if (debug) {
		LMSG("Source: %s Interval: %lu Sum: %lu Rate: %lu", source.c_str(), span, sum, (unsigned int) (sum / span));
	}

	pthread_mutex_unlock(&hashMutex);

	return (unsigned int) (sum / span);
}

void Rate::increment(string &source) {
	rate_entry_t *entry = NULL;
	time_t now = time(NULL);

	pthread_mutex_lock(&hashMutex);

	if (data.find(source) == data.end()) {
		/* Allocate entry structure. */
		entry = (rate_entry_t*) malloc(sizeof(rate_entry_t));
		entry->last_update = (time_t*) malloc(samples * sizeof(time_t));
		entry->samples = (unsigned int*) malloc(samples * sizeof(unsigned int));

		/* Initialize entry structure. */
		memset(entry->last_update, 0, samples * sizeof(time_t));
		memset(entry->samples, 0, samples * sizeof(unsigned int));

		/* Insert entry into data hash. */
		data[source] = entry;
	} else {
		entry = data[source];
	}

	assert(entry != NULL);

	if (entry->last_update[now % samples] == now) {
		/* Increment entry. */
		entry->samples[now % samples]++;
	} else {
		/* Reset entry to 1 and set last_update to now. */
		entry->last_update[now % samples] = now;
		entry->samples[now % samples] = 1;
	}

	pthread_mutex_unlock(&hashMutex);

	/* Increment clean counter. */
	if (increments++ >= clean) {
		clearStale();
	}
}

void Rate::clear() {
	rate_entry_t *entry;
	rate_data_t::iterator it;

	pthread_mutex_lock(&hashMutex);

	if (debug) {
		LMSG("Removing %lu rate entries", data.size());
	}

	it = data.begin();
	while (it != data.end()) {
		entry = it->second;

		SAFE_FREE(entry->last_update);
		SAFE_FREE(entry->samples);
		SAFE_FREE(entry);

		data.erase(it);
		it = data.begin();
	}

	increments = 0;

	pthread_mutex_unlock(&hashMutex);
}

void Rate::clear(string &source) {
	rate_entry_t *entry;
	rate_data_t::iterator it;

	pthread_mutex_lock(&hashMutex);

	if ((it = data.find(source)) != data.end()) {
		entry = it->second;

		if (debug) {
			LMSG("Removing rate entry for source %s", source.c_str());
		}

		SAFE_FREE(entry->last_update);
		SAFE_FREE(entry->samples);
		SAFE_FREE(entry);

		data.erase(it);
	} else if (debug) {
		LMSG("No rate entry for source %s found", source.c_str());
	}

	pthread_mutex_unlock(&hashMutex);

}

void Rate::clearStale() {
	bool stale;
	rate_entry_t *entry;
	rate_data_t::iterator it, last_it;
	time_t now = time(NULL);

	/* Restart increments. */
	increments = 0;

	pthread_mutex_lock(&hashMutex);

	it = data.begin();
	while (it != data.end()) {
		entry = it->second;

		/* Walk though current entry to see if its stale. */
		stale = true;
		for (unsigned int i = 0; i < samples && stale; i++) {
			/* Use samples from the last interval period. */
			if (entry->last_update[i] + interval > now) {
				stale = false;

				break;
			}
		}

		if (stale == false) {
			++it;
		} else {
			last_it = it;
			++it;

			if (debug) {
				LMSG("Removing stale entry for source: %s", last_it->first.c_str());
			}

			SAFE_FREE(entry->last_update);
			SAFE_FREE(entry->samples);
			SAFE_FREE(entry);

			data.erase(last_it);
		}
	}

	pthread_mutex_unlock(&hashMutex);
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
