#include "kernel.h"
#include "task.h"
#include "intmgr.h"
#include "screen.h"
#include "gdt.h"

TaskScheduler SystemScheduler;
TCB* TaskScheduler::CurrentTcb;

extern "C" void task_dispatch(TCB* tcb);

void TaskEntryPoint(Task* task) {
    task->run();
    while(true) {
        __asm__ __volatile__("hlt");
    }
}

//-------------------------------------------------------------------
// Task Class Method
//-------------------------------------------------------------------
Task::Task(size_t stack_size) :
    StackSize(stack_size), Priority(NORMAL_PRIORITY), 
    SubPriority(TaskPriority(2)), isStackInMemPool(true) {
    Stack = (uint8*)MemoryManager.alloc(StackSize);
    *((uint32*)&Stack[StackSize - 8]) = 0; // return address(dummy)
    *((uint32*)&Stack[StackSize - 4]) = (uint32)this;
    Tcb.Context = (TaskContext*)&Stack[StackSize - sizeof(TaskContext) - 8];
    Tcb.Context->eflags = 0x0202;
    Tcb.Context->cs  = Gdt::KERNEL_CS * sizeof(GdtEntry);
    Tcb.Context->eip = (uint32)TaskEntryPoint;
    Tcb.Context->ds  = Gdt::KERNEL_DS * sizeof(GdtEntry);
    Tcb.Context->es  = Tcb.Context->ds;
    Tcb.StackSegment = Tcb.Context->ds;
    Status = WAIT;
}

//-------------------------------------------------------------------
// TaskScheduler Class Method
//-------------------------------------------------------------------
void TaskScheduler::dispatch(bool dispatch_immd) {
    Task* next_task;
    if (((next_task = (Task*)ReadyQueue.dequeue()) != 0) &&
        (CurrentTask->getPriority() <= next_task->getPriority())) {
        addReadyQueue(*CurrentTask);
        CurrentTask = next_task;
        CurrentTask->setStatus(Task::RUNNING);
        TimeSlice = NORMAL_TIME_SLICE;
        if (dispatch_immd) {
            task_dispatch(&CurrentTask->getTcb());
        } else {
            CurrentTcb = &CurrentTask->getTcb();
        }
    }
}

void TaskScheduler::init(Timer& switch_timer, Task& cur_task) {
    CurrentTask = &cur_task;
    CurrentTcb  = &cur_task.getTcb();
    CurrentTask->setStatus(Task::RUNNING);
    TimeSlice = NORMAL_TIME_SLICE;
    SwitchTimer = &switch_timer;
    SwitchTimer->add(*this);
}

void TaskScheduler::sleep(Task& task, time_t duration) {
    InterruptManager.disable();
    if (CurrentTask == &task) {
       Task* next_task;
       if (((next_task = (Task*)ReadyQueue.dequeue()) != 0) &&
            (CurrentTask->getPriority() <= next_task->getPriority())) {
            CurrentTask->setStatus(Task::WAIT);
            CurrentTask = next_task;
            CurrentTask->setStatus(Task::RUNNING);
            TimeSlice = NORMAL_TIME_SLICE;
            task_dispatch(&CurrentTask->getTcb());
        }
    } else if (task.getStatus() == Task::READY) {
        ReadyQueue.remove(task);
        task.setStatus(Task::WAIT);
    }
    InterruptManager.enable();
}

void TaskScheduler::wakeup(Task& task) {
    if (task.getStatus() == Task::WAIT) addReadyQueue(task);
}

TCB* TaskScheduler::getCurrentTcb(void) {
    return &CurrentTask->getTcb();
}

