// Main interface for the 64DD from the rest of the game. Starts background
// threads and provides functions to submit commands to them.

#include "n64dd.h"

#include "libc64/sleep.h"
#include "array_count.h"
#include "fault.h"
#include "gfx.h"
#include "irqmgr.h"
#include "line_numbers.h"
#include "stack.h"
#include "stackcheck.h"
#include "sys_freeze.h"
#include "versions.h"
#include "audio.h"
#include "thread.h"

#pragma increment_block_number "ntsc-1.0:64 ntsc-1.1:64 ntsc-1.2:64 pal-1.0:64 pal-1.1:64"

typedef struct struct_801D9C30 {
    /* 0x000 */ s32 unk_000;       // disk start
    /* 0x004 */ s32 unk_004;       // disk end
    /* 0x008 */ uintptr_t unk_008; // ram start
    /* 0x00C */ uintptr_t unk_00C; // ram end
    /* 0x010 */ UNK_PTR unk_010;
    /* 0x014 */ char unk_014[0x104];
} struct_801D9C30; // size = 0x118

typedef struct struct_801D9B90 {
    /* 0x00 */ OSMesg unk_00[30];
    /* 0x78 */ OSMesgQueue unk_78;
    /* 0x90 */ IrqMgrClient unk_90;
    /* 0x98 */ IrqMgr* unk_98;
} struct_801D9B90; // size = 0x9C

s32 func_801C7A1C(struct_801E0D18* arg0);

void* D_801D2E50 = &gN64DDDiskReadTemporaryBuffer;
s32 (*D_801D2E54)(struct_801E0D18*) = func_801C7A1C;

struct_801D9B90 B_801D9B90;
struct_801D9C30 B_801D9C30;
struct_801D9C30* B_801D9D48;
struct_801D9D50 B_801D9D50;
OSMesgQueue B_801D9D80;
OSMesgQueue B_801D9D98;
OSMesg B_801D9DB0[1];
OSMesg B_801D9DB4[1];
volatile u8 B_801D9DB8;
volatile OSTime B_801D9DC0;
s32 B_801D9DC8; // 1 if disk gameName is correct, 2 otherwise
void* B_801D9DCC;
void* B_801D9DD0;
void* B_801D9DD4;
OSThread B_801D9DD8;
STACK(B_801D9F88, 0x1000);
StackEntry B_801DAF88;
STACK(B_801DAFA8, 0x1000);
StackEntry B_801DBFA8;
UNK_TYPE B_801DBFC4; // unused?

u32 func_801C6E80(void) {
#if OOT_NTSC
    return LeoDriveExist();
#else
    return 0;
#endif
}

void func_801C6EA0(Gfx** gfxP) {
}

void func_801C6EAC(void) {
    if (D_80121214 == 0) {
        func_800F6BDC();
        D_80121214 = 1;
    }
}

s32 func_801C6EF0(void) {
    return D_80121214 != 0;
}

s32 func_801C6F08(void) {
    if (D_80121214 != 0) {
        return 1;
    }
    return 1;
}

void func_801C6F30(void) {
    func_801C6EAC();
    while (func_801C6F08() == 0) {
        Sleep_Usec(16666); // 100000 / 6
    }
}

void func_801C6F78(void) {
    if (D_80121214 != 0) {
        D_80121214 = 0;
        func_800F6B3C();
    }
}

// boolean
s32 func_801C6FAC(void) {
    if (D_80121213 == 0) {
        return false;
    } else {
        D_80121213 = 0;
        return true;
    }
}

void func_801C6FD8(void) {
    while (!func_801C6FAC()) {
        Sleep_Usec(16666); // 100000 / 6
    }
}

// Adds a HungupAndCrash
void func_801C7018(void) {
    if (D_80121213 != 0) {
        Fault_AddHungupAndCrash("../z_n64dd.c", LN2(503, 551, 573));
    }

    D_80121213 = 1;
}

s32 func_801C7064(void) {
    B_801D9D50.unk_00 = 5;
    return (&func_801C8000)(&B_801D9D50);
}

