#include "lwt.h"

#include <pthread.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#include "util/die.h"
#include "sched/scheduler.h"
#include "sched/thread.h"

static pthread_t *threads = 0;
static uint32_t nthreads = 0;

//global objects
lwt::Scheduler scheduler;

static void* thread_start(void *unused)
{
	(void)unused;

	lwt_scheduler_loop();
	lwt::die("scheduler loop returned");

	return NULL;
}

int lwt_init()
{
	if(!scheduler.init()) {
		return -1;
	}

	const long processors = sysconf(_SC_NPROCESSORS_ONLN);
	if(processors <= 0) {
		lwt::die("sysconf(_SC_NPROCESSORS_ONLN) = %ld", processors);
		return 1;
	}

	nthreads = processors;

	threads = (pthread_t*)malloc(sizeof(pthread_t) * nthreads);
	if(threads == NULL) {
		lwt::die("malloc");
		return 2;
	}

	for(uint32_t i = 0; i != nthreads; ++i)
	{
		pthread_attr_t attr;
		errno = pthread_attr_init(&attr);
		if(errno != 0) {
			lwt::die("pthread_attr_init");
			return 3;
		}

		cpu_set_t cpuset;
		CPU_ZERO(&cpuset);
		CPU_SET(i % processors, &cpuset);

		errno = pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &cpuset);
		if(errno != 0) {
			lwt::die("pthread_attr_setaffinity_np");
			return 4;
		}

		errno = pthread_create(&threads[i], &attr, thread_start, NULL);
		if(errno != 0) {
			lwt::die("pthread_create");
			return 5;
		}

		errno = pthread_attr_destroy(&attr);
		if(errno != 0) {
			lwt::die("pthread_attr_destroy");
			return 6;
		}
	}

	return 0;
}

int lwt_run(lwt_func func, void *a, void *b)
{
	if(!scheduler.readyFunc(func, a, b)) {
		return 1;
	}
	return 0;
}

static void kill_func(void *th, void*)
{
	lwt::Thread *thread = (lwt::Thread*)th;
	lwt::Alloc::free(thread);

	pthread_exit(0);
}

void lwt_destroy()
{
	for(uint32_t i = 0; i != nthreads; ++i) {
		scheduler.readyFunc(kill_func, 0, 0, true);
	}

	for(uint32_t i = 0; i != nthreads; ++i) {
		pthread_join(threads[i], NULL);
	}

	free(threads);
}
