#include "dolphin/gx/GXFrameBuf.h"
#include "dolphin/gx/GX.h"

GXRenderModeObj GXNtsc480IntDf = {
    VI_TVMODE_NTSC_INT,
    640,
    480,
    480,
    40,
    0,
    640,
    480,
    VI_XFBMODE_DF,
    GX_FALSE,
    GX_FALSE,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    8,
    8,
    10,
    12,
    10,
    8,
    8,
};

GXRenderModeObj GXNtsc480Int = {
    VI_TVMODE_NTSC_INT,
    640,
    480,
    480,
    40,
    0,
    640,
    480,
    VI_XFBMODE_DF,
    GX_FALSE,
    GX_FALSE,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    0,
    0,
    21,
    22,
    21,
    0,
    0,
};

GXRenderModeObj GXMpal480IntDf = {
    VI_TVMODE_MPAL_INT,
    640,
    480,
    480,
    40,
    0,
    640,
    480,
    VI_XFBMODE_DF,
    GX_FALSE,
    GX_FALSE,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    8,
    8,
    10,
    12,
    10,
    8,
    8,
};

GXRenderModeObj GXPal528IntDf = {
    VI_TVMODE_PAL_INT,
    640,
    528,
    528,
    40,
    23,
    640,
    528,
    VI_XFBMODE_DF,
    GX_FALSE,
    GX_FALSE,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    8,
    8,
    10,
    12,
    10,
    8,
    8,
};

GXRenderModeObj GXEurgb60Hz480IntDf = {
    VI_TVMODE_EURGB60_INT,
    640,
    480,
    480,
    40,
    0,
    640,
    480,
    VI_XFBMODE_DF,
    GX_FALSE,
    GX_FALSE,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    6,
    8,
    8,
    10,
    12,
    10,
    8,
    8,
};

void GXSetDispCopySrc(u16 left, u16 top, u16 width, u16 height) {
    gx->cpDispSrc = 0;
    GX_BITFIELD_SET(gx->cpDispSrc, 22, 10, left);
    GX_BITFIELD_SET(gx->cpDispSrc, 12, 10, top);
    GX_BITFIELD_SET(gx->cpDispSrc, 0, 8, 73);

    gx->cpDispSize = 0;
    GX_BITFIELD_SET(gx->cpDispSize, 22, 10, width - 1);
    GX_BITFIELD_SET(gx->cpDispSize, 12, 10, height - 1);
    GX_BITFIELD_SET(gx->cpDispSize, 0, 8, 74);
}

void GXSetTexCopySrc(u16 left, u16 top, u16 width, u16 height) {
    gx->cpTexSrc = 0;
    GX_BITFIELD_SET(gx->cpTexSrc, 22, 10, left);
    GX_BITFIELD_SET(gx->cpTexSrc, 12, 10, top);
    GX_BITFIELD_SET(gx->cpTexSrc, 0, 8, 0x49);
    gx->cpTexSize = 0;
    GX_BITFIELD_SET(gx->cpTexSize, 22, 10, width - 1);
    GX_BITFIELD_SET(gx->cpTexSize, 12, 10, height - 1);
    GX_BITFIELD_SET(gx->cpTexSize, 0, 8, 0x4A);
}

void GXSetDispCopyDst(u16 arg0, u16 arg1) {
    s32 val = (s32)((arg0 << 1) & 0xFFFE) >> 5;
    gx->cpDispStride = 0;
    GX_BITFIELD_SET(gx->cpDispStride, 22, 10, val);
    GX_BITFIELD_SET(gx->cpDispStride, 0, 8, 0x4D);
}

void GXSetTexCopyDst(u16 width, u16 height, GXTexFmt format, GXBool useMIPmap) {
    u32 sp20, sp1C, sp18;
    u32 value;
    u8 depthRelated;

    gx->cpTexZ = GX_NONE;

    depthRelated = format & 0xf;
    if (format == GX_TF_Z16) {
        depthRelated = 0xb;
    }

    switch (format) {
    case GX_TF_I4:
    case GX_TF_I8:
    case GX_TF_IA4:
    case GX_TF_IA8:
    case GX_CTF_A8:
        GX_SET_REG(gx->cpTex, 3, 15, 16);
        break;
    default:
        GX_SET_REG(gx->cpTex, 2, 15, 16);
        break;
    }

    gx->cpTexZ = (format & 0x10) == 0x10;

    value = depthRelated >> 3;

    GX_SET_REG(gx->cpTex, value, 28, 28);

    depthRelated &= 7;

    __GetImageTileCount(format, width, height, &sp20, &sp1C, &sp18);

    gx->cpTexStride = GX_NONE;
    GX_SET_REG(gx->cpTexStride, sp20 * sp18, 22, 31);
    GX_SET_REG(gx->cpTexStride, 0x4D, 0, 7);

    GX_SET_REG(gx->cpTex, useMIPmap, 22, 22);
    GX_SET_REG(gx->cpTex, depthRelated, 25, 27);
}

