/**
 * @file semaphore.c
 * @brief implementation of semaphores
 * 
 * Semaphores are implemented as a integer variable and queue of threads waiting on the semaphore.
 * The implementation don't solve this cases (&& we know it && we think it can cause troubles )
 * Is undefined what happens, if thread waiting in the quque is waked up by another thread and not
 * by semaphore. It is undefined, what happens, if thread "owning" semaphore is killed.
 * 
 * This file is based on Kalisto, Development Kernel copyrighted (c) to 
 * Distributed Systems Research Group MFF UK, Czech republic.
 */

#include "semaphore.h"
#include "errnums.h"
#include "debug.h"
#include "timers.h"
#include "int.h"
#include "io.h"

/**
 * @brief Initilaization of the semaphore.
 * 
 * This function initiliaze structure for the semaphore.
 * 
 * @param sem - semaphore to be initialized
 * @param value - value to be initialized to
 */
void sem_init (semaphore_t * sem, const int value){
	thread_queue_init(&(sem->waiting_threads));
	sem->value = value;
}

/**
 * @brief free semaphore structures.
 * 
 * Function call panic if any thread is waiting on semaphore.
 * 
 * @param sem - semaphore to be initialized
 */  
void sem_destroy (semaphore_t * sem){
					#ifdef SEM_DEBUG
					printk("Destroy semaphore %p\n", sem);
					#endif
	
	/* semaphore should not be destroyed if there are any threads waiting on it */	
	if (thread_queue_empty(&(sem->waiting_threads)) == ENOTEMPTY){
		panic("Can not destroy semaphore with threads waiting on it. machine will be halted!\n");
		//unreachable, for delepment stage only return
		return ;  
	}
}


/**
 * @brief Get value of the semaphore
 * @return Return an actual value of the semaphore. 
 */
int sem_get_value (semaphore_t * sem){
	return sem->value;
}

/**
 * @brief Function increment semaphore value, in case some threads are waiting on the semaphore, it \ 
 * wakeup one of them 
 * @param sem - pointer to semaphore structure.  
 */
void sem_up (semaphore_t * sem){

 	thread_t thread;

 	save_and_disable_interrupts();
 					#ifdef SEM_DEBUG
 					printk("semaphore UP! from value = %d ", sem->value);
 					#endif
    /* wakeup first thread waiting on the semaphore, if any */
    if ((thread_queue_fetch(&(sem->waiting_threads), &thread, GENERAL)) == EOK) { 
    	thread_wakeup(thread);   	
    }
    
 	/*increment semaphore */
	sem->value++;
					#ifdef SEM_DEBUG
					printk("to new sem value = %d\n", sem->value);
					#endif
					//__thread_dprint(thread);
 	thread = thread_get_current();
 					//__thread_dprint(thread);
	thread->num_semaphores_inc--;
	
 	restore_interrupts();	
}

/**
 * @brief Sets down semaphore If the semaphore can not be set down, thread suspendon it.
 * 
 * @param sem - semaphore to be set down
*/ 
void sem_down (semaphore_t * sem){ 	
 	thread_t cur_thr = thread_get_current();
 	save_and_disable_interrupts(); 	 	
 	/* if semaphore is not usable go to sleep */
 					#ifdef SEM_DEBUG
 	 				printk("semaphore DOWN! from value = %d\n", sem->value);
 	 				#endif
 	while (sem->value == 0) {
			 		#ifdef SEM_DEBUG
			 		#ifdef QUEUE_DEBUG
			 		printk("------------- Thread queue on semaphore %p: ", sem);	
					thread_queue_debug_print(&(sem->waiting_threads), GENERAL);
					#endif
					#endif
		thread_set_state(cur_thr, THRS_SUSPENDED);
		thread_queue_add(&(sem->waiting_threads), &cur_thr, GENERAL);
			 	 	#ifdef SEM_DEBUG
			 		#ifdef QUEUE_DEBUG
			 		printk("Thread [%d] added to semaphore queue: ", cur_thr->id);	
					thread_queue_debug_print(&(sem->waiting_threads), GENERAL);
					#endif
					#endif
 	 	thread_suspend(); 	 	 	 	 	 
 	} 
 	/* set semaphore down */
 	sem->value--;
			 		#ifdef SEM_DEBUG
			 		printk("sem value = %d\n", sem->value);
			 		#endif
	cur_thr->num_semaphores_inc ++;
	restore_interrupts(); 	
}

/**  
 * @brief Internal function 
 * 
 * Tryes to set semaphore down
 * @return -1 if semaphore can not be set down, 0 otherwise.  
 * 
 */
int sem_try_down(semaphore_t * sem)
{
	int result;
	thread_t cur_thr;

 	save_and_disable_interrupts();
 	if (sem->value == 0)
 		result = -1; //semaphore can not be set down, so return -1, indicate unsuccesfull. 
 	else {
	 	// set semaphore down
	 	cur_thr = thread_get_current();
	 	cur_thr->num_semaphores_inc ++;
	 	sem->value--;
	 	result = 0;  // successful, semaphore down.
 	}
	restore_interrupts();
  	return result;	
}


/*
 * @brief For msec seconds keep trying to set semaphore down
 * @param sem - semaphore pointer
 * @param usec - time to wait, on zero the calling thread isn't blocked
 *
 */
int sem_down_timeout (semaphore_t * sem, const unsigned int usec){  
	//int res;
	
	unsigned int stime = get_sysutime ();
		
	while ( ((sem_try_down(sem)) == -1) ){
		if  ( (get_sysutime () - stime) /*us->ms*/ < usec ){
			thread_yield();
		}
		else{
			return ETIMEDOUT;
		}
	}
	return EOK;	
}

