#include "thread.h"
#include "errnums.h"
#include "malloc.h"
#include "offset.h"
#include "tqueue.h"
#include "sched.h"
#include "int.h"
#include "debug.h"
#include "io.h"
#include "timers.h"

/** global variable to store nr of living threads int he system */
int living_threads;

 char * thread_status [6]= {"d", "r", "sl", "su", "z"};

/** global wariables to store */
thread_t current_thread, idle_thread;

/** queue to store all threads living in system */

int thread_id_counter = 0;


inline unsigned thread_get_wakeup_time_in_ticks(thread_t thread){
	return thread->sleep_start_time + thread->sleep_length;
}	


/*
 * init_thread creates a new thread structure and initializes the
 * context information in its data structures.
 */
	
//////////////////////////////////////////////////////////////
// Interni funkce pro poziti v tomto souboru
//////////////////////////////////////////////////////////////	

/**
 * Alloc memory for new thread.
 * @param pointer to thread to allocate memory for.
 * @return EOK on succes or ENOMEM on memory lack problem
 */
int __thread_alloc_memory(thread_t* thr){
	thread_t t;
	/*
	 * Allocate memory for a thread control structure and stack.
	 */
	t = malloc (sizeof (struct thread_struct));
	if (!t)
		return ENOMEM;

	t->kernel_stack = malloc (KERNEL_STACK_SIZE);
	if (!t->kernel_stack) {
		free (t);
		return ENOMEM;
	}
	*thr = t;
	return EOK;
}




/**
 * Free all the memmory allocated for the thread...
 */
void __thread_free_memory(thread_t thread){
	free (thread->kernel_stack);
	free (thread);
}

static void __thread_exit()
{
	//thread_t head = current_thread->waiting_threads;
	save_and_disable_interrupts();
						#ifdef THREAD_DEBUG
						printk ("\n ======================== Thread [%d] terminated! ======================== \n", current_thread->id);
						#endif
						
		//wakeup waiting threads
	
	if (current_thread->joined){
		thread_wakeup(current_thread->joined);
	}
	thread_set_state(current_thread, THRS_ZOMBIE);
		// now re-plan to new thread 
	schedule();
	restore_interrupts();
}

/** wrapper function for thread function, contains __exit rutines */
static void __thread_wrapper(void (*start)(void *), void *arg)
{
	start(arg);
	thread_kill(current_thread);
	//___halt();
}

/**
 * Basic initialization of the thread context stored on the stack. Thread structure must be already allocated
 * @param t thread to be initialised
 * @param start is a function to be run in the thread
 * @status statust of the thread
 */
static void __thread_init_context(thread_t t, void (*start) (void*), void * data, int status){
		/* first set status to DISABLED, will be changed when thread is will be stored in scheduler structures */  
	t->state = THRS_DISABLED;
		/* set pointer to the top of the kernel stack, on exception is stored */
	t->kernel_stack_context = 0;
		/* pointer to a place for all needed registers while the context is switchd 
		 * FIXMEE k cemu je tam ta jednicka nakonci?? jako ze tam neni navratova adresa?? 
		 */					
		 					#ifdef THREAD_DEBUG
							printk("==== Start initilized to function on %p ===\n", start);
							#endif 
	t->kernel_stack_top = &t->kernel_stack [
		KERNEL_STACK_SIZE / sizeof (int) -
		EX_STACK_FRAME / sizeof (int) - 1];
		/* return adress */	
	((int *) t->kernel_stack_top) [OFFSET_RA / sizeof (int)] = (int)  __thread_wrapper; //start;
		/* nastavit prvni  */
	((int *) t->kernel_stack_top) [OFFSET_A0 / sizeof (int)] = (int) start;
		/* set procesing mode of mips to default :unmapped, uncached */
	((int *) t->kernel_stack_top) [OFFSET_A1 / sizeof (int)] = (int) data;
		/* set procesing mode of mips to default :unmapped, uncached */					
	((int *)t->kernel_stack_top) [OFFSET_GP / sizeof (int)] = OM_KSEG1;
		/*set status to status */
	((int *)t->kernel_stack_top) [OFFSET_STATUS / sizeof (int)] = status;
		/* set # of interrubts to 0 */
	t->interrupt_counter = 0;
					#ifdef THREAD_DEBUG
					dprintk ("Thread_context intitialized\n");
					#endif
}

