#include "gx.hpp"
#include "__gx.h"
#include "dolphin/gd/GDGeometry.h"

static inline void SETVCDATTR(GXAttr attr, GXAttrType type) {
  switch (attr) {
  case GX_VA_PNMTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 0, type);
    break;
  case GX_VA_TEX0MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 1, type);
    break;
  case GX_VA_TEX1MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 2, type);
    break;
  case GX_VA_TEX2MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 3, type);
    break;
  case GX_VA_TEX3MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 4, type);
    break;
  case GX_VA_TEX4MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 5, type);
    break;
  case GX_VA_TEX5MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 6, type);
    break;
  case GX_VA_TEX6MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 7, type);
    break;
  case GX_VA_TEX7MTXIDX:
    SET_REG_FIELD(0, __gx->vcdLo, 1, 8, type);
    break;
  case GX_VA_POS:
    SET_REG_FIELD(0, __gx->vcdLo, 2, 9, type);
    break;
  case GX_VA_NRM:
    __gx->hasNrms = (type != 0);
    if (type != GX_NONE) {
      __gx->nrmType = type;
    }
    break;
  case GX_VA_NBT:
    __gx->hasBiNrms = (type != 0);
    if (type != GX_NONE) {
      __gx->nrmType = type;
    }
    break;
  case GX_VA_CLR0:
    SET_REG_FIELD(0, __gx->vcdLo, 2, 13, type);
    break;
  case GX_VA_CLR1:
    SET_REG_FIELD(0, __gx->vcdLo, 2, 15, type);
    break;
  case GX_VA_TEX0:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 0, type);
    break;
  case GX_VA_TEX1:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 2, type);
    break;
  case GX_VA_TEX2:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 4, type);
    break;
  case GX_VA_TEX3:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 6, type);
    break;
  case GX_VA_TEX4:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 8, type);
    break;
  case GX_VA_TEX5:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 10, type);
    break;
  case GX_VA_TEX6:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 12, type);
    break;
  case GX_VA_TEX7:
    SET_REG_FIELD(0, __gx->vcdHi, 2, 14, type);
    break;
  default:
    break;
  }
}

static inline void SETVAT(u32* va, u32* vb, u32* vc, GXAttr attr, GXCompCnt cnt, GXCompType type, u8 frac) {
  switch (attr) {
  case GX_VA_POS:
    SET_REG_FIELD(0, *va, 1, 0, cnt);
    SET_REG_FIELD(0, *va, 3, 1, type);
    SET_REG_FIELD(0, *va, 5, 4, frac);
    break;
  case GX_VA_NRM:
  case GX_VA_NBT:
    SET_REG_FIELD(0, *va, 3, 10, type);
    if (cnt == GX_NRM_NBT3) {
      SET_REG_FIELD(0, *va, 1, 9, 1);
      SET_REG_FIELD(0, *va, 1, 31, 1);
    } else {
      SET_REG_FIELD(0, *va, 1, 9, cnt);
      SET_REG_FIELD(0, *va, 1, 31, 0);
    }
    break;
  case GX_VA_CLR0:
    SET_REG_FIELD(0, *va, 1, 13, cnt);
    SET_REG_FIELD(0, *va, 3, 14, type);
    break;
  case GX_VA_CLR1:
    SET_REG_FIELD(0, *va, 1, 17, cnt);
    SET_REG_FIELD(0, *va, 3, 18, type);
    break;
  case GX_VA_TEX0:
    SET_REG_FIELD(0, *va, 1, 21, cnt);
    SET_REG_FIELD(0, *va, 3, 22, type);
    SET_REG_FIELD(0, *va, 5, 25, frac);
    break;
  case GX_VA_TEX1:
    SET_REG_FIELD(0, *vb, 1, 0, cnt);
    SET_REG_FIELD(0, *vb, 3, 1, type);
    SET_REG_FIELD(0, *vb, 5, 4, frac);
    break;
  case GX_VA_TEX2:
    SET_REG_FIELD(0, *vb, 1, 9, cnt);
    SET_REG_FIELD(0, *vb, 3, 10, type);
    SET_REG_FIELD(0, *vb, 5, 13, frac);
    break;
  case GX_VA_TEX3:
    SET_REG_FIELD(0, *vb, 1, 18, cnt);
    SET_REG_FIELD(0, *vb, 3, 19, type);
    SET_REG_FIELD(0, *vb, 5, 22, frac);
    break;
  case GX_VA_TEX4:
    SET_REG_FIELD(0, *vb, 1, 27, cnt);
    SET_REG_FIELD(0, *vb, 3, 28, type);
    SET_REG_FIELD(0, *vc, 5, 0, frac);
    break;
  case GX_VA_TEX5:
    SET_REG_FIELD(0, *vc, 1, 5, cnt);
    SET_REG_FIELD(0, *vc, 3, 6, type);
    SET_REG_FIELD(0, *vc, 5, 9, frac);
    break;
  case GX_VA_TEX6:
    SET_REG_FIELD(0, *vc, 1, 14, cnt);
    SET_REG_FIELD(0, *vc, 3, 15, type);
    SET_REG_FIELD(0, *vc, 5, 18, frac);
    break;
  case GX_VA_TEX7:
    SET_REG_FIELD(0, *vc, 1, 23, cnt);
    SET_REG_FIELD(0, *vc, 3, 24, type);
    SET_REG_FIELD(0, *vc, 5, 27, frac);
    break;
  default:
    break;
  }
}

