#include "Game/Game.hpp"
#include "System/OverlayManager.hpp"
#include "System/Random.hpp"
#include "Unknown/UnkStruct_02049b74.hpp"
#include "Unknown/UnkStruct_02049bd4.hpp"
#include "Unknown/UnkStruct_0204a110.hpp"
#include "Unknown/UnkStruct_0204e64c.hpp"
#include "Unknown/UnkStruct_027e0208.hpp"
#include "Unknown/UnkStruct_ov000_020b50c0.hpp"
#include "versions.h"

#include <nitro/card.h>
#include <nitro/os.h>
#include <nitro/reg.h>

extern "C" void func_020196fc();
extern "C" void FlushGfxQueue();
extern "C" void func_020132c8();
extern "C" void func_020132dc();
extern "C" void func_02013354();
extern "C" void func_0201328c();
extern Mat3p gGeomMatrix;

void Game::func_02013370(unk32 param1) {
    data_0204a110.func_02018c78(param1);
    data_ov000_020b50c0.mUnk_9C = param1;
}

void Game::Run() {
    this->func_ov018_020c48a4();

    do {
        // initialization of the next game mode
        if (this->createCallback != NULL) {
            data_0204999c.func_02013014();

            {
                UnkDataStruct2 local_28(sizeof(GameSaveSlot));
                this->mpSaveSlot = (GameSaveSlot *) local_28.unk_00;

                if (this->mpCurrentGameMode != NULL) {
                    delete this->mpCurrentGameMode;
                }

                func_020196fc();
                data_02049b18.func_02013768();
                this->mFrameCounter = REG_FRAME_COUNTER;

                this->mpCurrentGameMode = this->createCallback();
                this->createCallback    = NULL;
                this->mpCurrentGameMode->vfunc_08();
                this->mpSaveSlot = NULL;
            }

            data_0204999c.func_02013070();
        }

        // update of the current game mode
        if (this->mpCurrentGameMode != NULL) {
            if (this->mUnk_08 != NULL) {
                this->mUnk_08();
                this->mUnk_08 = NULL;
            }

            if (CARD_func_0033()) {
                CARD_func_0034();
            }

            data_02049bd4.func_02014d98();
            data_0204a110.func_02019300(data_0204a110.mUnk_DF8);

            gRandom.UpdateRandomValue();

            unk32 uVar4 = data_0204a110.func_02019300(data_0204a110.mUnk_DF8);
            data_02049b74.func_02013a44(data_0204a110.mUnk_004);

            if (this->mUnk_14 == NULL) {
                data_02049b18.func_02013840(data_0204a110.mUnk_004, uVar4);
            } else {
                data_02049b18.func_020138f4(uVar4);
            }

            data_0204e64c.func_020201c4(1);
            data_0204a110.func_02019350();

            if (data_0204a110.func_02019514() == 0 && data_0204e64c.mUnk_00.mUnk_0B == 0) {
                this->mpCurrentGameMode->vfunc_0C();
            }

            data_027e0208.mUnk_0EC = 0x1000;
            data_027e0208.mUnk_0F0 = 0x1000;
            data_027e0208.mUnk_0F4 = 0x1000;

            data_027e0208.mUnk_0E0 = 0;
            data_027e0208.mUnk_0E4 = 0;
            data_027e0208.mUnk_0E8 = 0;

            Mat3p_InitIdentity(&gGeomMatrix);
            data_027e0208.mUnk_0FC = 0;
            FlushGfxQueue();
            this->mpCurrentGameMode->vfunc_18();
            data_0204a110.func_020194dc();

            if (data_0204a110.func_02019514() == 0) {
                this->mpCurrentGameMode->vfunc_10();
            }

#if IS_JP
            FlushGfxQueue();
#endif

            if (data_0204e64c.mUnk_00.mUnk_0B == 0) {
                data_0204a110.func_02019408();
            }

            this->mpCurrentGameMode->vfunc_1C();
            FlushGfxQueue();
            data_0204a110.func_02019454();
            this->mpCurrentGameMode->vfunc_20();

            if (gOverlayManager.mLoadedOverlays[OverlaySlot_Second] == OverlayIndex_Second) {
                this->func_ov000_020576d0();
                this->func_ov000_0205770c();
            }
        }

        if (this->mFrameCounter + data_0204a110.mUnk_004 - (s32) REG_FRAME_COUNTER > 1) {
            func_0201328c();
        }

        while (this->mFrameCounter + data_0204a110.mUnk_004 - (s32) REG_FRAME_COUNTER > 1) {
            func_020132c8();
        }

        {
            int enabled = OS_DisableInterrupts_Irq();
            this->mUnk_1C.func_02013e18(func_020132dc, 0);
            REG_GFX_FIFO_SWAP_BUFFERS = 3;
            OS_RestoreInterrupts(enabled);
        }

        func_020132c8();

        if (this->mUnk_18 != NULL) {
            while (this->mUnk_18() != 0) {
                while (this->mUnk_1C.func_02013e18(func_02013354, 0) == 0) {
                }

                func_020132c8();
            }
        }

        this->mFrameCounter = REG_FRAME_COUNTER;
    } while (true);
}

bool Game::TrySetCreateCallback(GameModeCreateCallback createCallback) {
    if (this->createCallback == NULL) {
        this->createCallback = createCallback;
        return true;
    }

    return false;
}
