#include "dolphin/PPCArch.h"
#include "dolphin/os/OSPriv.h"

static struct OSAlarmQueue {
  OSAlarm* head;
  OSAlarm* tail;
} AlarmQueue;

static void DecrementerExceptionHandler(__OSException exception, OSContext* context);
static BOOL OnReset(BOOL final);

void OSInitAlarm(void) {
  if (__OSGetExceptionHandler(8) != DecrementerExceptionHandler) {
    AlarmQueue.head = AlarmQueue.tail = NULL;
    __OSSetExceptionHandler(8, DecrementerExceptionHandler);
  }
}

void OSCreateAlarm(OSAlarm* alarm) {
  alarm->handler = 0;
  alarm->tag = 0;
}

static void SetTimer(OSAlarm* alarm) {
  OSTime delta;

  delta = alarm->fire - __OSGetSystemTime();
  if (delta < 0) {
    PPCMtdec(0);
  } else if (delta < 0x80000000) {
    PPCMtdec((u32)delta);
  } else {
    PPCMtdec(0x7fffffff);
  }
}

static void InsertAlarm(OSAlarm* alarm, OSTime fire, OSAlarmHandler handler) {
  OSAlarm* next;
  OSAlarm* prev;

  if (0 < alarm->period) {
    OSTime time = __OSGetSystemTime();

    fire = alarm->start;
    if (alarm->start < time) {
      fire += alarm->period * ((time - alarm->start) / alarm->period + 1);
    }
  }

  alarm->handler = handler;
  alarm->fire = fire;

  for (next = AlarmQueue.head; next; next = next->next) {
    if (next->fire <= fire) {
      continue;
    }

    alarm->prev = next->prev;
    next->prev = alarm;
    alarm->next = next;
    prev = alarm->prev;
    if (prev) {
      prev->next = alarm;
    } else {
      AlarmQueue.head = alarm;
      SetTimer(alarm);
    }
    return;
  }
  alarm->next = 0;
  prev = AlarmQueue.tail;
  AlarmQueue.tail = alarm;
  alarm->prev = prev;
  if (prev) {
    prev->next = alarm;
  } else {
    AlarmQueue.head = AlarmQueue.tail = alarm;
    SetTimer(alarm);
  }
}

void OSSetAlarm(OSAlarm* alarm, OSTime tick, OSAlarmHandler handler) {
  BOOL enabled;
  enabled = OSDisableInterrupts();
  alarm->period = 0;
  InsertAlarm(alarm, __OSGetSystemTime() + tick, handler);
  OSRestoreInterrupts(enabled);
}

void OSSetPeriodicAlarm(OSAlarm* alarm, OSTime start, OSTime period, OSAlarmHandler handler) {
  BOOL enabled;
  enabled = OSDisableInterrupts();
  alarm->period = period;
  alarm->start = __OSTimeToSystemTime(start);
  InsertAlarm(alarm, 0, handler);
  OSRestoreInterrupts(enabled);
}

void OSCancelAlarm(OSAlarm* alarm) {
  OSAlarm* next;
  BOOL enabled;

  enabled = OSDisableInterrupts();

  if (alarm->handler == 0) {
    OSRestoreInterrupts(enabled);
    return;
  }

  next = alarm->next;
  if (next == 0) {
    AlarmQueue.tail = alarm->prev;
  } else {
    next->prev = alarm->prev;
  }
  if (alarm->prev) {
    alarm->prev->next = next;
  } else {
    AlarmQueue.head = next;
    if (next) {
      SetTimer(next);
    }
  }
  alarm->handler = 0;

  OSRestoreInterrupts(enabled);
}

static void DecrementerExceptionCallback(register __OSException exception,
                                         register OSContext* context) {
  OSAlarm* alarm;
  OSAlarm* next;
  OSAlarmHandler handler;
  OSTime time;
  OSContext exceptionContext;
  time = __OSGetSystemTime();
  alarm = AlarmQueue.head;
  if (alarm == 0) {
    OSLoadContext(context);
  }

  if (time < alarm->fire) {
    SetTimer(alarm);
    OSLoadContext(context);
  }

  next = alarm->next;
  AlarmQueue.head = next;
  if (next == 0) {
    AlarmQueue.tail = 0;
  } else {
    next->prev = 0;
  }

  handler = alarm->handler;
  alarm->handler = 0;
  if (0 < alarm->period) {
    InsertAlarm(alarm, 0, handler);
  }

  if (AlarmQueue.head) {
    SetTimer(AlarmQueue.head);
  }

  OSDisableScheduler();
  OSClearContext(&exceptionContext);
  OSSetCurrentContext(&exceptionContext);
  handler(alarm, context);
  OSClearContext(&exceptionContext);
  OSSetCurrentContext(context);
  OSEnableScheduler();
  __OSReschedule();
  OSLoadContext(context);
}

static asm void DecrementerExceptionHandler(register __OSException exception,
                                            register OSContext* context) {

  nofralloc 
  OS_EXCEPTION_SAVE_GPRS(context)
  stwu r1, -8(r1)
  b DecrementerExceptionCallback
}
