/**
 * @file sched.c
 * @brief Implementation of simple scheduler
 * 
 * Scheduler is implemented as simple round robin (no priorities).
 * Scheduler holds all the time two queues of threads. One for runnable threads (unsorted double-linked cyclic fifo)
 * and one for to_remove_zombie threads (), that 
 * are finished, somebody join them or they were detached and can be removed from system. These
 * zombie threads are removed from system in function schedule(). Sleeping threads dont need
 * any special queue, all the thread has internal timer, and rutine called from this timer
 * wake up thread to be set as runnable. 
 * Scheduler at first try to wake up sleeping threads (check timers,so move them in queue 
 * of ready to run threads),
 * Than remove zombies, that could be removed (thread can not free memory of itself), and
 * than switch context for the first ready to run thread.
 * In system is all the time at least one runnable thread, system thread with ID == 0.
 * 
 * This file is based on Kalisto, Development Kernel copyrighted (c) to 
 * Distributed Systems Research Group MFF UK, Czech republic.
 */
 
#include "sched.h"
#include "thread.h"
#include "sys.h"
#include "debug.h"
#include "timers.h"
#include "thread.h"
#include "int.h"
#include "tqueue.h"
#include "io.h"


/**
 * Defined for debugging, allows to simly change time quantum for threads and try behavior on different
 * time slices to run.
 */
#define THR_QUANTUM_MULTIPLIER 1
/**
 * Default amount of time for a thread.
 */
#define THR_DEFAULT_QUANTUM	2026

/**
 * A list of ready-to-run threads. This serves as a round-robin queue -- the
 * thread at the head will be activated by a schedule() call.
 * Struture is organized as fifo, uses pointers prev, next of thread_t structure.
 * (cyclic fifo, uses prev next items of thread_t structure)
 * @see thread_t
 */
thread_queue_t runnable_threads;
/**
 * A list of zombies, that can be romoved from the system (were detached or joined by some
 * ohter thread).
 * thread are enqued to this thread in thread_kill() function and are removed in schedule() call.
 * (cyclic fifo, uses prev next items of thread_t structure)
 * @see thread_t
 * @see thread_kill()
 * @see __thread_wrapper()
 */
thread_queue_t zombies_to_kill_threads;
/**
 * A list of all valid threads int the system
 * thread are enqued to it by thread_create() and remoded by thread_remove()
 * (cyclic fifo, uses vprev vnext items of thread_t structure)
 * @see thread_t
 * @see thread_create()
 * @see thread
 */
thread_queue_t valid_threads;

thread_queue_t sleeping_threads;

/**
 * Init the scheduler structures (all the queues).
 * Insert first thread into runnable, then set next timer interrupt to start scheduling.
 * @param t - first, system thread to be enqueud into runnable list structure.
 */
void init_scheduler ( thread_t t){
		/* Init thread queues used by scheduler */
	thread_queue_init(&runnable_threads);
	thread_queue_init(&sleeping_threads);
	thread_queue_init(&zombies_to_kill_threads);
	thread_queue_init(&valid_threads);

	thread_set_state(t, THRS_READY);
	/* fixme make beter and nicer with timers */
	write_cp0_compare (read_cp0_count () + THR_QUANTUM_MULTIPLIER * THR_DEFAULT_QUANTUM);
}

/**
 * Switch procesor context from the current thread to context of thread t
 * @param t - thread to be switched context to
 */
void context_switch (struct thread_struct *t)
{
	thread_t ct = current_thread;
	current_thread = t;
	switch_cpu_context (
		(void **) &ct->kernel_stack_top,
		t->kernel_stack_top);
}



/**
 * @brief Scheduling function
 * Function wake up runnable threads(by timer checking), remove from system zombies, that
 * could be removed, choose next thread from the ready to run list
 * and switch context to this thread.
 * Note: In the system is still at least one runnable thread (system thread).
 * Note: zombie can not be removed (freed()), when this schedule rutine
 * is runninin its context.
 */
void schedule(void)
{						
					#ifdef SCHEDULER_DEBUG
					printk("XXXXX scheduer [%d]\n", read_cp0_count());
				    printk("Living threads = %d \n", living_threads);
				    printk("Current thread: Thread [%d]\n", current_thread->id);
				    #endif
	static thread_t tmp_thread;

	unsigned int cur_time;
	timer_check();		//unleash sleeping threads if any and is their time

	if (thread_queue_empty(&runnable_threads) == ENOTEMPTY)
		thread_queue_rotate(&runnable_threads, GENERAL);
	
					#ifdef SCHEDULER_DEBUG
					if (living_threads <= 2) {
					printk("Sleeping threads: ");
					thread_queue_debug_print(&sleeping_threads, GENERAL);
					printk("Runnable threads? ");
					thread_queue_debug_print(&runnable_threads, GENERAL);
					printk("Zombies to kill? ");
					thread_queue_debug_print(&zombies_to_kill_threads, GENERAL);
					}
					#endif
		

	while(1) {
		cur_time = read_cp0_count();
			/* while any sleeping thread should be woken up */
					#ifdef SCHEDULER_DEBUG
					#ifdef QUEUE_DEBUG
					printk("Sleeping threads: ");
					thread_queue_debug_print(&sleeping_threads, GENERAL);
					printk("Runnable threads? ");
					thread_queue_debug_print(&runnable_threads, GENERAL);
					printk("Zombies to kill? ");
					thread_queue_debug_print(&zombies_to_kill_threads, GENERAL);
					#endif
					if (thread_queue_empty(&runnable_threads)==EEMPTY)
						___halt();
					#endif
		while (thread_queue_fetch(&zombies_to_kill_threads, &tmp_thread, GENERAL) == EOK) {
					#ifdef SCHEDULER_DEBUG
					printk("Definitelly killing zombie thread [%d]\n", tmp_thread->id);
					printk("Current thread is %d\n", current_thread->id);
					#endif
			if (current_thread != tmp_thread) {
					#ifdef SCHEDULER_DEBUG
					printk("Removing Thread [%d], currently runnig is Thread [%d]", tmp_thread->id, current_thread->id);
					#endif
				thread_remove(tmp_thread);
			}
//			else	//I shoud add fetched thread from zombie list again to zombie
//				thread_queue_add(&zombies_to_kill_threads, &tmp_thread);
		}
		// is there a thread to schedule?
		if (thread_queue_empty(&runnable_threads) != EEMPTY) {
			break;
		}

		// if no life thread left, panic, that can never happen
		if (living_threads == 0)
			panic("Can not schedule. No living thread. Where is system thread?.\n");

			//scheduling = 1;
		enable_interrupts();
			/* now timer interrupt can occur to increase clock time and then to wake up sleeping thread,
			   or keybord interrupt can occur to wake up thread which is suspended in getc() */
		disable_interrupts();
			//scheduling = 0;
	}
	// set next quantum
	/* fixme, beter, timers ! */
	write_cp0_compare (read_cp0_count () +  THR_QUANTUM_MULTIPLIER * THR_DEFAULT_QUANTUM);
	
	//if current thread is going to schedule again, return (no context switching is necessary
					#ifdef SCHEDULER_DEBUG
					printk("--- end of scheduler\n", read_cp0_count());
				    #endif

	if (current_thread == runnable_threads) {
		return;
	}
	//switch context to next choosen thread.
	context_switch (runnable_threads);
}