void GXSetDispCopyFrame2Field(GXCopyMode arg0) {
    GX_BITFIELD_SET(gx->cpDisp, 18, 2, arg0);
    GX_BITFIELD_SET(gx->cpTex, 18, 2, 0);
}

// clang-format off
#define INSERT_FIELD(reg, value, nbits, shift)                                 \
    (reg) = ((u32) (reg) & ~(((1 << (nbits)) - 1) << (shift))) |               \
            ((u32) (value) << (shift));
// clang-format on

void GXSetCopyClamp(GXFBClamp clamp) {
    u8 isTop = (clamp & GX_CLAMP_TOP) == GX_CLAMP_TOP;
    u8 isBottom = (clamp & GX_CLAMP_BOTTOM) == GX_CLAMP_BOTTOM;
    gx->cpDisp = __rlwimi(gx->cpDisp, isTop, 0, 31, 31);
    gx->cpDisp = __rlwimi(gx->cpDisp, isBottom, 1, 30, 30);
    gx->cpTex = __rlwimi(gx->cpTex, isTop, 0, 31, 31);
    gx->cpTex = __rlwimi(gx->cpTex, isBottom, 1, 30, 30);
}

static u32 __GXGetNumXfbLines(u32 height, u32 scale) {
    u32 numLines;
    u32 actualHeight;
    u32 newScale;

    numLines = (height - 1) * 0x100;
    actualHeight = (numLines / scale) + 1;

    newScale = scale;

    if (newScale > 0x80 && newScale < 0x100) {
        while (newScale % 2 == 0) {
            newScale /= 2;
        }

        if (height % newScale == 0) {
            actualHeight++;
        }
    }

    if (actualHeight > 0x400) {
        actualHeight = 0x400;
    }

    return actualHeight;
}

u16 GXGetNumXfbLines(u16 efbHeight, f32 yScale) {
    u32 scale = (u32)(256.0f / yScale) & 0x1FF;

    return __GXGetNumXfbLines(efbHeight, scale);
}

f32 GXGetYScaleFactor(u16 efbHeight, u16 xfbHeight) {
    u32 scale;
    u32 height1;
    u32 height2;
    f32 scale2;
    f32 scale1;

    height1 = xfbHeight;
    scale1 = (f32)xfbHeight / (f32)efbHeight;
    scale = (u32)(256.0f / scale1) & 0x1FF;
    height2 = __GXGetNumXfbLines(efbHeight, scale);

    while (height2 > xfbHeight) {
        height1--;
        scale1 = (f32)height1 / (f32)efbHeight;
        scale = (u32)(256.0f / scale1) & 0x1FF;
        height2 = __GXGetNumXfbLines(efbHeight, scale);
    }

    scale2 = scale1;
    while (height2 < xfbHeight) {
        scale2 = scale1;
        height1++;
        scale1 = (f32)height1 / (f32)efbHeight;
        scale = (u32)(256.0f / scale1) & 0x1FF;
        height2 = __GXGetNumXfbLines(efbHeight, scale);
    }

    return scale2;
}

u32 GXSetDispCopyYScale(f32 vertScale) {
    u32 scale;
    GXBool check;
    u32 height;
    u32 reg;

    scale = (u32)(256.0f / vertScale) & 0x1FF;
    check = (scale != 0x100);

    reg = 0;
    GX_SET_REG(reg, scale, 23, 31);
    GX_SET_REG(reg, 0x4E, 0, 7);
    GX_BP_LOAD_REG(reg);
    gx->bpSentNot = GX_FALSE;

    GX_SET_REG(gx->cpDisp, check, 21, 21);

    height = (gx->cpDispSize >> 10 & 0x3FF) + 1;

    return __GXGetNumXfbLines(height, scale);
}

void GXSetCopyClear(GXColor color, u32 clear_z) {
    u32 r6 = 0;
    GX_BITFIELD_SET(r6, 24, 8, color.r);
    GX_BITFIELD_SET(r6, 16, 8, color.a);
    GX_BITFIELD_SET(r6, 0, 8, 0x4f);
    GXFIFO.u8 = 0x61;
    GXFIFO.u32 = r6;

    r6 = 0;
    GX_BITFIELD_SET(r6, 24, 8, color.b);
    GX_BITFIELD_SET(r6, 16, 8, color.g);
    GX_BITFIELD_SET(r6, 0, 8, 0x50);
    GXFIFO.u8 = 0x61;
    GXFIFO.u32 = r6;

    r6 = 0;
    GX_BITFIELD_SET(r6, 8, 24, clear_z);
    GX_BITFIELD_SET(r6, 0, 8, 0x51);
    GXFIFO.u8 = 0x61;
    GXFIFO.u32 = r6;

    gx->bpSentNot = 0;
}

