/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Round-robin scheduler
 * @license GPL-3.0-only
 */

#include <kernel.h>
#include <sched/process.h>
#include <mem/paging.h>
#include <stdint.h>
#include <io/serial/serial.h>
#include <arch/gdt.h>

extern struct process* processes_list;
extern struct process* current_process;
extern struct process* idle_proc;

extern struct tss tss;

/*
 * scheduler_init - Choose the first process
 */
void scheduler_init()
{
    current_process = processes_list;
    DEBUG("scheduler starting with: pid=%u, name='%s', context=%p", current_process->pid, current_process->name, current_process->context);
}

/*
 * scheduler_schedule - Main scheduling routine
 * @context: CPU context of previous process
 *
 * Chooses the next process that we should run.
 * The routine is executed every SCHEDULER_QUANTUM ticks.
 *
 * Return:
 * <context> - CPU context for next process
 */
struct cpu_status* scheduler_schedule(struct cpu_status* context)
{
    if (context == NULL) {
        panic(NULL, "Scheduler called with NULL context"); 
    }

    if (current_process == NULL) {
        panic(NULL, "current_process is NULL");
    }
    
    if (current_process->context == NULL) {
        panic(NULL, "current_process->context is NULL");
    }

    current_process->context = context;

    if (current_process->status == DEAD) {
        struct process* dead_process = current_process;
        struct process* next_process = (dead_process->next != NULL) ? dead_process->next : processes_list;

        process_delete(&processes_list, dead_process);

        if (processes_list == NULL || next_process == dead_process) {
            current_process = idle_proc;
            return idle_proc->context;
        }

        current_process = next_process;
    } else if (current_process->next != NULL) {
        current_process = current_process->next;
    } else {
        current_process = processes_list;
    }

    for (;;) {
        if (current_process->status == DEAD) {
            struct process* dead_process = current_process;
            struct process* next_process = (current_process->next != NULL) ? current_process->next : processes_list;

            process_delete(&processes_list, dead_process);

            if (processes_list == NULL || next_process == dead_process) {
                current_process = idle_proc;
                return idle_proc->context;
            }

            current_process = next_process;
            continue;
        }

        current_process->status = RUNNING;
        break;
    }

    // Here, we chose next running process so we load its kernel stack & page tables
    tss.rsp0 = (uint64_t)current_process->kernel_stack;
    load_cr3(VIRT_TO_PHYS((uint64_t)current_process->root_page_table));

    return current_process->context;
}