/**
 * @file rwlock.c
 * @brief Functions for using synchronization primitive called R/W lock
 * 
 * Functions for initialize, destroy, lock for writing or reading and timeout lock.
 * R/w lock can be locked by multiple readers at a time and only one writer at a time.
 * Writers has higher priority, it means that if there are waiting readers and waiting writers
 * the first waiting writer will be woken up and readers will stay suspended.
 * Writing thread can locked the lock recursively and the lock will be free after 
 * the count of callings unlock will be same as count of callings lock or lock timeout.
 * This implementation does not test wheter the unlocking thread has the r/w lock locked! 
 * 
 */
 
#include "rwlock.h"
#include "malloc.h"
#include "int.h"
#include "debug.h"
#include "timers.h"
#include "thread.h"
#include "errnums.h"

/**
 * @brief
 * @param rwl - r/w lock structure
 * 
 * Function initialize r/w lock structure to state open. Set pointers to waiting threads to
 * NULL and count of writing and reading threads to 0
 * 
 */
void rwlock_init (rwlock_t * rwl){
	rwl->w_count = 0;
	rwl->r_count = 0;
	rwl->writing_thread = NULL;
	thread_queue_init(&(rwl->r_queue));
	thread_queue_init(&(rwl->w_queue)); 
}

/**
 * @brief Destroy r/w lock
 * @param rwl - r/w lock structure
 * 
 * This only check if there are any waiting threads in both queues, and if it is
 * call panic.
 * 
 */
void rwlock_destroy(rwlock_t * rwl){
	if (thread_queue_empty(&(rwl->w_queue))!= EEMPTY 
		|| thread_queue_empty(&(rwl->r_queue))!= EEMPTY)
		panic("Destroying R/W lock, but there are waitings threads.");
}

/**
 * Funkce se pokusi zamknout zamek v read rezimu, pokud to neni
 * mozne, zablokuje volajici vlakno na zamku.
 * 
 * @param r/w lock structure
 */
 
/**
 * @brief Lock the r/w lock for reading
 * @param rwl - r/w lock structure
 * 
 * Function try to lock r/w lock for reading, if there is writing thread, 
 * current thread is suspended on this lock.
 * 
 */
void rwlock_read_lock (rwlock_t * rwl){
	thread_t cur_thread = thread_get_current();
	// entry to interrupts save mode
	save_and_disable_interrupts();
	// while there is writing thread, while is there due to scheduling
	while (rwl->w_count > 0){
		// lock is not free for reading, insert thread to queue and suspend
					#ifdef RW_DEBUG
					printk("   rwlock_read_lock, w_count = %d\n", rwl->w_count);
					#endif
				thread_set_state(cur_thread, THRS_SUSPENDED);
					#ifdef QUEUE_DEBUG
					printk("     rwl w_queue:");
					thread_queue_debug_print(&(rwl->r_queue), GENERAL);
					#endif
				thread_queue_add(&(rwl->r_queue), &cur_thread, GENERAL);
					#ifdef QUEUE_DEBUG
					printk("     rwl r_queue after adding: ");
					thread_queue_debug_print(&(rwl->r_queue), GENERAL);
					#endif
				thread_suspend();
				
	}
	// lock is opened, raise count of reading threads and raise thread resource counter
	rwl->r_count++;
	cur_thread->resource_couter++;
	restore_interrupts();
}

/**
 * @brief Lock the r/w lock for writing
 * @param rwl - r/w lock structure
 * 
 * Function try to lock r/w lock for writing, if there is writing or reading thread,
 * current thread is suspended on this lock. If the current thread is same as actual
 * thread it is recursion call of lock function and only count of writing threads is 
 * raised.
 * 
 */
void rwlock_write_lock (rwlock_t * rwl){
	thread_t cur_thread = thread_get_current();
	// entry to interrupts save mode
	save_and_disable_interrupts();
	// if the current thread is same as actually writing thread -> recursion
	if (rwl->writing_thread != cur_thread){
		// while there is writing or reading thread, while is there due to scheduling
		while (rwl->w_count > 0 || rwl->r_count > 0){
			// lock is not free for writing, insert thread to queue and suspend
				#ifdef QUEUE_DEBUG
				printk("     rwl w_queue: ");
				thread_queue_debug_print(&(rwl->w_queue), GENERAL);
				#endif
			thread_set_state(cur_thread, THRS_SUSPENDED);
			thread_queue_add(&(rwl->w_queue), &cur_thread, GENERAL);
				#ifdef QUEUE_DEBUG
				printk("     rwl w_queue after adding: ");
				thread_queue_debug_print(&(rwl->w_queue), GENERAL);
				#endif
			thread_suspend();
		}
		// lock is opened, set pointer to writing thread and raise thread resource counter
		rwl->writing_thread = cur_thread;
		cur_thread->resource_couter++;
	}
	rwl->w_count++;
	restore_interrupts();
}

/**
 * @brief Non block read lock
 * @param rwl - r/w lock structure
 * @return EOK if success and ETIMEDOUT otherwise
 * 
 * Function try to lock the r/w lock for reading, but not suspend on this lock.
 * Return EOK if the lock was succesfully locked for reading and ETIMEDOUT if there
 * is writing thread on the lock
 * 
 */
