#define MODULE_LOG_PREFIX "gc"

#include "globals.h"
#include "oscam-garbage.h"
#include "oscam-lock.h"
#include "oscam-string.h"
#include "oscam-time.h"

#define HASH_BUCKETS 250

struct cs_garbage
{
	time_t time;
	void *data;
#ifdef WITH_DEBUG
	char *file;
	uint32_t line;
#endif
	struct cs_garbage *next;
};

static int32_t counter = 0;
static pthread_mutex_t add_lock;
static struct cs_garbage *garbage_first[HASH_BUCKETS];
static CS_MUTEX_LOCK garbage_lock[HASH_BUCKETS];
static pthread_t garbage_thread;
static int32_t garbage_collector_active;
static int32_t garbage_debug;

#ifdef WITH_DEBUG
void add_garbage_debug(void *data, char *file, uint32_t line)
{
#else
void add_garbage(void *data)
{
#endif
	if(!data)
		{ return; }

	if(!garbage_collector_active || garbage_debug == 1)
	{
		NULLFREE(data);
		return;
	}

	SAFE_MUTEX_LOCK(&add_lock);

	int32_t bucket = counter++;

	if(counter >= HASH_BUCKETS)
	{
		counter = 0;
	}

	SAFE_MUTEX_UNLOCK(&add_lock);

	struct cs_garbage *garbage = (struct cs_garbage*)malloc(sizeof(struct cs_garbage));
	if(garbage == NULL)
	{
		cs_log("*** MEMORY FULL -> FREEING DIRECT MAY LEAD TO INSTABILITY!!! ***");
		NULLFREE(data);
		return;
	}
	garbage->time = time(NULL);
	garbage->data = data;
	garbage->next = NULL;
#ifdef WITH_DEBUG
	garbage->file = file;
	garbage->line = line;
#endif

	cs_writelock(__func__, &garbage_lock[bucket]);

#ifdef WITH_DEBUG
	if(garbage_debug == 2)
	{
		struct cs_garbage *garbagecheck = garbage_first[bucket];
		while(garbagecheck)
		{
			if(garbagecheck->data == data)
			{
				cs_log("Found a try to add garbage twice. Not adding the element to garbage list...");
				cs_log("Current garbage addition: %s, line %d.", file, line);
				cs_log("Original garbage addition: %s, line %d.", garbagecheck->file, garbagecheck->line);
				cs_writeunlock(__func__, &garbage_lock[bucket]);
				NULLFREE(garbage);
				return;
			}
			garbagecheck = garbagecheck->next;
		}
	}
#endif

	garbage->next = garbage_first[bucket];
	garbage_first[bucket] = garbage;

	cs_writeunlock(__func__, &garbage_lock[bucket]);
}

static pthread_cond_t sleep_cond;
static pthread_mutex_t sleep_cond_mutex;

static void garbage_collector(void)
{
	int32_t i,j;
	struct cs_garbage *garbage, *next, *prev, *first;
	set_thread_name(__func__);
	int32_t timeout_time = 2 * cfg.ctimeout / 1000 + 6;

	while(garbage_collector_active)
	{
		time_t deltime = time(NULL) - timeout_time;

		for(i = 0; i < HASH_BUCKETS; ++i)
		{
			j = 0;
			cs_writelock(__func__, &garbage_lock[i]);
			first = garbage_first[i];

			for(garbage = first, prev = NULL; garbage; prev = garbage, garbage = garbage->next, j++)
			{
				if(j == 2)
 				{
					j++;
					cs_writeunlock(__func__, &garbage_lock[i]);
				}

				if(garbage->time < deltime) // all following elements are too new
				{
					if(prev)
					{
						prev->next = NULL;
					}
					else
					{
						garbage_first[i] = NULL;
					}
					break;
				}
			}

			cs_writeunlock(__func__, &garbage_lock[i]);

			// list has been taken out before so we don't need a lock here anymore!
			while(garbage)
			{
				next = garbage->next;
				free(garbage->data);
				free(garbage);
				garbage = next;
			}
		}
		sleepms_on_cond(__func__, &sleep_cond_mutex, &sleep_cond, 500);
	}
	pthread_exit(NULL);
}

void start_garbage_collector(int32_t debug)
{
	garbage_debug = debug;
	int32_t i;

	SAFE_MUTEX_INIT(&add_lock, NULL);

	for(i = 0; i < HASH_BUCKETS; ++i)
	{
		cs_lock_create(__func__, &garbage_lock[i], "garbage_lock", 9000);

		garbage_first[i] = NULL;
	}
	cs_pthread_cond_init(__func__, &sleep_cond_mutex, &sleep_cond);

	garbage_collector_active = 1;

	int32_t ret = start_thread("garbage", (void *)&garbage_collector, NULL, &garbage_thread, 0, 1);
	if(ret)
	{
		cs_exit(1);
	}
}

void stop_garbage_collector(void)
{
	if(garbage_collector_active)
	{
		int32_t i;

		garbage_collector_active = 0;
		SAFE_COND_SIGNAL(&sleep_cond);
		cs_sleepms(300);
		SAFE_COND_SIGNAL(&sleep_cond);
		SAFE_THREAD_JOIN(garbage_thread, NULL);

		for(i = 0; i < HASH_BUCKETS; ++i)
			{ cs_writelock(__func__, &garbage_lock[i]); }

		for(i = 0; i < HASH_BUCKETS; ++i)
		{
			while(garbage_first[i])
			{
				struct cs_garbage *next = garbage_first[i]->next;
				NULLFREE(garbage_first[i]->data);
				NULLFREE(garbage_first[i]);
				garbage_first[i] = next;
			}
		}

		for(i = 0; i < HASH_BUCKETS; ++i)
		{
			cs_writeunlock(__func__, &garbage_lock[i]);
			cs_lock_destroy(__func__, &garbage_lock[i]);
		}

		pthread_mutex_destroy(&add_lock);
		pthread_cond_destroy(&sleep_cond);
		pthread_mutex_destroy(&sleep_cond_mutex);
	}
}