static void
ctx_thread_system (struct thread_struct *t, void (*start) (void ), int status)
{
	/*
	 * Initialize thread state, stack and context.
	 */
	t->state = THRS_DISABLED;
	t->kernel_stack_context = 0;
	t->kernel_stack_top = &t->kernel_stack [
		KERNEL_STACK_SIZE / sizeof (int) -
		EX_STACK_FRAME / sizeof (int) - 1];
	t->kernel_stack_top [OFFSET_RA / sizeof (int)] = (int) start;
	t->kernel_stack_top [OFFSET_GP / sizeof (int)] = OM_KSEG1;
	t->kernel_stack_top [OFFSET_STATUS / sizeof (int)] = status;

	t->joined = NULL; //thread joined to this thread
	t->interrupt_counter = 0;
	t->detached = FALSE;
	t->finished = 0;
	t->resource_couter = 0;
	t->woken_up = WOKEN_UNDEFINED;
		//enque it to valid threads.
	//valid_threads.tail->next = t->vt;
	t->id = thread_id_counter++; 	//system thread has got 
		//not necessary to be set, but...
	t->sleep_start_time = 0;
	t->sleep_length = 0;
		//will be set later
	t->next = NULL;
	t->prev = NULL; 
	t->vnext = NULL;
	t->vprev = NULL; 
	
	living_threads++;

}





static void __thread_init_structures(thread_t thread){
		/** init queue of threads waiting for this thread to empty */
		//valid_thread_item_struct_t *vts;
	thread->interrupt_counter = 0;
	thread->detached = FALSE;
	thread->finished = 0;
	thread->resource_couter = 0;
	thread->woken_up = WOKEN_UNDEFINED;
		//enque it to valid threads.
	//valid_threads.tail->next = thread->vt;
	thread->id = thread_id_counter++; 
		//not necessary to be set, but...
	thread->sleep_start_time = 0;
	thread->sleep_length = 0;
		//will be set later
	thread->next = NULL;
	thread->prev = NULL; 
	thread->vnext = NULL;
	thread->vprev = NULL; 
}

/** FIXMEE, neni kompletni, jen to co jsme potrebovali... */
int __thread_set_state(thread_t thread, int state){
	save_and_disable_interrupts();
						#ifdef THREAD_DEBUG
						printk("Setting thread state of thread [%d] from %s -> %s. \n", thread->id, ts(thread->state), ts(state));
						#endif
	switch (state){
		
	case THRS_READY :
		switch (thread->state){
			case THRS_DISABLED:
					thread_valid_add(&valid_threads, &thread);
				break;
		    case THRS_SLEEPING :
		    			#ifdef THREAD_DEBUG
		    			#ifdef QUEUE_DEBUG
						printk("------------- Sleeping thread queue: "); 
						thread_queue_debug_print(&sleeping_threads);
						#endif
						#endif
			   	thread_queue_remove_item((&sleeping_threads), &thread);
			   			#ifdef THREAD_DEBUG
			   			#ifdef QUEUE_DEBUG
						printk("Thread [%d] removed from sleeping thread queue.\n", thread->id);
						thread_queue_debug_print(&sleeping_threads);
						printk("------------------------------------ \n");
						#endif
						#endif
			    break;
			case THRS_READY:
				panic("Changing state from ready to ready");
				restore_interrupts();
				return EOK;
		}
		thread->state = THRS_READY;
						#ifdef THREAD_DEBUG
						#ifdef QUEUE_DEBUG
						printk("------------- Runnable thread queue: ");		
						thread_queue_debug_print(&runnable_threads);
						#endif
						#endif
		thread_queue_add(&runnable_threads, &thread);
						#ifdef THREAD_DEBUG
						#ifdef QUEUE_DEBUG
						printk("Thread [%d] added to runnable thread queue.\n",thread->id);
						thread_queue_debug_print(&runnable_threads);
						printk("------------------------------------ \n");
						#endif
						#endif
		break;	
	case THRS_ZOMBIE:
		switch (thread->state) {
			case THRS_READY:
				#ifdef THREAD_DEBUG
				#ifdef QUEUE_DEBUG
				printk("------------- Runnable thread queue: ");		
				thread_queue_debug_print(&runnable_threads);
				#endif
				#endif
				thread_queue_remove_item(&runnable_threads, &thread);
				#ifdef THREAD_DEBUG
				#ifdef QUEUE_DEBUG
				printk("Thread [%d] removed from runnable thread queue.", thread->id);
				thread_queue_debug_print(&runnable_threads);	
				#endif
				#endif				
				
				break;
			case THRS_SLEEPING:
				thread_queue_remove_item(&sleeping_threads, &thread);
				break;
			case THRS_SUSPENDED:
					panic("No state switch because of suspended state - FIXME\n");
				break;
		}
		thread->state = THRS_ZOMBIE;
		living_threads--;
		break;
	case THRS_SLEEPING:
		thread->state = THRS_SLEEPING;
					#ifdef THREAD_DEBUG
					#ifdef QUEUE_DEBUG
					printk("------------- Runnable thread queue: ");		
					thread_queue_debug_print(&runnable_threads);
					#endif
					#endif
		thread_queue_remove_item(&runnable_threads, &thread);
					#ifdef THREAD_DEBUG
					#ifdef QUEUE_DEBUG
					printk("Thread [%d] removed from runnable thread queue.", thread->id);
					thread_queue_debug_print(&runnable_threads);	
					printk("------------------------------------ \n");	
					printk("------------- Sleeping thread queue: "); 
					thread_queue_debug_print(&sleeping_threads);
					#endif
					#endif
		thread_queue_enqueue(&sleeping_threads, &thread);
					#ifdef THREAD_DEBUG
					#ifdef QUEUE_DEBUG
					printk("Thread [%d] added to sleeping thread queue.", thread->id);
					thread_queue_debug_print(&sleeping_threads);
					printk("------------------------------------ \n");						
					#endif	
					#endif
		break;
	case THRS_SUSPENDED:
		if (thread->state == THRS_SUSPENDED) {
			break;
		}			
					#ifdef THREAD_DEBUG
					#ifdef QUEUE_DEBUG
					printk("------------- Runnable thread queue: ");		
					thread_queue_debug_print(&runnable_threads);
					#endif
					#endif
		thread_queue_remove_item_by_id(&runnable_threads, thread->id);
					#ifdef THREAD_DEBUG
					#ifdef QUEUE_DEBUG
					printk("Thread [%d] removed from runnable thread queue.", thread->id);
					thread_queue_debug_print(&runnable_threads);
					printk("------------------------------------ \n");								
					#endif
					#endif
		thread->state = THRS_SUSPENDED;
		break;
		
	}
	restore_interrupts();
	return EOK;
}

