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

#include "schedule.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.
   Author:

   G.D. van Albada
   Section Computational Science
   Universiteit van Amsterdam

   Date:
   October 23, 2003
   Modified:
   September 29, 2005
*/

#define N_TRY 5
#define SLICE 10

/* This variable will simulate the allocatable memory */

static long memory[MEM_SIZE];

/* Struct with user data*/

typedef struct u_data {
    int priority;
} u_data;

/* Retrieves the first proces with a given or higher priority
   from the ready queue */

student_pcb* get_prio(int priority) {
    student_pcb *proc;

    proc = ready_proc;
    if (!proc) {
        return proc;
    }
    while (((u_data *)proc->userdata)->priority > priority) {
        proc = proc->next;
    }
    return proc;
}

/* Possibly moves the last proces to another queue
   based on the recieved event*/

void handle_last(event_type event) {
    student_pcb *last_proc = queue_last(&ready_proc);
    int last_prio = ((u_data *)last_proc->userdata)->priority;

    switch (event) {
    case TIME_EVENT:
        if (last_prio < 5) {
            ((u_data *)last_proc->userdata)->priority += 1;
        }
        break;
    case IO_EVENT:
        if (last_prio > 0) {
            ((u_data *)last_proc->userdata)->priority -= 1;
        }
        break;
    default:
        break;
    }
}

/* The actual CPU scheduler is implemented here */

static void cpu_scheduler(event_type event) {
    static int prio_offset = 0;
    student_pcb *proc;
    int proc_prio;

    proc = get_prio(prio_offset);
    if (!proc && prio_offset < 5) {
        prio_offset += 1;
        cpu_scheduler(event);
        return;
    } else if (!proc && prio_offset == 5) {
        return;
    }
    proc_prio = ((u_data *)proc->userdata)->priority;

    handle_last(event);
    queue_remove(&ready_proc, proc);
    set_slice(SLICE + pow(2, proc_prio));
    queue_append(&ready_proc, proc);
}

/* The high-level memory allocation scheduler is implemented here */

static void give_memory() {
    int index;
    student_pcb *proc;

    proc = new_proc;
    for (int i = 0; i < N_TRY; i++) {
        if (proc) {
            index = mem_get(proc->mem_need);
            if (index >= 0) {
                proc->mem_base = index;
                u_data *ud = malloc(sizeof(u_data));
                if (!ud) {
                    exit(1);
                }
                proc->userdata = ud;
                ((u_data *)proc->userdata)->priority = 0;
                queue_remove(&new_proc, proc);
                queue_append(&ready_proc, proc);
                break;
            } else {
                proc = proc->next;
            }
        } else {
            break;
        }
    }
}

/* Here we reclaim the memory of a process after it
  has finished */

static void reclaim_memory() {
    student_pcb *proc;

    proc = defunct_proc;
    while (proc) {
        /* Free your own administrative structure if it exists
         */
        if (proc->userdata) {
            free(proc->userdata);
        }
        /* 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;
    }
}

/* The main scheduling routine */

void schedule(event_type event) {
    static int first = 1;

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

    switch (event) {
    case NEW_PROCESS_EVENT:
        give_memory();
        break;
    case TIME_EVENT:
    case IO_EVENT:
        cpu_scheduler(event);
        break;
    case READY_EVENT:
        break;
    case FINISH_EVENT:
        reclaim_memory();
        give_memory();
        cpu_scheduler(event);
        break;
    default:
        printf("I cannot handle event nr. %d\n", event);
        break;
    }
}
