#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "scheduler.h"
#include "mem_alloc.h"

/*
 * This file contains a bare bones skeleton for the scheduler function
 * for the second assignment for the OSN course of the 2005 fall semester.
 *
 * G.D. van Albada
 * Section Computational Science
 * Universiteit van Amsterdam
 * September 29, 2005
 *
 * Student name .... Sander van Veen & Taddeus Kroes
 * Student email ... sandervv@gmail.com & taddeuskroes@hotmail.com
 * Collegekaart .... 6167969 & 6054129
 * Date ............ 10.10.2010
 */

#define FAIRNESS_THRESHOLD 3

static double timeslice = 100;

/*
 * This variable will simulate the allocatable memory
 */
static long memory[MEM_SIZE];

/*
 * Round Robin CPU scheduler
 */
static void CPU_scheduler_RR()
{
    if( !ready_proc )
        return;

    set_slice(timeslice);
    enqueue_back(&ready_proc, dequeue(&ready_proc));
}

/*
 * Longest job first CPU scheduler
 */
static void CPU_scheduler_LJF()
{
    if( !ready_proc )
        return;

    pcb *proc, *proc_max;

    // Find the longest job and put it at the front of the queue
    for( proc = proc_max = ready_proc; proc; proc = proc->next )
    {
        if( proc->MEM_need > proc_max->MEM_need )
            proc_max = proc;
    }

    set_slice(timeslice);
    enqueue_front(&ready_proc, remove_from_queue(&ready_proc, proc_max));
}

/*
 * First come, first serve CPU scheduler
 */
static void CPU_scheduler_FCFS()
{
    // This function is implemented by the simulator.
}

/*
 * The high-level memory allocation scheduler is implemented here
 */
static void GiveMemory_FCFFS()
{
    int index;
    pcb *proc1, *proc2;

    for( proc2 = new_proc; proc2; proc2 = proc2->next )
    {
        // Search for a new process that should be given memory.
        if( (index = mem_get(proc2->MEM_need)) >= 0 )
        {
            // Allocation succeeded, now put in administration
            proc2->MEM_base = index;

            // You might want to move this process to the ready
            //queue now
            proc1 = proc2->next;
            enqueue_back(&ready_proc, remove_from_queue(&new_proc, proc2));

            if( !(proc2 = proc1) )
                break;
        }
    }
}

/*
 * First come, first serve high-level scheduler
 */
static void GiveMemory_FCFS()
{
    int index;

    // Assign all available memory to the first processes in the queue
    while( new_proc && (index = mem_get(new_proc->MEM_need)) >= 0 )
    {
        new_proc->MEM_base = index;
        enqueue_back(&ready_proc, dequeue(&new_proc));
    }
}

/*
 * Shortest job first high-level scheduler
 */
static void GiveMemory_SJF()
{
    int index;

    pcb *proc, *proc_min;

    for( ;; )
    {
        // Find the shortest process
        for( proc = proc_min = new_proc; proc; proc = proc->next )
        {
            if( proc->MEM_need < proc_min->MEM_need )
                proc_min = proc;
        }

        // Assign memory or if no memory is available, break loop
        if( proc_min && (index = mem_get(proc_min->MEM_need)) >= 0 )
        {
            proc_min->MEM_base = index;
            enqueue_back(&ready_proc, remove_from_queue(&new_proc, proc_min));
        }
        else
            break;
    }
}

/*
 * Shortest job first high-level scheduler with fairness threshold
 */
static void GiveMemory_SJF_fairness()
{
    int index, thres_max = 0;

    pcb *proc, *proc_min, *proc_long;

    for( ;; )
    {
        proc_long = NULL;

        // Find the shortest process and the process with the
        // highest fairness threshold value
        for( proc = proc_min = new_proc; proc; proc = proc->next )
        {
            if( proc->MEM_need < proc_min->MEM_need )
                proc_min = proc;

            if( proc->your_admin && *((int *)proc->your_admin) > thres_max )
            {
                thres_max = *((int *)proc->your_admin);
                proc_long = proc;
            }
        }

        // Shortest process gives precedence to a process
        // which exceeds the fairness threshold
        if( proc_long && thres_max >= FAIRNESS_THRESHOLD )
            proc = proc_long;
        else
            proc = proc_min;

        // The queue is empty
        if( !proc )
            break;

        // If memory is available, assign it to the process.
        // Otherwise, increment its fairness value
        if( (index = mem_get(proc->MEM_need)) >= 0 )
        {
            proc->MEM_base = index;
            enqueue_back(&ready_proc, remove_from_queue(&new_proc, proc));
        }
        else if( proc->your_admin )
        {
            (*((int *) proc->your_admin))++;
            break;
        }
        else
        {
            proc->your_admin = (int *) malloc(sizeof(int));
            *((int *) proc->your_admin) = 1;
            break;
        }
    }
}