extern "C" {

void GXSetVtxDesc(GXAttr attr, GXAttrType type) {
  SETVCDATTR(attr, type);
  if (__gx->hasNrms || __gx->hasBiNrms) {
    SET_REG_FIELD(0, __gx->vcdLo, 2, 11, __gx->nrmType);
  } else {
    SET_REG_FIELD(0, __gx->vcdLo, 2, 11, 0);
  }
  __gx->dirtyState |= 8;
}

void GXSetVtxDescv(GXVtxDescList* list) {
  while (list->attr != GX_VA_NULL) {
    SETVCDATTR(list->attr, list->type);
    ++list;
  }
  if (__gx->hasNrms || __gx->hasBiNrms) {
    SET_REG_FIELD(0, __gx->vcdLo, 2, 11, __gx->nrmType);
  } else {
    SET_REG_FIELD(0, __gx->vcdLo, 2, 11, 0);
  }
  __gx->dirtyState |= 8;
}

void GXClearVtxDesc() {
  __gx->vcdLo = 0;
  SET_REG_FIELD(0, __gx->vcdLo, 2, 9, 1); // GX_VA_POS = GX_DIRECT
  __gx->vcdHi = 0;
  __gx->hasNrms = 0;
  __gx->hasBiNrms = 0;
  __gx->dirtyState |= 8;
}

void GXSetVtxAttrFmt(GXVtxFmt vtxfmt, GXAttr attr, GXCompCnt cnt, GXCompType type, u8 frac) {
  CHECK(vtxfmt >= GX_VTXFMT0 && vtxfmt < GX_MAX_VTXFMT, "invalid vtxfmt {}", underlying(vtxfmt));
  CHECK(attr >= GX_VA_POS && attr < GX_VA_MAX_ATTR, "invalid attr {}", underlying(attr));

  u32* va = &__gx->vatA[vtxfmt];
  u32* vb = &__gx->vatB[vtxfmt];
  u32* vc = &__gx->vatC[vtxfmt];
  SETVAT(va, vb, vc, attr, cnt, type, frac);
  __gx->dirtyState |= 0x10;
  __gx->dirtyVAT |= static_cast<u8>(1 << vtxfmt);
}

void GXSetVtxAttrFmtv(GXVtxFmt vtxfmt, const GXVtxAttrFmtList* list) {
  CHECK(vtxfmt >= GX_VTXFMT0 && vtxfmt < GX_MAX_VTXFMT, "invalid vtxfmt {}", underlying(vtxfmt));
  CHECK(list != nullptr, "null vtx attr fmt list");

  u32* va = &__gx->vatA[vtxfmt];
  u32* vb = &__gx->vatB[vtxfmt];
  u32* vc = &__gx->vatC[vtxfmt];
  while (list->attr != GX_VA_NULL) {
    CHECK(list->attr >= GX_VA_POS && list->attr < GX_VA_MAX_ATTR, "invalid attr {}", underlying(list->attr));
    SETVAT(va, vb, vc, list->attr, list->cnt, list->type, list->frac);
    ++list;
  }

  __gx->dirtyState |= 0x10;
  __gx->dirtyVAT |= static_cast<u8>(1 << vtxfmt);
}

void GXSetArray(GXAttr attr, const void* data, u32 size, u8 stride, bool le) {
  GXAttr cpAttr = static_cast<GXAttr>(attr);
  if (attr == GX_VA_NBT) {
    cpAttr = GX_VA_NRM;
  }
  u32 cpIdx = cpAttr - GX_VA_POS;

  assert((cpIdx & ~0xF) == 0);

  // Write array base
  GX_WRITE_AURORA(GX_LOAD_AURORA_ARRAYBASE | cpIdx);
  GX_WRITE_U64(reinterpret_cast<u64>(data));
  GX_WRITE_U32(size);
  GX_WRITE_U8(le ? 1 : 0);

  // Write array stride
  GX_WRITE_CP_REG(CP_REG_ARRAYSTRIDE_ID | cpIdx, stride);
}

void GXSetTexCoordGen2(GXTexCoordID dst, GXTexGenType type, GXTexGenSrc src, u32 mtx, GXBool normalize, u32 postMtx) {
  CHECK(dst >= GX_TEXCOORD0 && dst <= GX_TEXCOORD7, "invalid tex coord {}", underlying(dst));

  u32 reg = 0;
  u32 row = 5;
  u32 form = 0;

  switch (src) {
  case GX_TG_POS:
    row = 0;
    form = 1;
    break;
  case GX_TG_NRM:
    row = 1;
    form = 1;
    break;
  case GX_TG_BINRM:
    row = 3;
    form = 1;
    break;
  case GX_TG_TANGENT:
    row = 4;
    form = 1;
    break;
  case GX_TG_COLOR0:
    row = 2;
    break;
  case GX_TG_COLOR1:
    row = 2;
    break;
  case GX_TG_TEX0:
    row = 5;
    break;
  case GX_TG_TEX1:
    row = 6;
    break;
  case GX_TG_TEX2:
    row = 7;
    break;
  case GX_TG_TEX3:
    row = 8;
    break;
  case GX_TG_TEX4:
    row = 9;
    break;
  case GX_TG_TEX5:
    row = 10;
    break;
  case GX_TG_TEX6:
    row = 11;
    break;
  case GX_TG_TEX7:
    row = 12;
    break;
  default:
    break;
  }

  switch (type) {
  case GX_TG_MTX2x4:
    SET_REG_FIELD(0, reg, 1, 1, 0);
    SET_REG_FIELD(0, reg, 1, 2, form);
    SET_REG_FIELD(0, reg, 3, 4, 0);
    SET_REG_FIELD(0, reg, 5, 7, row);
    break;
  case GX_TG_MTX3x4:
    SET_REG_FIELD(0, reg, 1, 1, 1);
    SET_REG_FIELD(0, reg, 1, 2, form);
    SET_REG_FIELD(0, reg, 3, 4, 0);
    SET_REG_FIELD(0, reg, 5, 7, row);
    break;
  case GX_TG_SRTG:
    SET_REG_FIELD(0, reg, 1, 1, 0);
    SET_REG_FIELD(0, reg, 1, 2, form);
    if (src == GX_TG_COLOR0) {
      SET_REG_FIELD(0, reg, 3, 4, 2);
    } else {
      SET_REG_FIELD(0, reg, 3, 4, 3);
    }
    SET_REG_FIELD(0, reg, 5, 7, 2);
    break;
  default:
    // Bump mapping types
    if (type >= 2 && type <= 9) {
      SET_REG_FIELD(0, reg, 1, 1, 0);
      SET_REG_FIELD(0, reg, 1, 2, form);
      SET_REG_FIELD(0, reg, 3, 4, 1);
      SET_REG_FIELD(0, reg, 5, 7, row);
      SET_REG_FIELD(0, reg, 3, 12, src - 12);
      SET_REG_FIELD(0, reg, 3, 15, type - 2);
    }
    break;
  }

  GX_WRITE_XF_REG(dst + 0x40, reg);

  // Post-transform register
  u32 postReg = 0;
  SET_REG_FIELD(0, postReg, 6, 0, postMtx - 64);
  SET_REG_FIELD(0, postReg, 1, 8, normalize);
  GX_WRITE_XF_REG(dst + 0x50, postReg);

  // Update matrix index shadows
  switch (dst) {
  case GX_TEXCOORD0:
    SET_REG_FIELD(0, __gx->matIdxA, 6, 6, mtx);
    break;
  case GX_TEXCOORD1:
    SET_REG_FIELD(0, __gx->matIdxA, 6, 12, mtx);
    break;
  case GX_TEXCOORD2:
    SET_REG_FIELD(0, __gx->matIdxA, 6, 18, mtx);
    break;
  case GX_TEXCOORD3:
    SET_REG_FIELD(0, __gx->matIdxA, 6, 24, mtx);
    break;
  case GX_TEXCOORD4:
    SET_REG_FIELD(0, __gx->matIdxB, 6, 0, mtx);
    break;
  case GX_TEXCOORD5:
    SET_REG_FIELD(0, __gx->matIdxB, 6, 6, mtx);
    break;
  case GX_TEXCOORD6:
    SET_REG_FIELD(0, __gx->matIdxB, 6, 12, mtx);
    break;
  default:
    SET_REG_FIELD(0, __gx->matIdxB, 6, 18, mtx);
    break;
  }
  __GXSetMatrixIndex(static_cast<GXAttr>(dst + 1));
}

void GXSetNumTexGens(u8 num) {
  SET_REG_FIELD(0, __gx->genMode, 4, 0, num);
  GX_WRITE_XF_REG(0x3F, num);
  __gx->dirtyState |= 4;
}

void GXInvalidateVtxCache() { GX_WRITE_U8(0x48); }

void GXSetLineWidth(u8 width, GXTexOffset offs) {
  SET_REG_FIELD(0, __gx->lpSize, 8, 0, width);
  SET_REG_FIELD(0, __gx->lpSize, 3, 16, offs);
  GX_WRITE_RAS_REG(__gx->lpSize);
  __gx->bpSent = 1;
}

void GXSetPointSize(u8 pointSize, GXTexOffset texOffsets) {
  SET_REG_FIELD(0, __gx->lpSize, 8, 8, pointSize);
  SET_REG_FIELD(0, __gx->lpSize, 3, 19, texOffsets);
  GX_WRITE_RAS_REG(__gx->lpSize);
  __gx->bpSent = 1;
}

void GXEnableTexOffsets(GXTexCoordID coord, GXBool line_enable, GXBool point_enable) {
  SET_REG_FIELD(0, __gx->suTs0[coord], 1, 18, line_enable);
  SET_REG_FIELD(0, __gx->suTs0[coord], 1, 19, point_enable);
  GX_WRITE_RAS_REG(__gx->suTs0[coord]);
  __gx->bpSent = 1;
}
}
