
#include <lighttpd/tasklet.h>

typedef struct liTasklet liTasklet;

struct liTaskletPool {
	GThreadPool *threadpool;

	liEventAsync finished_watcher;
	GAsyncQueue *finished;

	int threads;

	/* -1: running finished_watcher_cb, do not delete
	 *  0: standard, do not delete, can delete
	 *  1: running finished_watcher_cb, delete in finished_watcher_cb
	 */
	int delete_later;
};

struct liTasklet {
	liTaskletRunCB run_cb;
	liTaskletFinishedCB finished_cb;
	gpointer data;
};

static void finished_watcher_cb(liEventBase *watcher, int events) {
	liTaskletPool *pool = LI_CONTAINER_OF(li_event_async_from(watcher), liTaskletPool, finished_watcher);
	liTasklet *t;
	UNUSED(events);

	pool->delete_later = -1;

	while (NULL != (t = g_async_queue_try_pop(pool->finished))) {
		t->finished_cb(t->data);

		g_slice_free(liTasklet, t);

		if (1 == pool->delete_later) {
			g_slice_free(liTaskletPool, pool);
			return;
		}
	}

	pool->delete_later = 0;
}

static void run_tasklet(gpointer data, gpointer userdata) {
	liTaskletPool *pool = userdata;
	liTasklet *t = data;

	t->run_cb(t->data);
	g_async_queue_push(pool->finished, t);
	li_event_async_send(&pool->finished_watcher);
}

liTaskletPool* li_tasklet_pool_new(liEventLoop *loop, gint threads) {
	liTaskletPool *pool = g_slice_new0(liTaskletPool);

	li_event_async_init(loop, "tasklet pool", &pool->finished_watcher, finished_watcher_cb);

	pool->finished = g_async_queue_new();

	li_tasklet_pool_set_threads(pool, threads);

	return pool;
}

void li_tasklet_pool_free(liTaskletPool *pool) {
	liTasklet *t;

	if (!pool) return;

	li_tasklet_pool_set_threads(pool, 0);

	while (NULL != (t = g_async_queue_try_pop(pool->finished))) {
		t->finished_cb(t->data);
	}
	g_async_queue_unref(pool->finished);
	pool->finished = NULL;

	li_event_clear(&pool->finished_watcher);

	if (-1 == pool->delete_later) {
		pool->delete_later = 1;
	} else {
		g_slice_free(liTaskletPool, pool);
	}
}

void li_tasklet_pool_set_threads(liTaskletPool *pool, gint threads) {
	if (threads < 0) threads = -1;
	if (pool->threads == threads) return;

	if (NULL != pool->threadpool) {
		if (pool->threads > 0 && threads > 0) {
			/* pool was exclusive, stays exclusive. just change number of threads */
			g_thread_pool_set_max_threads(pool->threadpool, threads, NULL);
			pool->threads = g_thread_pool_get_num_threads(pool->threadpool);
			/* as we already had exclusive threads running, pool->threads should be > 0 */
			return;
		}

		/* stop old pool */
		g_thread_pool_free(pool->threadpool, FALSE, TRUE);
		pool->threadpool = NULL;
	}

	if (threads != 0) {
		pool->threadpool = g_thread_pool_new(run_tasklet, pool, threads, (threads > 0), NULL);
		if (threads > 0) { /* exclusive pool, see how many threads we got */
			threads = g_thread_pool_get_num_threads(pool->threadpool);
			if (threads == 0) { /* couldn't get exclusive threads, share threads instead */
				g_thread_pool_free(pool->threadpool, FALSE, TRUE);
				pool->threadpool = g_thread_pool_new(run_tasklet, pool, -1, FALSE, NULL);
				threads = -1;
			}
		}
	}

	pool->threads = threads;
}

gint li_tasklet_pool_get_threads(liTaskletPool *pool) {
	return pool->threads;
}

void li_tasklet_push(liTaskletPool* pool, liTaskletRunCB run, liTaskletFinishedCB finished, gpointer data) {
	liTasklet *t = g_slice_new0(liTasklet);
	t->run_cb = run;
	t->finished_cb = finished;
	t->data = data;

	if (NULL != pool->threadpool) {
		g_thread_pool_push(pool->threadpool, t, NULL);
	} else {
		run_tasklet(t, pool);
	}
}