/*
 * Here we reclaim the memory of a process after it has finished
 */
static void ReclaimMemory()
{
    pcb *proc;

    proc = defunct_proc;
    while( proc )
    {
        //Free your own administrative structure if it exists
        if( proc->your_admin )
        {
            free(proc->your_admin);
        }

        // Free the simulated allocated memory
        mem_free(proc->MEM_base);
        proc->MEM_base = -1;

        // Call the function that cleans up the simulated process
        rm_process(&proc);

        // See if there are more processes to be removed
        proc = defunct_proc;
    }
}

/*
 * You may want to have the last word...
 */
static void my_finale()
{
    // Your very own code goes here
    // Less is more :-)
}

/*
 * The main scheduling routine
 */
void schedule(event_type event)
{
    static int first = 1;
    static void (* hl_handler)(void) ;
    static void (* cpu_handler)(void) ;

    if( first )
    {
        mem_init(memory);
        finale = my_finale;
        first = 0;

        int scheduler_type = 0;

        printf("Kies een high-level scheduler:\n"
                " 1. FCFS\n"
                " 2. SJF\n"
                " 3. FCFFS\n"
                " 4. SJF (met fairness)\n"
                "Keuze: "
              );

        if( !scanf("%d", &scheduler_type) )
            exit(EXIT_FAILURE);

        switch( scheduler_type )
        {
            case 1: hl_handler = &GiveMemory_FCFS; break;
            case 2: hl_handler = &GiveMemory_SJF; break;
            case 3: hl_handler = &GiveMemory_FCFFS; break;
            case 4: hl_handler = &GiveMemory_SJF_fairness; break;
            default:
                    fprintf(stderr,
                            "Ongeldige invoer (alleen 1 t/m 4 toegestaan).\n");
                    exit(EXIT_FAILURE);
                    break;
        }

        printf("\n");

        int cpu_type = 0;

        printf("Kies een cpu scheduler:\n"
                " 0. FCFS (standaard)\n"
                " 1. Round robin\n"
                " 2. LJF\n"
                "Keuze: "
              );

        if( !scanf("%d", &cpu_type) )
            exit(EXIT_FAILURE);

        switch( cpu_type )
        {
            case 0: cpu_handler = &CPU_scheduler_FCFS; break;
            case 1: cpu_handler = &CPU_scheduler_RR; break;
            case 2: cpu_handler = &CPU_scheduler_LJF; break;
            default:
                    fprintf(stderr,
                            "Ongeldige invoer (alleen 0 t/m 2 toegestaan).\n");
                    exit(EXIT_FAILURE);
                    break;
        }

        printf("\n");

        printf("Geef lengte time-slice: ");

        if( !scanf("%lf", &timeslice) || timeslice < 1.0 )
            timeslice = 1.0;

        printf("\n");
    }

    switch( event )
    {
        // You may want to do this differently
        case NewProcess_event:
            (*hl_handler)();
            break;
        case Time_event:
        case IO_event:
            (*cpu_handler)();
            break;
        case Ready_event:

            break;
        case Finish_event:
            ReclaimMemory();
            (*hl_handler)();
            (*cpu_handler)();
            break;
        default:
            printf("I cannot handle event nr. %d\n", event);
            break;
    }
}

/*
 * Enqueue event at the front of the queue
 */
void enqueue_front(pcb **queue, pcb *elem)
{
    // Attach the (non-empty) queue to the new item
    if( *queue )
    {
        elem->next = *queue;
        (*queue)->prev = elem;
    }

    // Put the new element at the front of the queue
    *queue = elem;
}

/*
 * Enqueue an event at the back of a queue
 */
void enqueue_back(pcb **queue, pcb *elem)
{
    pcb *last;

    // Check if the queue is empty
    if( !(*queue) )
    {
        *queue = elem;
        return;
    }

    // Walk through the queue until the last queued item is reached
    for( last = *queue; last->next; last = last->next );

    // Attach the element to the last item
    last->next = elem;
    elem->prev = last;
}

/*
 * Remove and return an event from any position in a queue
 */
pcb *remove_from_queue(pcb **queue, pcb *elem)
{
    // Remove from front of queue.
    if( *queue == elem )
        *queue = elem->next;

    if( elem->next )
        elem->next->prev = elem->prev;

    if( elem->prev )
        elem->prev->next = elem->next;

    elem->next = elem->prev = NULL;

    return elem;
}

/*
 * Dequeue and return an event
 */
pcb *dequeue(pcb **queue)
{
    return remove_from_queue(queue, *queue);
}
