#include <stdio.h>
#include <stdlib.h>
#include <stdbool.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
*/

/* This variable will simulate the allocatable memory */

static long memory[MEM_SIZE];

/* Data used by the scheduler */
struct sched_data
{
    unsigned char priority;
};

static struct student_pcb *last_scheduled_process = NULL;

bool queue_contains(struct student_pcb* queue_head, struct student_pcb *proc)
{
    if (!queue_head) return false;
    do {
        if (queue_head == proc)
            return true;
    } while ((queue_head = queue_head->next));

    return false;
}

struct sched_data* get_or_create_userdata(struct student_pcb* proc)
{
    if (proc->userdata) return proc->userdata;

    proc->userdata = calloc(1, sizeof(struct sched_data));
    return proc->userdata;
}

/* The actual CPU scheduler is implemented here */
static void cpu_scheduler()
{
    /* As a priority-based scheduler, we implement a variant of the
     * multilevel feedback queue described at
     * https://en.wikipedia.org/wiki/Multilevel_feedback_queue
     *
     * Differences are mostly in intra-queue ordering, and there are
     * potentially small differences when it comes to priority reduction.
     */
    struct student_pcb *proc = ready_proc;

    /* If there are no processes to schedule, don't schedule any. */
    if (!proc) return;

    struct sched_data* userdata;

    /* If we've scheduled a process in the past, check whether it is finished.
     * If not, reduce its priority. */
    if (last_scheduled_process)
    {
        if (queue_contains(ready_proc, last_scheduled_process))
        {
            userdata = get_or_create_userdata(last_scheduled_process);
            if (userdata->priority < 255)
                userdata->priority++;
        }

        if (queue_contains(io_proc, last_scheduled_process))
        {
            userdata = get_or_create_userdata(last_scheduled_process);
            // Promote IO-bound processes.
            if (userdata->priority > 0)
                userdata->priority--;
        }
    }

    int max_priority = 255;
    struct student_pcb *max_priority_process;

    do
    {
        userdata = get_or_create_userdata(proc);
        if (userdata->priority < max_priority)
        {
            max_priority = userdata->priority;
            max_priority_process = proc;
        }
    } while ((proc = proc->next));

    /* This remove-and-prepend behavior automatically causes queue rotation. */
    queue_remove(&ready_proc, max_priority_process);
    queue_prepend(&ready_proc, max_priority_process);

    set_slice(pow(2, max_priority));

    last_scheduled_process = max_priority_process;
}

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

/* Defined in simul2018.c */
extern int N_TRY;

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

    bool try_counter_started = false;
    int tries_left = N_TRY;

    if (!proc->userdata)
    {
        proc->userdata = calloc(1, sizeof (struct sched_data));
    }

    while ((proc = proc->next))
    {
        index = mem_get(proc->mem_need);

        if (index >= 0)
        {
            // Allocation succeeded. Set the base address, and put the
            // process in the ready queue.
            proc->mem_base = index;

            queue_remove(&new_proc, proc);
            queue_append(&ready_proc, proc);
        } else {
            // Allocation failed. Try N_try more processes before exiting.
            try_counter_started = true;
        }

        if (try_counter_started && tries_left--)
            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;
    }
}

/* You may want to have the last word... */

static void my_finale() { /* Your very own code goes here */ }

/* The main scheduling routine */

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

    if (first) {
        mem_init(memory);
        finale = my_finale;
        first = 0;
        /* Add your own initialisation code here
         */
    }

    switch (event) {
    /* You may want to do this differently
     */
    case NEW_PROCESS_EVENT:
        give_memory();
        break;
    case TIME_EVENT:
    case IO_EVENT:
        cpu_scheduler();
        break;
    case READY_EVENT:
        break;
    case FINISH_EVENT:
        reclaim_memory();
        give_memory();
        cpu_scheduler();
        break;
    default:
        printf("I cannot handle event nr. %d\n", event);
        break;
    }
}
