#include "dolphin/arq.h"
#include "dolphin/os.h"

const char* __ARQVersion = "<< Dolphin SDK - ARQ\trelease build: Sep  5 2002 05:34:29 (0x2301) >>";
static ARQRequest* __ARQRequestQueueHi;
static ARQRequest* __ARQRequestTailHi;
static ARQRequest* __ARQRequestQueueLo;
static ARQRequest* __ARQRequestTailLo;
static ARQRequest* __ARQRequestPendingHi;
static ARQRequest* __ARQRequestPendingLo;
static ARQCallback __ARQCallbackHi;
static ARQCallback __ARQCallbackLo;
static u32 __ARQChunkSize;

static volatile BOOL __ARQ_init_flag = FALSE;

void __ARQPopTaskQueueHi(void);
void __ARQServiceQueueLo(void);
void __ARQCallbackHack(void);
void __ARQInterruptServiceRoutine(void);
void __ARQInitTempQueue(void);
void __ARQPushTempQueue(ARQRequest* task);

void __ARQPopTaskQueueHi(void) {

  if (__ARQRequestQueueHi) {
    if (__ARQRequestQueueHi->type == ARQ_TYPE_MRAM_TO_ARAM) {
      ARStartDMA(__ARQRequestQueueHi->type, __ARQRequestQueueHi->source, __ARQRequestQueueHi->dest,
                 __ARQRequestQueueHi->length);
    } else {
      ARStartDMA(__ARQRequestQueueHi->type, __ARQRequestQueueHi->dest, __ARQRequestQueueHi->source,
                 __ARQRequestQueueHi->length);
    }

    __ARQCallbackHi = __ARQRequestQueueHi->callback;

    __ARQRequestPendingHi = __ARQRequestQueueHi;

    __ARQRequestQueueHi = __ARQRequestQueueHi->next;
  }
}

void __ARQServiceQueueLo(void) {

  if ((__ARQRequestPendingLo == NULL) && (__ARQRequestQueueLo)) {
    __ARQRequestPendingLo = __ARQRequestQueueLo;

    __ARQRequestQueueLo = __ARQRequestQueueLo->next;
  }

  if (__ARQRequestPendingLo) {
    if (__ARQRequestPendingLo->length <= __ARQChunkSize) {
      if (__ARQRequestPendingLo->type == ARQ_TYPE_MRAM_TO_ARAM)
        ARStartDMA(__ARQRequestPendingLo->type, __ARQRequestPendingLo->source,
                   __ARQRequestPendingLo->dest, __ARQRequestPendingLo->length);
      else
        ARStartDMA(__ARQRequestPendingLo->type, __ARQRequestPendingLo->dest,
                   __ARQRequestPendingLo->source, __ARQRequestPendingLo->length);

      __ARQCallbackLo = __ARQRequestPendingLo->callback;
    } else {
      if (__ARQRequestPendingLo->type == ARQ_TYPE_MRAM_TO_ARAM)
        ARStartDMA(__ARQRequestPendingLo->type, __ARQRequestPendingLo->source,
                   __ARQRequestPendingLo->dest, __ARQChunkSize);
      else
        ARStartDMA(__ARQRequestPendingLo->type, __ARQRequestPendingLo->dest,
                   __ARQRequestPendingLo->source, __ARQChunkSize);
    }

    __ARQRequestPendingLo->length -= __ARQChunkSize;
    __ARQRequestPendingLo->source += __ARQChunkSize;
    __ARQRequestPendingLo->dest += __ARQChunkSize;
  }
}
void __ARQCallbackHack(void) { return; }

void __ARQInterruptServiceRoutine(void) {

  if (__ARQCallbackHi) {
    (*__ARQCallbackHi)((u32)__ARQRequestPendingHi);
    __ARQRequestPendingHi = NULL;
    __ARQCallbackHi = NULL;
  }

  else if (__ARQCallbackLo) {
    (*__ARQCallbackLo)((u32)__ARQRequestPendingLo);
    __ARQRequestPendingLo = NULL;
    __ARQCallbackLo = NULL;
  }

  __ARQPopTaskQueueHi();

  if (__ARQRequestPendingHi == NULL)
    __ARQServiceQueueLo();
}

void ARQInit(void) {

  if (TRUE == __ARQ_init_flag) {
    return;
  }

  OSRegisterVersion(__ARQVersion);
  __ARQRequestQueueHi = __ARQRequestQueueLo = NULL;
  __ARQChunkSize = ARQ_CHUNK_SIZE_DEFAULT;
  ARRegisterDMACallback(&__ARQInterruptServiceRoutine);
  __ARQRequestPendingHi = NULL;
  __ARQRequestPendingLo = NULL;
  __ARQCallbackHi = NULL;
  __ARQCallbackLo = NULL;
  __ARQ_init_flag = TRUE;
}

void ARQPostRequest(ARQRequest* request, u32 owner, u32 type, u32 priority, u32 source, u32 dest,
                    u32 length, ARQCallback callback) {

  BOOL enabled;

  request->next = NULL;
  request->owner = owner;
  request->type = type;
  request->source = source;
  request->dest = dest;
  request->length = length;

  if (callback) {
    request->callback = callback;
  } else {
    request->callback = (ARQCallback)&__ARQCallbackHack;
  }

  enabled = OSDisableInterrupts();

  switch (priority) {
  case ARQ_PRIORITY_LOW:

    if (__ARQRequestQueueLo) {
      __ARQRequestTailLo->next = request;
    } else {
      __ARQRequestQueueLo = request;
    }
    __ARQRequestTailLo = request;

    break;

  case ARQ_PRIORITY_HIGH:

    if (__ARQRequestQueueHi) {
      __ARQRequestTailHi->next = request;
    } else {
      __ARQRequestQueueHi = request;
    }

    __ARQRequestTailHi = request;

    break;
  }

  if ((__ARQRequestPendingHi == NULL) && (__ARQRequestPendingLo == NULL)) {
    __ARQPopTaskQueueHi();

    if (__ARQRequestPendingHi == NULL) {
      __ARQServiceQueueLo();
    }
  }

  OSRestoreInterrupts(enabled);
}

u32 ARQGetChunkSize(void) { return __ARQChunkSize; }
