#include "dolphin/os.h"

#define EXI_TX 0x800400u
#define EXI_MAGIC 0xa5ff005a

static s32 Chan;
static u32 Dev;
static u32 Enabled = 0;
static u32 BarnacleEnabled = 0;

static BOOL ProbeBarnacle(s32 chan, u32 dev, u32* revision) {
  BOOL err;
  u32 cmd;

  if (chan != 2 && dev == 0 && !EXIAttach(chan, NULL)) {
    return FALSE;
  }

  err = !EXILock(chan, dev, NULL);
  if (!err) {
    err = !EXISelect(chan, dev, EXI_FREQ_1M);
    if (!err) {
      cmd = 0x20011300;
      err = FALSE;
      err |= !EXIImm(chan, &cmd, 4, EXI_WRITE, NULL);
      err |= !EXISync(chan);
      err |= !EXIImm(chan, revision, 4, EXI_READ, NULL);
      err |= !EXISync(chan);
      err |= !EXIDeselect(chan);
    }
    EXIUnlock(chan);
  }

  if (chan != 2 && dev == 0) {
    EXIDetach(chan);
  }

  if (err) {
    return FALSE;
  }

  return (*revision != 0xFFFFFFFF) ? TRUE : FALSE;
}

void __OSEnableBarnacle(s32 chan, u32 dev) {
  u32 id;

  if (EXIGetID(chan, dev, &id)) {
    switch (id) {
    case 0xffffffff:
    case EXI_MEMORY_CARD_59:
    case EXI_MEMORY_CARD_123:
    case EXI_MEMORY_CARD_251:
    case EXI_MEMORY_CARD_507:
    case EXI_USB_ADAPTER:
    case EXI_NPDP_GDEV:
    case EXI_MODEM:
    case EXI_MARLIN:
    case 0x04220000:
    case 0x04020100:
    case 0x04020200:
    case 0x04020300:
    case 0x04040404:
    case 0x04060000:
    case 0x04120000:
    case 0x04130000:
    case 0x80000000 | EXI_MEMORY_CARD_59:
    case 0x80000000 | EXI_MEMORY_CARD_123:
    case 0x80000000 | EXI_MEMORY_CARD_251:
    case 0x80000000 | EXI_MEMORY_CARD_507:
      break;
    default:
      if (ProbeBarnacle(chan, dev, &id)) {
        Chan = chan;
        Dev = dev;
        Enabled = BarnacleEnabled = EXI_MAGIC;
      }
      break;
    }
  }
}

u32 InitializeUART(u32 baudRate) {
  if (BarnacleEnabled == EXI_MAGIC) {
    return 0;
  }

  if (!(OSGetConsoleType() & OS_CONSOLE_DEVELOPMENT)) {
    Enabled = 0;
    return 2;
  } else {
    Chan = 0;
    Dev = 1;
    Enabled = EXI_MAGIC;
    return 0;
  }
}

u32 ReadUARTN(void* bytes, unsigned long length) { return 4; }

static int QueueLength(void) {
  u32 cmd;

  if (!EXISelect(Chan, Dev, EXI_FREQ_8M))
    return -1;

  cmd = EXI_TX << 6;
  EXIImm(Chan, &cmd, 4, EXI_WRITE, NULL);
  EXISync(Chan);

  EXIImm(Chan, &cmd, 1, EXI_READ, NULL);
  EXISync(Chan);
  EXIDeselect(Chan);

  return 16 - (int)((cmd >> 24) & 0xff);
}

u32 WriteUARTN(const void* buf, unsigned long len) {
  u32 cmd;
  int qLen;
  long xLen;
  char* ptr;
  BOOL locked;
  u32 error;

  if (Enabled != EXI_MAGIC)
    return 2;

  locked = EXILock(Chan, Dev, 0);
  if (!locked) {
    return 0;
  }

  for (ptr = (char*)buf; ptr - buf < len; ptr++) {
    if (*ptr == '\n')
      *ptr = '\r';
  }

  error = 0;
  cmd = (EXI_TX | 0x2000000) << 6;
  while (len) {
    qLen = QueueLength();
    if (qLen < 0) {
      error = 3;
      break;
    }

    if (qLen < 12 && qLen < len)
      continue;

    if (!EXISelect(Chan, Dev, EXI_FREQ_8M)) {
      error = 3;
      break;
    }

    EXIImm(Chan, &cmd, 4, EXI_WRITE, NULL);
    EXISync(Chan);

    while (qLen && len) {
      if (qLen < 4 && qLen < len)
        break;
      xLen = (len < 4) ? (long)len : 4;
      EXIImm(Chan, (void*)buf, xLen, EXI_WRITE, NULL);
      (u8*)buf += xLen;
      len -= xLen;
      qLen -= xLen;
      EXISync(Chan);
    }
    EXIDeselect(Chan);
  }

  EXIUnlock(Chan);
  return error;
}