void GXSetCopyFilter(GXBool useAA, u8 samplePattern[12][2], GXBool doVertFilt, u8 vFilt[7]) {
    u32 vals[4];
    u32 unk1;
    u32 unk2;

    if (useAA) {
        vals[0] = 0;
        GX_SET_REG(vals[0], samplePattern[0][0], 28, 31);
        GX_SET_REG(vals[0], samplePattern[0][1], 24, 27);
        GX_SET_REG(vals[0], samplePattern[1][0], 20, 23);
        GX_SET_REG(vals[0], samplePattern[1][1], 16, 19);
        GX_SET_REG(vals[0], samplePattern[2][0], 12, 15);
        GX_SET_REG(vals[0], samplePattern[2][1], 8, 11);
        GX_SET_REG(vals[0], 1, 0, 7);

        vals[1] = 0;
        GX_SET_REG(vals[1], samplePattern[3][0], 28, 31);
        GX_SET_REG(vals[1], samplePattern[3][1], 24, 27);
        GX_SET_REG(vals[1], samplePattern[4][0], 20, 23);
        GX_SET_REG(vals[1], samplePattern[4][1], 16, 19);
        GX_SET_REG(vals[1], samplePattern[5][0], 12, 15);
        GX_SET_REG(vals[1], samplePattern[5][1], 8, 11);
        GX_SET_REG(vals[1], 2, 0, 7);

        vals[2] = 0;
        GX_SET_REG(vals[2], samplePattern[6][0], 28, 31);
        GX_SET_REG(vals[2], samplePattern[6][1], 24, 27);
        GX_SET_REG(vals[2], samplePattern[7][0], 20, 23);
        GX_SET_REG(vals[2], samplePattern[7][1], 16, 19);
        GX_SET_REG(vals[2], samplePattern[8][0], 12, 15);
        GX_SET_REG(vals[2], samplePattern[8][1], 8, 11);
        GX_SET_REG(vals[2], 3, 0, 7);

        vals[3] = 0;
        GX_SET_REG(vals[3], samplePattern[9][0], 28, 31);
        GX_SET_REG(vals[3], samplePattern[9][1], 24, 27);
        GX_SET_REG(vals[3], samplePattern[10][0], 20, 23);
        GX_SET_REG(vals[3], samplePattern[10][1], 16, 19);
        GX_SET_REG(vals[3], samplePattern[11][0], 12, 15);
        GX_SET_REG(vals[3], samplePattern[11][1], 8, 11);
        GX_SET_REG(vals[3], 4, 0, 7);
    } else {
        vals[0] = 0x01666666;
        vals[1] = 0x02666666;
        vals[2] = 0x03666666;
        vals[3] = 0x04666666;
    }

    GX_BP_LOAD_REG(vals[0]);
    GX_BP_LOAD_REG(vals[1]);
    GX_BP_LOAD_REG(vals[2]);
    GX_BP_LOAD_REG(vals[3]);

    unk1 = 0;
    GX_SET_REG(unk1, 0x53, 0, 7);
    unk2 = 0;
    GX_SET_REG(unk2, 0x54, 0, 7);

    if (doVertFilt) {
        GX_SET_REG(unk1, vFilt[0], 26, 31);
        GX_SET_REG(unk1, vFilt[1], 20, 25);
        GX_SET_REG(unk1, vFilt[2], 14, 19);
        GX_SET_REG(unk1, vFilt[3], 8, 13);
        GX_SET_REG(unk2, vFilt[4], 26, 31);
        GX_SET_REG(unk2, vFilt[5], 20, 25);
        GX_SET_REG(unk2, vFilt[6], 14, 19);

    } else {
        GX_SET_REG(unk1, 0, 26, 31);
        GX_SET_REG(unk1, 0, 20, 25);
        GX_SET_REG(unk1, 21, 14, 19);
        GX_SET_REG(unk1, 22, 8, 13);
        GX_SET_REG(unk2, 21, 26, 31);
        GX_SET_REG(unk2, 0, 20, 25);
        GX_SET_REG(unk2, 0, 14, 19);
    }

    GX_BP_LOAD_REG(unk1);
    GX_BP_LOAD_REG(unk2);

    gx->bpSentNot = GX_FALSE;
}