s32 func_801C7098(void) {
    s32 phi_v1;

#if OOT_VERSION < PAL_1_0
    if (0) {}
#endif

    B_801D9D50.unk_00 = 10;
    phi_v1 = (&func_801C8000)(&B_801D9D50);
    if (phi_v1 < 0) {
        Freeze_CurrentThread();
    }
    return phi_v1;
}

s32 func_801C70E4(void) {
    return B_801D9DC8 == 1;
}

// Used by EnMag and FileChoose
s32 func_801C70FC(void) {
    return func_801C70E4();
}

void func_801C711C(void* arg) {
    static void* B_801DBFC8;
    struct_801D9B90* arg0 = (struct_801D9B90*)arg;
    s16* sp58;
    s32 var_s0;
    void* temp_v0;

    sp58 = NULL;
    arg0->unk_98 = &gIrqMgr;
    osCreateMesgQueue(&arg0->unk_78, arg0->unk_00, ARRAY_COUNT(arg0->unk_00));
    IrqMgr_AddClient(arg0->unk_98, &arg0->unk_90, &arg0->unk_78);
    var_s0 = 0;
    do {
        osRecvMesg(&arg0->unk_78, (OSMesg*)&sp58, OS_MESG_BLOCK);
        switch (*sp58) {
            case 1:
                temp_v0 = osViGetNextFramebuffer();
                if (B_801DBFC8 != temp_v0) {
                    B_801DBFC8 = temp_v0;
                    B_801D9DB8 = 1;
                }
                func_801C8AA8();
                break;
            case 4:
                LeoReset();
                break;
            case 3:
                var_s0 = 1;
                break;
        }
    } while (var_s0 == 0);
    IrqMgr_RemoveClient(arg0->unk_98, &arg0->unk_90);
}

#if OOT_VERSION >= NTSC_1_1
void func_801C7B28_ne2(void) {
    s32 temp;

    if (B_801D9DC0 != 0) {
        temp = (osGetTime() - B_801D9DC0) * 64 / 3000;
        if (1000000 - temp > 0) {
            Sleep_Usec(1000000 - temp);
        }
    }
}
#endif

void func_801C7268(void) {
    s32 pad;
    s32 sp20;
    s32 sp1C;

    sp20 = func_801C6EF0();
    if (sp20 == 0) {
        func_801C6F30();
    }
    B_801D9DB8 = 1;
    B_801D9DC0 = 0;
    if (func_801C7064() == 1) {
        func_801C7098();
    } else if (B_801D9DC8 != 0) {
        B_801D9DC8 = 0;
    }
#if OOT_VERSION < NTSC_1_1
    if (B_801D9DC0 != 0) {
        sp1C = (osGetTime() - B_801D9DC0) * 64 / 3000;

        // Remnants from debug statements?
        (void)(osGetTime() - B_801D9DC0);
        (void)((osGetTime() - B_801D9DC0) * 64 / 3000);
        (void)((osGetTime() - B_801D9DC0) * 64 / 3000);

        if (1000000 - sp1C > 0) {
            Sleep_Usec(1000000 - sp1C);
        }
    }
#else
    if (D_801D2EA8 == 1 || B_801E0F60 == 1 || B_801E0F64 == 1) {
        B_801D9DC0 = osGetTime();
    }
    func_801C7B28_ne2();
#endif
    if (sp20 == 0) {
        func_801C6F78();
    }
}

// Clears framebuffer
void func_801C7438(void* arg0) {
    u16* var_v0;

    for (var_v0 = (u16*)arg0; var_v0 < (u16*)arg0 + SCREEN_WIDTH * SCREEN_HEIGHT; var_v0++) {
        *var_v0 = 1;
    }
}

