#include <ultra64.h>
#include <global.h>

UNK_TYPE4 sDmaMgrDmaBuffSize = 0x2000;

s32 DmaMgr_DMARomToRam(u32 a0, void* a1, u32 a2) {
    OSIoMesg sp60;
    OSMesgQueue sp48;
    OSMesg sp44;
    s32 ret;
    u32 s0 = sDmaMgrDmaBuffSize;

    osInvalDCache(a1, a2);
    osCreateMesgQueue(&sp48, &sp44, 1);

    if (s0 != 0) {
        while (s0 < a2) {
            sp60.hdr.pri = 0;
            sp60.hdr.retQueue = &sp48;
            sp60.devAddr = (u32)a0;
            sp60.dramAddr = a1;
            sp60.size = s0;
            ret = osEPiStartDma(gCartHandle, &sp60, 0);
            if (ret) goto END;

            osRecvMesg(&sp48, NULL, 1);
            a2 -= s0;
            a0 = a0 + s0;
            a1 = (u8*)a1 + s0;
        }
    }
    sp60.hdr.pri = 0;
    sp60.hdr.retQueue = &sp48;
    sp60.devAddr = (u32)a0;
    sp60.dramAddr = a1;
    sp60.size = (u32)a2;
    ret = osEPiStartDma(gCartHandle, &sp60, 0);
    if (ret) goto END;

    osRecvMesg(&sp48, NULL, 1);

    osInvalDCache(a1, a2);

END:
    return ret;
}

void DmaMgr_DmaCallback0(OSPiHandle* pihandle, OSIoMesg* mb, s32 direction) {
    osEPiStartDma(pihandle, mb, direction);
}

DmaEntry* Dmamgr_FindDmaEntry(u32 a0) {
    DmaEntry* curr;

    for (curr = dmadata; curr->vromEnd != 0; curr++) {
        if (a0 < curr->vromStart) continue;
        if (a0 >= curr->vromEnd) continue;

        return curr;
    }

    return NULL;
}

u32 Dmamgr_TranslateVromToRom(u32 a0) {
    DmaEntry* v0 = Dmamgr_FindDmaEntry(a0);

    if (v0 != NULL) {
        if (v0->romEnd == 0) {
            return a0 + v0->romStart - v0->vromStart;
        }

        if (a0 == v0->vromStart) {
            return v0->romStart;
        }

        return -1;
    }

    return -1;
}

s32 Dmamgr_FindDmaIndex(u32 a0) {
    DmaEntry* v0 = Dmamgr_FindDmaEntry(a0);

    if (v0 != NULL) {
		return v0 - dmadata;
    }

    return -1;
}

// TODO this should be a string
char* func_800809F4(u32 a0) {
    return &D_800981C0[0];
}

#ifdef NON_MATCHING
void DmaMgr_ProcessMsg(DmaRequest* a0) {
    u32 sp34;
    u32 sp30;
    UNK_TYPE sp2C;
    UNK_TYPE sp28;
    UNK_TYPE sp24;
    UNK_TYPE sp20;
    s32 sp1C;
    UNK_TYPE sp18;

    sp34 = a0->vromStart;
    sp30 = a0->dramAddr;
    sp2C = a0->size;

    sp1C = Dmamgr_FindDmaIndex(sp34);

    if ((sp1C >= 0) && (sp1C < numDmaEntries)) {
        if (dmadata[sp1C].romEnd == 0) {
            if (dmadata[sp1C].vromEnd < (sp2C + sp34)) {
                Fault_AddHungupAndCrash(dmamgrString800981C4, 499);
            }
            DmaMgr_DMARomToRam((dmadata[sp1C].romStart + sp34) - dmadata[sp1C].vromStart, (u8*)sp30, sp2C);
            return;
        }

        // TODO this part is arranged slightly different is ASM
        sp24 = dmadata[sp1C].romEnd - dmadata[sp1C].romStart;
        sp28 = dmadata[sp1C].romStart;

        if (sp34 != dmadata[sp1C].vromStart) {
            Fault_AddHungupAndCrash(dmamgrString800981D4, 518);
        }

        if (sp2C != (dmadata[sp1C].vromEnd - dmadata[sp1C].vromStart)) {
            Fault_AddHungupAndCrash(dmamgrString800981E4, 525);
        }

        osSetThreadPri(NULL, 10);
        Yaz0_LoadAndDecompressFile(sp28, sp30, sp24);
        osSetThreadPri(NULL, 17);
    } else {
        Fault_AddHungupAndCrash(dmamgrString800981F4, 558);
    }
}
#else
GLOBAL_ASM("./asm/non_matchings/z_std_dma/DmaMgr_ProcessMsg.asm")
#endif

void Dmamgr_ThreadEntry(void* a0) {
    OSMesg sp34;
	u32 pad;
    DmaRequest* s0;

    for (;;) {
        osRecvMesg(&dmamgrMsq, &sp34, 1);
        if (sp34 == NULL) return;
        s0 = (DmaRequest*)sp34;
        DmaMgr_ProcessMsg(s0);
        if (s0->notifyQueue == NULL) continue;
        osSendMesg(s0->notifyQueue, s0->notifyMsg, 0);
    }
}

s32 DmaMgr_SendRequestImpl(DmaRequest* request, void* vramStart, u32 vromStart, u32 size, UNK_TYPE4 unused, OSMesgQueue* callback, void* callbackMesg) {
    if (gIrqMgrResetStatus >= 2) {
        return -2;
    }

    request->vromAddr = vromStart;
    request->dramAddr = vramStart;
    request->size = size;
    request->unk14 = 0;
    request->notifyQueue = callback;
    request->notifyMsg = callbackMesg;

    osSendMesg(&dmamgrMsq, request, 1);

    return 0;
}

s32 DmaMgr_SendRequest0(void* vramStart, u32 vromStart, u32 size) {
	DmaRequest sp48;
    OSMesgQueue sp30;
    OSMesg sp2C;
	s32 ret;

    osCreateMesgQueue(&sp30, &sp2C, 1);

	ret = DmaMgr_SendRequestImpl(&sp48, vramStart, vromStart, size, 0, &sp30, 0);

	if (ret == -1) {
		return ret;
	} else {
		osRecvMesg(&sp30, NULL, 1);
	}

	return 0;
}

#ifdef NON_MATCHING
// TODO missing a useless move initializing v0, and some reorderings
void Dmamgr_Start() {
	DmaEntry* v0;
	u32 v1;
	DmaMgr_DMARomToRam((u32)&dmadata_vrom_start, dmadata, (u32)&dmadata_vrom_end - (u32)&dmadata_vrom_start);

	for (v0 = dmadata, v1 = 0; v0->vromEnd != 0; v0++, v1++);

	numDmaEntries = v1;

	osCreateMesgQueue(&dmamgrMsq, dmamgrMsqMessages, 32);

	StackCheck_Init(&dmamgrStackEntry, &dmamgrStack, &dmamgrStack[1280], 0, 256, dmamgrThreadName);

	osCreateThread(&dmamgrOSThread, 18, Dmamgr_ThreadEntry, NULL, &dmamgrStack[1280], 17);

	osStartThread(&dmamgrOSThread);
}
#else
GLOBAL_ASM("./asm/non_matchings/z_std_dma/Dmamgr_Start.asm")
#endif

void Dmamgr_Stop() {
    osSendMesg(&dmamgrMsq, NULL, 1);
}
