//                              -*- Mode: CFA -*-
//
// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
//
// The contents of this file are covered under the licence agreement in the
// file "LICENCE" distributed with Cforall.
//
// signal.c --
//
// Author           : Thierry Delisle
// Created On       : Mon Jun 5 14:20:42 2017
// Last Modified By : Thierry Delisle
// Last Modified On : --
// Update Count     : 0
//

#include "preemption.h"

extern "C" {
#include <signal.h>
}

#define __CFA_DEFAULT_PREEMPTION__ 10

__attribute__((weak)) unsigned int default_preemption() {
	return __CFA_DEFAULT_PREEMPTION__;
}

static void preempt( processor   * this );
static void timeout( thread_desc * this );

//=============================================================================================
// Kernel Preemption logic
//=============================================================================================

void kernel_start_preemption() {

}

void tick_preemption() {
	alarm_list_t * alarms = &systemProcessor->alarms;
	__cfa_time_t currtime = __kernel_get_time();
	while( alarms->head && alarms->head->alarm < currtime ) {
		alarm_node_t * node = pop(alarms);
		if( node->kernel_alarm ) {
			preempt( node->proc );
		}
		else {
			timeout( node->thrd );
		}

		if( node->period > 0 ) {
			node->alarm += node->period;
			insert( alarms, node );
		}
		else {
			node->set = false;
		}
	}

	if( alarms->head ) {
		__kernel_set_timer( alarms->head->alarm - currtime );
	}
}

void update_preemption( processor * this, __cfa_time_t duration ) {
	//     assert( THREAD_GETMEM( disableInt ) && THREAD_GETMEM( disableIntCnt ) == 1 );
	alarm_node_t * alarm = this->preemption_alarm;

	// Alarms need to be enabled
	if ( duration > 0 && !alarm->set ) {
		alarm->alarm = __kernel_get_time() + duration;
		alarm->period = duration;
		register_self( alarm );
	}
	// Zero duraction but alarm is set
	else if ( duration == 0 && alarm->set ) {
		unregister_self( alarm );
		alarm->alarm = 0;
		alarm->period = 0;
	}
	// If alarm is different from previous, change it
	else if ( duration > 0 && alarm->period != duration ) {
		unregister_self( alarm );
		alarm->alarm = __kernel_get_time() + duration;
		alarm->period = duration;
		register_self( alarm );
	}
}

void ?{}( preemption_scope * this, processor * proc ) {
	(&this->alarm){ proc };
	this->proc = proc;
	this->proc->preemption_alarm = &this->alarm;
	update_preemption( this->proc, this->proc->preemption );
}

void ^?{}( preemption_scope * this ) {
	update_preemption( this->proc, 0 );
}

//=============================================================================================
// Kernel Signal logic
//=============================================================================================

static inline bool preemption_ready() {
	return this_processor->disable_preempt_count == 0;
}

static inline void defer_ctxSwitch() {
	this_processor->pending_preemption = true;
}

static inline void defer_alarm() {
	systemProcessor->pending_alarm = true;
}

void sigHandler_ctxSwitch( __attribute__((unused)) int sig ) {
	if( preemption_ready() ) {
		ScheduleInternal( this_processor->current_thread );
	}
	else {
		defer_ctxSwitch();
	}
}

void sigHandler_alarm( __attribute__((unused)) int sig ) {
	if( try_lock( &systemProcessor->alarm_lock ) ) {
		tick_preemption();
		unlock( &systemProcessor->alarm_lock );
	}
	else {
		defer_alarm();
	}
}

static void preempt( processor * this ) {
	pthread_kill( this->kernel_thread, SIGUSR1 );
}

static void timeout( thread_desc * this ) {
	//TODO : implement waking threads
}