void func_801C746C(void* arg0, void* arg1, void* arg2) {
    void* sp2C;

    if (arg0 != NULL || arg1 != NULL || arg2 != NULL) {
        sp2C = (u8*)osViGetNextFramebuffer() + 0x20000000;
        if ((u32)sp2C & 0xFFFFFF) {
            if (B_801D9DB8 != 0) {
                B_801D9DB8 = 0;
                func_801C7438(sp2C);
                B_801D9DC0 = osGetTime();
            }
            if (arg0 != NULL) {
                func_801CA1F0(arg0, 96, 32, 192, 16, 11, sp2C, SCREEN_WIDTH);
            }
            if (arg1 != NULL) {
                func_801CA1F0(arg1, 0, 80, 320, 64, 11, sp2C, SCREEN_WIDTH);
            }
            if (arg2 != NULL) {
                func_801CA1F0(arg2, 0, 176, 320, 32, 11, sp2C, SCREEN_WIDTH);
            }
#if OOT_VERSION < PAL_1_0
            osViBlack(0);
#endif
        }
    }
}

void func_801C75BC(void* arg0, void* arg1, void* arg2) {
    s32 temp;

    if (arg0 == NULL && arg1 == NULL && arg2 == NULL) {
        return;
    }

    if (B_801D9DB8) {}

    if (arg0 != 0) {
        B_801D9DCC = arg0;
    }
    if (arg1 != 0) {
        B_801D9DD0 = arg1;
    }
    if (arg2 != 0) {
        B_801D9DD4 = arg2;
    }
    func_801C746C(arg0, arg1, arg2);
}

void func_801C761C(void) {
    Sleep_Msec(100);
    func_801C746C(B_801D9DCC, B_801D9DD0, B_801D9DD4);
}

s32 func_801C7658(void) {
    if (D_80121212 != 0) {
        return 0;
    }

#if OOT_VERSION < PAL_1_0
    StackCheck_Init(&B_801DAF88, B_801D9F88, STACK_TOP(B_801D9F88), 0, 0x100, "ddmsg");
    osCreateThread(&B_801D9DD8, THREAD_ID_DDMSG, &func_801C711C, &B_801D9B90, STACK_TOP(B_801D9F88), THREAD_PRI_DDMSG);
    osStartThread(&B_801D9DD8);
#endif

    osCreateMesgQueue(&B_801D9D80, B_801D9DB0, ARRAY_COUNT(B_801D9DB0));
    osCreateMesgQueue(&B_801D9D98, B_801D9DB4, ARRAY_COUNT(B_801D9DB4));

    StackCheck_Init(&B_801DBFA8, B_801DAFA8, STACK_TOP(B_801DAFA8), 0, 0x100, "n64dd");

    B_801D9D50.unk_1C = &B_801D9D80;
    B_801D9D50.unk_20 = &B_801D9D98;
    B_801D9D50.unk_24 = THREAD_ID_N64DD;
    B_801D9D50.unk_28 = STACK_TOP(B_801DAFA8);
    B_801D9D50.unk_2C = THREAD_PRI_N64DD;
    B_801D9D50.unk_00 = 1;

    (&func_801C8000)(&B_801D9D50);

    D_80121213 = 1;
    func_801C6FD8();

    B_801D9D50.unk_00 = 2;
    B_801D9D50.unk_10 = 6;
    B_801D9D50.unk_14 = &DmaMgr_DmaFromDriveRom;
    B_801D9D50.unk_0C = &func_801C75BC;
    (&func_801C8000)(&B_801D9D50);

    B_801D9D50.unk_00 = 13;
    (&func_801C8000)(&B_801D9D50);

#if OOT_VERSION >= PAL_1_0
    StackCheck_Init(&B_801DAF88, B_801D9F88, STACK_TOP(B_801D9F88), 0, 0x100, "ddmsg");
    osCreateThread(&B_801D9DD8, THREAD_ID_DDMSG, &func_801C711C, &B_801D9B90, STACK_TOP(B_801D9F88), THREAD_PRI_DDMSG);
    osStartThread(&B_801D9DD8);
#endif

    return 0;
}