////////////////////////////////////////////////////////////////////
// Funkce propagovane v hlavickovem souboru
////////////////////////////////////////////////////////////////////


thread_t thread_create_system (void (*start) (void), int status)
{
	thread_t t;
	
		/*not thread safe alocating, only for system thread*/
	t = alloc_mem_block (sizeof (struct thread_struct));
		/*check allocated space*/
	if (!t)
		panic("Not enough memory for system thread.");
		/*allocate memory for system thread stack*/
	t->kernel_stack = alloc_mem_block (KERNEL_STACK_SIZE);
		/*initialize context*/
	ctx_thread_system (t, start, status);
		/*current thread is this system thread*/
	current_thread = t;

	return t;
}



/**  
 * free memory of the thread, so remove thread from the system. Thread is not valid any more...
 */
 void thread_remove(thread_t t) {
			#ifdef THREAD_DEBUG
			printk("........................Thread [%d] removed from valid threads by Thread [%d].\n", t->id, current_thread->id);
			#endif
	thread_valid_remove_item(&valid_threads, &t);
	__thread_free_memory(t);
}

/**
 * Set state to the thread (change thread internal structure a do thread queues managements 
 * Function is now implemented correctly only for state THRS_READY
 */
void thread_set_state(thread_t t, int state){
	__thread_set_state(t, state);
}

void __thread_dprint(thread_t thr){
	#ifdef THREAD_DEBUG
	printk("============================================\n");
	printk("Thread debug print: ");
	printk("Thread id = [%d], %s\n", thr->id, ts(thr->state));
	printk("============================================\n");
	#endif
}


