/*-
 * Copyright (c) 2002-2018 The UbixOS Project.
 * All rights reserved.
 *
 * This was developed by Christopher W. Olsen for the UbixOS Project.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted
 * provided that the following conditions are met:
 *
 * 1) Redistributions of source code must retain the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors.
 * 2) Redistributions in binary form must reproduce the above copyright notice, this list of
 *    conditions, the following disclaimer and the list of authors in the documentation and/or
 *    other materials provided with the distribution.
 * 3) Neither the name of the UbixOS Project nor the names of its contributors may be used to
 *    endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <ubixos/sched.h>
#include <ubixos/kpanic.h>
#include <ubixos/spinlock.h>
#include <ubixos/endtask.h>
#include <vfs/mount.h>
#include <lib/kmalloc.h>
#include <lib/kprintf.h>
#include <vmm/vmm.h>
#include <sys/gdt.h>
#include <sys/idt.h>
#include <isa/8259.h>
#include <string.h>
#include <assert.h>
#include <sys/descrip.h>

#include <ubixos/spinlock.h>

static kTask_t *taskList = 0x0;
static kTask_t *delList = 0x0;
static uInt32 nextID = -1;

kTask_t *_current = 0x0;
kTask_t *_usedMath = 0x0;

static struct spinLock schedulerSpinLock = SPIN_LOCK_INITIALIZER;

/************************************************************************

 Function: int sched_init()

 Description: This function is used to enable the kernel scheduler

 Notes:

 02/20/2004 - Approved for quality

 ************************************************************************/

int sched_init() {
  taskList = (kTask_t *) kmalloc(sizeof(kTask_t));
  if (taskList == 0x0)
    kpanic("Unable to create task list");

  taskList->id = nextID++;

  /* Print out information on scheduler */
  kprintf("sched0 - Address: [0x%X]\n", taskList);

  /* Return so we know everything went well */
  return (0x0);
}

void sched() {
  uInt32 memAddr = 0x0;
  kTask_t *tmpTask = 0x0;
  kTask_t *delTask = 0x0;

  if (!spinTryLock(&schedulerSpinLock))
    return;

  tmpTask = _current->next;
  //outportByte(0xE9,_current->id + '0');
  schedStart:

  /* Yield the next task from the current prio queue */
  for (; tmpTask != 0x0; tmpTask = tmpTask->next) {
    if (tmpTask->state > 0x0) {
      _current = tmpTask;
      if (_current->state == FORK)
        _current->state = READY;
      break;
    }
    else if (tmpTask->state == DEAD) {
      delTask = tmpTask;
      tmpTask = tmpTask->next;
      sched_deleteTask(delTask->id);
      sched_addDelTask(delTask);
      goto schedStart;
    }
  }

  /* Finished all the tasks, restarting the list */
  if (0x0 == tmpTask) {
    tmpTask = taskList;
    goto schedStart;
  }

  if (_current->state > 0x0) {
    if (_current->oInfo.v86Task == 0x1)
      irqDisable(0x0);
    asm("cli");
    memAddr = (uInt32) &(_current->tss);
    ubixGDT[4].descriptor.baseLow = (memAddr & 0xFFFF);
    ubixGDT[4].descriptor.baseMed = ((memAddr >> 16) & 0xFF);
    ubixGDT[4].descriptor.baseHigh = (memAddr >> 24);
    ubixGDT[4].descriptor.access = '\x89';
    spinUnlock(&schedulerSpinLock);
    asm("sti");
    asm("ljmp $0x20,$0\n");
  }
  else {
    spinUnlock(&schedulerSpinLock);
  }

  return;
}

kTask_t *schedNewTask() {
  int i = 0;
  kTask_t *tmpTask = (kTask_t *) kmalloc(sizeof(kTask_t));
  struct file *fp = 0x0;
  if (tmpTask == 0x0)
    kpanic("Error: schedNewTask() - kmalloc failed trying to initialize a new task struct\n");

  memset(tmpTask, 0x0, sizeof(kTask_t));
  /* Filling in tasks attrs */
  tmpTask->usedMath = 0x0;
  tmpTask->state = NEW;

  /* HACK */
  for (i = 0; i < 3; i++) {
    fp = (void *) kmalloc(sizeof(struct file));
    tmpTask->td.o_files[i] = (uint32_t) fp;
    fp->f_flag = 0x4;
  }

  spinLock(&schedulerSpinLock);
  tmpTask->id = nextID++;
  tmpTask->next = taskList;
  tmpTask->prev = 0x0;
  taskList->prev = tmpTask;
  taskList = tmpTask;

  spinUnlock(&schedulerSpinLock);

  return (tmpTask);
}

int sched_deleteTask(pidType id) {
  kTask_t *tmpTask = 0x0;

  /* Checking each task from the prio queue */
  for (tmpTask = taskList; tmpTask != 0x0; tmpTask = tmpTask->next) {
    if (tmpTask->id == id) {
      if (tmpTask->prev != 0x0)
        tmpTask->prev->next = tmpTask->next;
      if (tmpTask->next != 0x0)
        tmpTask->next->prev = tmpTask->prev;
      if (taskList == tmpTask)
        taskList = tmpTask->next;

      return (0x0);
    }
  }
  return (0x1);
}

int sched_addDelTask(kTask_t *tmpTask) {
  tmpTask->next = delList;
  tmpTask->prev = 0x0;
  if (delList != 0x0)
    delList->prev = tmpTask;
  delList = tmpTask;
  return (0x0);
}

kTask_t *sched_getDelTask() {
  kTask_t *tmpTask = 0x0;

  if (delList == 0x0)
    return (0x0);

  tmpTask = delList;
  delList = delList->next;
  return (tmpTask);
}

kTask_t *
schedFindTask(uInt32 id) {
  kTask_t *tmpTask = 0x0;

  for (tmpTask = taskList; tmpTask; tmpTask = tmpTask->next) {
    if (tmpTask->id == id)
      return (tmpTask);
  }

  return (0x0);
}

/************************************************************************

 Function: void schedEndTask()

 Description: This function will end a task

 Notes:

 02/20/2004 - Approved for quality

 ************************************************************************/
void schedEndTask(pidType pid) {
  endTask(_current->id);
  sched_yield();
}

/************************************************************************

 Function: int schedEndTask()

 Description: This function will yield a task

 Notes:

 02/20/2004 - Approved for quality

 ************************************************************************/

void sched_yield() {
  sched();
}

/*
 asm(
 ".globl sched_yield \n"
 "sched_yield:       \n"
 "  cli              \n"
 "  call sched       \n"
 );
 */

/************************************************************************

 Function: int sched_setStatus(pidType pid,tState state)

 Description: Change the tasks status

 Notes:

 ************************************************************************/
int sched_setStatus(pidType pid, tState state) {
  kTask_t *tmpTask = schedFindTask(pid);
  if (tmpTask == 0x0)
    return (0x1);
  tmpTask->state = state;
  return (0x0);
}

/***
 END
 ***/