void GXSetDispCopyGamma(GXGamma gamma) {
    GX_BITFIELD_SET(gx->cpDisp, 23, 2, gamma);
}

void GXCopyDisp(void* dest, GXBool doClear) {
    u32 reg;
    u32 newDest;
    GXBool check;

    if (doClear) {
        reg = gx->zmode;
        GX_SET_REG(reg, 1, 31, 31);
        GX_SET_REG(reg, 7, 28, 30);
        GX_BP_LOAD_REG(reg);

        reg = gx->cmode0;
        GX_SET_REG(reg, 0, 31, 31);
        GX_SET_REG(reg, 0, 30, 30);
        GX_BP_LOAD_REG(reg);
    }

    check = GX_FALSE;
    if ((doClear || (gx->peCtrl & 0x7) == 3) && (gx->peCtrl >> 6 & 0x1) == 1) {
        check = GX_TRUE;
        reg = gx->peCtrl;
        GX_SET_REG(reg, 0, 25, 25);
        GX_BP_LOAD_REG(reg);
    }

    GX_BP_LOAD_REG(gx->cpDispSrc);
    GX_BP_LOAD_REG(gx->cpDispSize);
    GX_BP_LOAD_REG(gx->cpDispStride);

    newDest = (u32)dest & 0x3FFFFFFF;
    reg = 0;
    GX_SET_REG(reg, newDest >> 5, 11, 31);
    GX_SET_REG(reg, 0x4B, 0, 7);
    GX_BP_LOAD_REG(reg);

    GX_SET_REG(gx->cpDisp, doClear, 20, 20);
    GX_SET_REG(gx->cpDisp, 1, 17, 17);
    GX_SET_REG(gx->cpDisp, 0x52, 0, 7);
    GX_BP_LOAD_REG(gx->cpDisp);

    if (doClear) {
        GX_BP_LOAD_REG(gx->zmode);
        GX_BP_LOAD_REG(gx->cmode0);
    }

    if (check) {
        GX_BP_LOAD_REG(gx->peCtrl);
    }

    gx->bpSentNot = GX_FALSE;
}

void GXCopyTex(void* dest, GXBool doClear) {
    u32 reg;
    u32 reg2;
    u32 newDest;
    GXBool check;

    if (doClear) {
        reg = gx->zmode;
        GX_SET_REG(reg, 1, 31, 31);
        GX_SET_REG(reg, 7, 28, 30);
        GX_BP_LOAD_REG(reg);

        reg = gx->cmode0;
        GX_SET_REG(reg, 0, 31, 31);
        GX_SET_REG(reg, 0, 30, 30);
        GX_BP_LOAD_REG(reg);
    }

    check = GX_FALSE;
    reg2 = gx->peCtrl;
    if (gx->cpTexZ && (reg2 & 0x7) != 3) {
        check = GX_TRUE;
        GX_SET_REG(reg2, 3, 29, 31);
    }

    if ((doClear || (reg2 & 0x7) == 3) && (reg2 >> 6 & 0x1) == 1) {
        check = GX_TRUE;
        GX_SET_REG(reg2, 0, 25, 25);
    }

    if (check) {
        GX_BP_LOAD_REG(reg2);
    }

    GX_BP_LOAD_REG(gx->cpTexSrc);
    GX_BP_LOAD_REG(gx->cpTexSize);
    GX_BP_LOAD_REG(gx->cpTexStride);

    newDest = (u32)dest & 0x3FFFFFFF;
    reg = 0;
    GX_SET_REG(reg, newDest >> 5, 11, 31);
    GX_SET_REG(reg, 0x4B, 0, 7);
    GX_BP_LOAD_REG(reg);

    GX_SET_REG(gx->cpTex, doClear, 20, 20);
    GX_SET_REG(gx->cpTex, 0, 17, 17);
    GX_SET_REG(gx->cpTex, 0x52, 0, 7);
    GX_BP_LOAD_REG(gx->cpTex);

    if (doClear) {
        GX_BP_LOAD_REG(gx->zmode);
        GX_BP_LOAD_REG(gx->cmode0);
    }

    if (check) {
        GX_BP_LOAD_REG(gx->peCtrl);
    }

    gx->bpSentNot = GX_FALSE;
}

void GXClearBoundingBox(void) {
    GXFIFO.u8 = GX_LOAD_BP_REG;
    GXFIFO.u32 = 0x550003FF;
    GXFIFO.u8 = GX_LOAD_BP_REG;
    GXFIFO.u32 = 0x560003FF;
    set_x2(GX_FALSE);
}