int thread_create (thread_t * thread_ptr, void (* thread_start) (void *), void * data){
	save_and_disable_interrupts();
						#ifdef THREAD_DEBUG
						printk("XXXXXXXXXX Starting Creating new thread... XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
						#endif
	thread_t new_thread;
	if (__thread_alloc_memory(& new_thread) == ENOMEM){
		return THREAD_INVALID;
	}
	__thread_init_context(new_thread, thread_start, data, KERNEL_THREAD_STATUS); //intit thread to a default kernel staus
	__thread_init_structures(new_thread);
	thread_set_state(new_thread, THRS_READY);
	living_threads ++;
						#ifdef THREAD_DEBUG
						printk("XXXXXXXXXX Thread created with id %d, number of living threads: %d \n XXXXXXX", new_thread->id, living_threads);
						#endif
	*thread_ptr = new_thread;
	restore_interrupts();	
	return EOK;
}



/**
 * current thread yields one times
 */

void thread_yield (void){
	save_and_disable_interrupts();
						#ifdef THREAD_DEBUG
						printk("Thread %d yielding...\n", current_thread->id);
						#endif
	schedule();
	restore_interrupts();	
}

/**
 * curent thread suspends until another thread calls wake_up
 */

void thread_suspend (void){
	save_and_disable_interrupts();
						#ifdef THREAD_DEBUG
						printk("Suspending thread %d addr: %p...\n", current_thread->id, current_thread);
						#endif
	thread_set_state(current_thread, THRS_SUSPENDED);
	schedule();
	restore_interrupts();
}


/**
 * wake up the thread
 * @param thread to wake up
 * @return EINVAL on invalid thread identifier, EOK else
 */

int thread_wakeup (thread_t thr){
	save_and_disable_interrupts();
	if (thread_is_valid(thr) == EINVAL){
					#ifdef THREAD_DEBUG
					printk("Waking up invalid thread\n");
					#endif
		restore_interrupts();
		return EINVAL;
	}
	if (thr->state == THRS_READY) {
		restore_interrupts();
					#ifdef THREAD_DEBUG
					panic("Thread %d already woken up by timer, an attempt to wake it up from Thread [%d] ignored\n", thr->id, current_thread->id);
					#endif
 		return EOK;
	}
	thr->woken_up = WOKEN_BY_ANOTHER_THREAD;
	__thread_set_state(thr, THRS_READY);
					#ifdef THREAD_DEBUG
					printk("\nThread %d wake up\n", thr->id);
					#endif
 	restore_interrupts();
	return EOK;	
}


/**
 * @return EINVAL on invalid thread id, EOK else
 */

int thread_kill (thread_t thr){
	save_and_disable_interrupts();
			#ifdef THREAD_DEBUG
			printk("Killing thread [%d]\n", thr->id);
			#endif
	if (thread_is_valid(thr) == EINVAL){
		restore_interrupts();
		return EINVAL;
	}
			#ifdef THREAD_DEBUG
			printk("The thread is valid.\n");
			#endif
	thr->finished = TRUE;
	thread_set_state(thr, THRS_ZOMBIE);
	if (thr->joined) {
			#ifdef THREAD_DEBUG
			printk("The thread has been joined in the past.\n");
			#endif	
		//thr->joined could be killend during his suspendation
		//therefore nobody will free stuctures of this thread
		if (thread_wakeup(thr->joined) == EINVAL) {
			#ifdef THREAD_DEBUG
			printk("Invalid thread to wake up after my [%d] suicide.\n", thr->id);
			printk("Adding me to zombie list.\n");
			#endif
			thread_queue_add(&zombies_to_kill_threads, &thr); //adding to threads ready to kill
			#ifdef THREAD_DEBUG
			thread_queue_debug_print(&zombies_to_kill_threads);
			#endif
		} 
		thr->joined = NULL;	
	}
	if (thr->detached) {
		#ifdef THREAD_DEBUG
		printk("Thread detached - Adding me to zombie list.\n");
		#endif
		thread_queue_add(&zombies_to_kill_threads, &thr);
	}
	if (thr == current_thread) {
		#ifdef THREAD_DEBUG
		printk("An attempt to suicide - killing me as running thread. Shedule.\n");
		#endif
		schedule();
	}
	restore_interrupts();
	return EOK;
}


/**
 * 
 */
 
int thread_detach (thread_t thr) {
	save_and_disable_interrupts();
	if ( (thread_is_valid(thr) == EINVAL) || /* thread ID invalid */
		 (thr->detached) ||	/* thread already detached */
		 (thr->state == THRS_ZOMBIE) || /* thread is not running and is waiting for join */
		 (thr->joined != NULL)) { /* somebody is waiting for us */
		 	restore_interrupts();
			return EINVAL;
	}
	thr->detached = TRUE;
	restore_interrupts();
	return EOK;
}


int thread_join (thread_t thr){
	if ( (thread_is_valid(thr) == EINVAL) || /* thread ID invalid */
		 (thr->detached) ||	/* thread already detached */
		 (thr == current_thread) || /* thread is calling join to itself */
		 (thr->joined != NULL)) { /* somebody is waiting for us */
			#ifdef THREAD_DEBUG
			printk("Tested validity of joining thread... ");
			if (thread_is_valid(thr) == EINVAL) printk("Invalid joining thread\n");
			if (thr->detached) printk("Detached thread\n");  
			if (thr == current_thread) printk("Current thread\n");
			if (thr->joined != NULL) printk("Somebody is waiting for us");
			//printk("Thread [%d], joined by %p, thread [%d]\n", thr->id, thr->joined, thr->joined->id);
			#endif
			return EINVAL;
	}
	save_and_disable_interrupts();
	#ifdef THREAD_DEBUG
			printk("Joining Thread [%d] by Thread [%d]\n", thr->id, current_thread->id);
	#endif
	
	if (thr->finished == FALSE) { //the thread is not running, is finished zombie, 
								 //there is NO NEED TO SUSPEND myself
		#ifdef THREAD_DEBUG
			printk("   Thread [%d] finished is FALSE\n", thr->id);
		#endif			
		thr->joined = current_thread;
		thread_suspend();
	}
	thread_remove(thr);		
	restore_interrupts();
	return EOK;

}

int thread_join_timeout (thread_t thr, const unsigned int usec){
	if ( (thread_is_valid(thr) == EINVAL) || /* thread ID invalid */
		 (thr->detached) ||	/* thread already detached */
		 (thr == current_thread) || /* thread is calling join to itself */
		 (thr->joined != NULL)) { /* somebody is waiting for us */
			return EINVAL;
	}
	save_and_disable_interrupts();
	
	thr->joined = current_thread;
	thread_usleep(usec);

	if (thread_is_valid(thr) == EINVAL) {
		restore_interrupts();
		return EOK;
	} else { //thread is valid
		if (thr->finished) {
			thread_queue_add(&zombies_to_kill_threads, &thr);
			restore_interrupts();
			return EOK;	
		} else { //the joined thread is still running -> timeout reached, returning ETIMEDOUT
			thr->joined = NULL;
			restore_interrupts();
			return ETIMEDOUT;
		}
	}
	//return EINVAL; //just for compilation warnings and errors
}

void thread_sleep (const unsigned int sec) {
	if (sec > 0) {
		save_and_disable_interrupts();
		current_thread->sleep_start_time = read_cp0_count();
		current_thread->sleep_length = sec * MSIM_FREQUENCY + 1;	
		thread_set_state(current_thread, THRS_SLEEPING);
		schedule();
		restore_interrupts();
	}
}


void thread_usleep (const unsigned int usec) {
	if (usec > 0) {
		save_and_disable_interrupts();
		if (usec <= 1000) {
			unsigned start_time = read_cp0_count();
			unsigned sleep_time = usec * MSIM_FREQUENCY_USEC;
			while(read_cp0_count() - start_time < sleep_time);			
		} else {
			current_thread->sleep_start_time = read_cp0_count();
			current_thread->sleep_length = usec * MSIM_FREQUENCY_USEC +1;
			thread_set_state(current_thread, THRS_SLEEPING);
			schedule();			
		}	
		restore_interrupts();
	}
}


/** fixme
 * vraci EINVAL pri invalidovi, EOK pri validnim
 */
int thread_is_valid(thread_t thr) {
	if (thread_valid_exists(&valid_threads, thr) == EOK)
	{
		return EOK;
	}
	else
	{
		return EINVAL;
	}
}
thread_t thread_get_current (void) {
	return(current_thread);	
}

/* used for testing thread_queue data structure */
/*
void thread_test(){
	thread_t thr;
	thread_t thr_pokus;
	thread_queue_t tq;
	
	thread_queue_init(&tq);
	
	//tq = (thread_queue_t*) malloc(sizeof (thread_t));
	
	__thread_alloc_memory(&thr);
	thr->state = THRS_READY;
	thr->id = 1;
	thr->sleep_length = 1;

	
	thread_queue_add(&tq, &thr);
	thread_queue_debug_print(&tq);


	
	__thread_alloc_memory(&thr);
	thr->state = THRS_READY;
	thr->id = 2;
	thr->sleep_length =9;

	
	thread_queue_enqueue(&tq, &thr);
	thread_queue_debug_print(&tq);
	
		thread_queue_rotate(&tq);
	thread_queue_debug_print(&tq);
	
	__thread_alloc_memory(&thr);
	thr->state = THRS_ZOMBIE;
	thr->id = 3;
	thr->sleep_length =	6;
		thr_pokus = thr;
	
	thread_queue_enqueue(&tq, &thr);
	thread_queue_debug_print(&tq);
}
*/