int rwlock_read_lock_try (rwlock_t * rwl){
	int res;
	
	thread_t cur_thread;
	save_and_disable_interrupts();
	// if there is not writing thread
	if (!rwl->w_count){
		rwl->r_count++;
		cur_thread = thread_get_current();
		cur_thread->resource_couter++;
		res = EOK;
	}
	else
	  res = ETIMEDOUT;
	  
	restore_interrupts();
	return res;
}

/**
 * @brief Non block write lock
 * @param rwl - r/w lock structure
 * @return EOK if success and ETIMEDOUT otherwise
 * 
 * Function try to lock the r/w lock for writing, but not suspend on this lock.
 * Return EOK if the lock was succesfully locked for writing and ETIMEDOUT if there
 * is writing or reading thread on the lock
 * 
 */
int rwlock_write_lock_try (rwlock_t * rwl){
	int res;
	
	thread_t cur_thread;
	save_and_disable_interrupts();
	// if there is not writing or reading thread
	if (!rwl->w_count && !rwl->r_count){
		rwl->w_count++;
		cur_thread = thread_get_current();
		cur_thread->resource_couter++;
		rwl->writing_thread = cur_thread;
		res = EOK;
	}
	else
	  res = ETIMEDOUT;
	  
	restore_interrupts();
	return res;
}


/**Funkce se pokusi zamknout zamek v pozadovanem rezimu, pokud to neni
 * mozne, zablokuje volajici vlakno na zamku.
 * Varianta _timeout ceka na zamknuti zamku nejdele usec mikrosekund.
 * Pokud se behem teto doby podari zamek zamknout, vraci EOK, jinak
 * ETIMEDOUT. Pokud je casovy limit 0, k zablokovani vlakna nedochazi. */
/**
 * @brief Read lock with timeout
 * @param rwl - r/w lock structure
 * @param usec - count of microseconds
 * @return EOK if success and ETIMEDOUT otherwise
 * 
 * Trying to lock r/w lock for reading only for usec microseconds. Return
 * EOK if the lock was succesfully locked for reading and return ETIMEDOUT
 * if usec microseconds run out.
 * 
 */
int rwlock_read_timeout (rwlock_t * rwl, const unsigned int usec){
	unsigned int start;
	int res;
	
	start = get_sysutime();
	
	while (((res = rwlock_read_lock_try(rwl)) == ETIMEDOUT)
		   && (get_sysutime() - start) < usec)
		thread_yield();
	return res;
}

/**
 * @brief Write lock with timeout
 * @param rwl - r/w lock structure
 * @param usec - count of microseconds
 * @return EOK if success and ETIMEDOUT otherwise
 * 
 * Trying to lock r/w lock for writing only for usec microseconds. Return
 * EOK if the lock was succesfully locked for writing and return ETIMEDOUT
 * if usec microseconds run out.
 * 
 */
int rwlock_write_timeout (rwlock_t * rwl, const unsigned int usec){
	unsigned int start;
	int res;
	
	start = get_sysutime();
	
	while (((res = rwlock_write_lock_try(rwl)) == ETIMEDOUT)
		   && (get_sysutime() - start) < usec)
		thread_yield();
	return res;
}


/**
 * @brief Release r/w lock locked for reading
 * @param rwl - r/w lock structure
 * 
 * Less the count of reading threads and if the count is 0, release the lock and wake up
 * threads suspended on this lock
 * 
 */
void rwlock_read_unlock (rwlock_t * rwl){
	thread_t cur_thr = thread_get_current();
	
	save_and_disable_interrupts();
	rwl->r_count--;
	cur_thr->resource_couter--;
	
	// if there are not reading threads and queue of threads waiting for writing is not
	// empty wake up first waiting thread
	if ((rwl->r_count == 0) && (thread_queue_fetch(&(rwl->w_queue), &cur_thr, GENERAL) == EOK))
		thread_wakeup(cur_thr);
	else
	// this queue is empty, because lock is locked for reading
		while (thread_queue_fetch(&(rwl->r_queue), &cur_thr, GENERAL) == EOK)
			thread_wakeup(cur_thr);
	  
	restore_interrupts();
}

/**
 * @brief Release r/w lock locked for writing
 * @param rwl - r/w lock structure
 * 
 * Less the count of writing recursion and if the count is 0, release the lock and wake up
 * threads suspended on this lock
 * 
 */
void rwlock_write_unlock (rwlock_t * rwl){
	thread_t  cur_thr = thread_get_current();
	
	save_and_disable_interrupts();
	rwl->w_count--;
	// if all recursive lock are released
	if (!rwl->w_count){
		cur_thr->resource_couter--;
		rwl->writing_thread = NULL;
		
		// if there is thread in queue of waiting threads, wakeup it
		if (thread_queue_fetch(&(rwl->w_queue), &cur_thr, GENERAL) == EOK){
			thread_wakeup(cur_thr);
		}
		// else wake up all threads waiting for reading
		else
			while (thread_queue_fetch(&(rwl->r_queue), &cur_thr, GENERAL) == EOK)
				thread_wakeup(cur_thr);		
	}
	restore_interrupts();
}
