#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <inttypes.h>
#include <pthread.h>
#include <sched.h>
#include <time.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

static pthread_barrier_t  barrier;
static pthread_mutex_t    mutex;

static void idle(unsigned ms) {
	struct timespec  ts;
	ts.tv_sec = 0;
	ts.tv_nsec = ms * 1000U * 1000U;
	nanosleep(&ts, NULL);
}

static void busy(unsigned ms) {
	uint64_t  cycles = (ms * SYSPAGE_ENTRY(qtime)->cycles_per_sec) / 1000U;
	uint64_t  t0 = ClockCycles();
	while((ClockCycles() - t0) < cycles) {
	}
}

static void *A(void *arg) {
	pthread_setname_np(0, "Thread A");
	pthread_barrier_wait(&barrier);
	idle(1);
	busy(2);
	pthread_mutex_lock(&mutex);
	busy(1);
	idle(2);
	return arg;
}

static void *B(void *arg) {
	pthread_setname_np(0, "Thread B");
	pthread_barrier_wait(&barrier);
	idle(2);
	busy(5);
	idle(3);
	return arg;
}

static void *C(void *arg) {
	pthread_setname_np(0, "Thread C");
	pthread_barrier_wait(&barrier);
	pthread_mutex_lock(&mutex);
	idle(3);
	pthread_mutex_unlock(&mutex);
	busy(1);
	idle(1);
	return arg;
}

int main(int argc, char *argv[]) {
	struct sched_param  sched_param_A = { .sched_priority = 21 };
	struct sched_param  sched_param_B = { .sched_priority = 21 };
	struct sched_param  sched_param_C = { .sched_priority = 10 };
	pthread_attr_t  attr_A;
	pthread_attr_t  attr_B;
	pthread_attr_t  attr_C;
	pthread_t  thread_A;
	pthread_t  thread_B;
	pthread_t  thread_C;

	pthread_barrier_init(&barrier, NULL, 3U);
	pthread_mutex_init(&mutex, NULL);

	pthread_attr_init(&attr_A);
	pthread_attr_init(&attr_B);
	pthread_attr_init(&attr_C);

	pthread_attr_setinheritsched(&attr_A, PTHREAD_EXPLICIT_SCHED);
	pthread_attr_setinheritsched(&attr_B, PTHREAD_EXPLICIT_SCHED);
	pthread_attr_setinheritsched(&attr_C, PTHREAD_EXPLICIT_SCHED);

	pthread_attr_setschedparam(&attr_A, &sched_param_A);
	pthread_attr_setschedparam(&attr_B, &sched_param_B);
	pthread_attr_setschedparam(&attr_C, &sched_param_C);

	pthread_attr_setschedpolicy(&attr_A, SCHED_RR);
	pthread_attr_setschedpolicy(&attr_B, SCHED_RR);
	pthread_attr_setschedpolicy(&attr_C, SCHED_RR);

	pthread_create(&thread_A, &attr_A, A, NULL);
	pthread_create(&thread_B, &attr_B, B, NULL);
	pthread_create(&thread_C, &attr_C, C, NULL);

	pthread_join(thread_A, NULL);
	pthread_join(thread_B, NULL);
	pthread_join(thread_C, NULL);

	return EXIT_SUCCESS;
}