s32 func_801C7818(void) {
#if OOT_VERSION >= NTSC_1_1
    B_801D9DB8 = 1;
    B_801D9DC0 = 0;
#endif

    B_801D9D50.unk_00 = 12;
    (&func_801C8000)(&B_801D9D50);

    while (func_801C81C4() == 0) {
        // the number 16666 sounds like it could be 1 frame (at 60 frames per second)
        Sleep_Usec(1000000 * 1 / 60);
    }

#if OOT_VERSION >= NTSC_1_1
    if (D_801D2EA8 == 1 || B_801E0F60 == 1 || B_801E0F64 == 1) {
        B_801D9DC0 = osGetTime();
    }
    func_801C7B28_ne2();
#endif

    if (func_801C81C4() != 2) {
        func_801C761C();
        Freeze_CurrentThread();
        return -3;
    }

    func_801C7018();
    D_80121212 = 1;
    return 0;
}

s32 func_801C78B8(void) {
    s32 phi_v1 = func_801C7658();

    if (phi_v1 == 0) {
        phi_v1 = func_801C7818();
    }
    return phi_v1;
}

s32 func_801C78F0(void) {
    B_801D9D50.unk_00 = 0;
    return (&func_801C8000)(&B_801D9D50);
}

void func_801C7920(s32 arg0, void* arg1, s32 arg2) {
    B_801D9D50.unk_18 = arg1;
    B_801D9D50.unk_1C = (void*)arg0;
    B_801D9D50.unk_20 = (void*)arg2;
    B_801D9D50.unk_00 = 3;
    (&func_801C8000)(&B_801D9D50);
    osGetTime();
    B_801D9D50.unk_00 = 6;
    while ((&func_801C8000)(&B_801D9D50) != 0) {
        Sleep_Usec(16666); // 100000 / 6
    }
    B_801D9D50.unk_00 = 7;
    if ((&func_801C8000)(&B_801D9D50) != 0) {
        Freeze_CurrentThread();
    }
}

void func_801C79CC(void* arg0, s32 arg1, s32 arg2) {
    B_801D9D50.unk_18 = arg0;
    B_801D9D50.unk_1C = (void*)arg1;
    B_801D9D50.unk_20 = (void*)arg2;
    B_801D9D50.unk_00 = 4;
    (&func_801C8000)(&B_801D9D50);
}

void func_801C7A10(LEODiskID* arg0) {
}

// Checks diskId, sets B_801D9DC8 and returns true if diskId is correct
s32 func_801C7A1C(struct_801E0D18* arg0) {
    static LEODiskID B_801DBFD0;
    static s32 B_801DBFF0; // bool

    func_801C7A10(&arg0->diskId);
    if (!B_801DBFF0) {
#if OOT_NTSC
        if (bcmp(arg0->diskId.gameName, "EZLJ", 4) == 0 || bcmp(arg0->diskId.gameName, "EZLE", 4) == 0)
#else
        if (bcmp(arg0->diskId.gameName, "EZLP", 4) == 0)
#endif
        {
            B_801DBFD0 = arg0->diskId;
            B_801DBFF0 = true;
            B_801D9DC8 = 1;
        } else {
            B_801D9DC8 = 2;
        }
    } else if (bcmp(&B_801DBFD0, &arg0->diskId, sizeof(LEODiskID)) == 0) {
        B_801D9DC8 = 1;
    } else {
        B_801D9DC8 = 2;
    }
    return B_801D9DC8 == 1;
}

// Translates byte position to LBA and byte offset
s32 func_801C7B48(s32 arg0, s32* arg1, s32* arg2) {
    s32 sp2C;
    s32 temp_v0_2;
    s32 sp24;
    s32 sp20;
    s32 temp_v0;

    temp_v0_2 = LeoByteToLBA(1, arg0 + 1, &sp2C);
    if (temp_v0_2 != LEO_ERROR_GOOD) {
        return temp_v0_2;
    }
    sp24 = sp2C - 1;
    if (sp2C == 1) {
        sp20 = 0;
    } else {
        temp_v0 = LeoLBAToByte(1, sp24, &sp20);
        if (temp_v0 != LEO_ERROR_GOOD) {
            return temp_v0;
        }
    }
    *arg1 = sp24 + 1;
    *arg2 = arg0 - sp20;
    return LEO_ERROR_GOOD;
}

s32 func_801C7BEC(s32 startLBA) {
    s32 bytes;

    if (LeoLBAToByte(startLBA, 1, &bytes) == LEO_ERROR_GOOD) {
        return bytes;
    }
    return 0;
}

// Copies bytes from disk to arg0
void func_801C7C1C(void* dest, s32 offset, s32 size) {
    s32 sp5C;
    s32 sp58;
    s32 sp54;
    s32 sp50;
    void* sp4C;
    s32 var_s0;
    s32 var_s1;
    s32 temp_v1_2;

    func_801C6FD8();
    func_801C6F30();
    B_801D9DB8 = 1;
    B_801D9DC0 = 0;
    func_801C7B48(offset, &sp5C, &sp54);
    func_801C7B48(offset + size, &sp58, &sp50);
    sp4C = D_801D2E50;
    if (sp5C == sp58) {
        func_801C7920(sp5C, sp4C, func_801C7BEC(sp5C));
        bcopy((u8*)sp4C + sp54, dest, size);
    } else {
        var_s1 = 0;
        func_801C7920(sp5C, sp4C, func_801C7BEC(sp5C));
        bcopy((u8*)sp4C + sp54, dest, func_801C7BEC(sp5C) - sp54);
        if (sp5C + 1 < sp58) {
            for (var_s0 = sp5C + 1; var_s0 < sp58; var_s0++) {
                var_s1 += func_801C7BEC(var_s0);
            }
            func_801C7920(sp5C + 1, (u8*)dest + func_801C7BEC(sp5C) - sp54, var_s1);
        }
        if (sp50 > 0) {
            func_801C7920(sp58, sp4C, func_801C7BEC(sp58));
            bcopy((u8*)sp4C, (u8*)dest + func_801C7BEC(sp5C) - sp54 + var_s1, sp50);
        }
    }
#if OOT_VERSION < NTSC_1_1
    if (B_801D9DC0 != 0) {
        temp_v1_2 = (osGetTime() - B_801D9DC0) * 64 / 3000;
        if (1000000 - temp_v1_2 > 0) {
            Sleep_Usec(1000000 - temp_v1_2);
        }
    }
#else
    func_801C7B28_ne2();
#endif
    func_801C7018();
    func_801C6F78();
}

void func_801C7E78(void) {
}

s32 func_801C7E80(void) {
    s32 sp24;
    s32 sp20;
    s32 sp1C;
    uintptr_t sp18;

    if (B_801D9D48 != NULL) {
        return -1;
    }
    B_801D9D48 = &B_801D9C30;
    func_801C7C1C(B_801D9D48, 0x1060, 0x118);
    sp24 = B_801D9D48->unk_004 - B_801D9D48->unk_000;
    sp20 = B_801D9D48->unk_00C - B_801D9D48->unk_008;
    sp18 = B_801D9D48->unk_008 + sp24;
    func_801C7C1C((void*)B_801D9D48->unk_008, B_801D9D48->unk_000, sp24);
    bzero((void*)sp18, sp20 - sp24);
    func_800AD4C0(B_801D9D48->unk_010);
    return 0;
}

s32 func_801C7F24(void) {
    uintptr_t temp_a0;
    struct_801D9C30* temp_v0;

    if (B_801D9D48 == 0) {
        return -1;
    }

    // Function from code
    func_800AD51C();

    temp_v0 = B_801D9D48;
    temp_a0 = temp_v0->unk_008;
    bzero((void*)temp_a0, temp_v0->unk_00C - temp_a0);
    bzero(B_801D9D48, sizeof(struct_801D9C30));
    B_801D9D48 = 0;
    return 0;
}

void n64dd_SetDiskVersion(s32 arg0) {
    if (arg0 != 0) {
        if (B_801D9D48 == 0) {
            func_801C7E80();
        }
    } else if (B_801D9D48 != 0) {
        func_801C7F24();
    }
}
