#include "level_reset.h"

#include "binary.h"
#include "types.h"
#include "game/camera.h"
#include "game/level_update.h"
#include "game/envfx_snow.h"
#include "object_constants.h"
#include "libc/stddef.h"

#include "cfg.h"
#include "timer.h"

bool gPraccyComplete = false;
extern void warp_special(s32 arg);
extern u8 gMyStupidLastNodeId;
static bool sTimerRunningDeferred = false;
extern u8 sTransitionColorFadeCount[4];
extern u16 sTransitionTextureFadeCount[2];

#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); \
                (type *)( (char *)__mptr - offsetof(type,member) );})

static void resetCamera()
{
    struct MarioState* m = gMarioStates;
    if (CAMERA_MODE_BEHIND_MARIO  == gCamera->mode
     || CAMERA_MODE_WATER_SURFACE == gCamera->mode
     || CAMERA_MODE_INSIDE_CANNON == gCamera->mode
     || CAMERA_MODE_CLOSE         == gCamera->mode
     || CAMERA_MODE_C_UP          == gCamera->mode)
    {
        set_camera_mode(m->area->camera, m->area->camera->defMode, 1);
    }
    
    m->area->camera->cutscene = 0;
}

static void resetTransition()
{
    for (int i = 0; i < 4; i++)
        sTransitionColorFadeCount[i] = 0;
}

extern u8 g100CoinStarSpawned;
extern s8  gRedCoinsCollected;

extern int gWarpTimerOffset;
static void miniResetCommon()
{
    
    gWarpTimerOffset = gGlobalTimer;
    if (gCurrLevelNum == 0x10 &&
        gCurrAreaIndex == 0x01 &&
        gMyStupidLastNodeId == 0x00) {
        warp_special(WARP_SPECIAL_FULLRESET);
        return;
    }
    g100CoinStarSpawned = FALSE;
    gRedCoinsCollected = 0;
    gMarioStates->health = 0x880;
    gHudDisplay.coins = 0;
    gMarioStates->numCoins = 0;
    gSnowParticleCount = 5;
    gHudDisplay.timer = 0;
    sTimerRunning = true;
    Timer_reset();
    sWarpDest.type = 2;
    resetCamera();
    resetTransition();
}

static void resetCommon()
{
    miniResetCommon();
    sTimerRunningDeferred = true;
}

void warp_thing() {
    gMarioStates->health = 0x880;
    sCurrPlayMode = 0x4;
    gHudDisplay.timer = 0;
    sTimerRunning = true;
    sTimerRunningDeferred = true;
    Timer_reset();
    resetCamera();
}


#define ARRAY_SIZEE(x) (sizeof(x) / sizeof(*x))
struct WarpDest gPraccyWarps[] = {
    { 2, 0x10, 0x01, 0x00, 0 },
    { 2, 0x10, 0x01, 0xAA, 0 },
    { 2, 0x10, 0x01, 0x32, 0 },
    { 2, 0x1A, 0x05, 0x14, 0 },
    { 2, 0x06, 0x02, 0x14, WARP_FLAG_DOOR_FLIP_MARIO },
    { 2, 0x06, 0x02, 0x1E, 0 },
    { 2, 0x06, 0x02, 0x32, 0 },
    { 2, 0x06, 0x02, 0x8C, 0 },
    { 2, 0x06, 0x02, 0x28, 0 },
    { 2, 0x06, 0x02, 0x64, 0 },
    { 2, 0x1A, 0x01, 0xA, 0 },
    { 2, 0x1A, 0x01, 0x46, 0 },
    { 2, 0x1A, 0x01, 0x28, 0 },
    { 2, 0x1A, 0x02, 0x14, 0 },
    { 2, 0x1A, 0x03, 0x32, WARP_FLAG_DOOR_FLIP_MARIO },
    { 2, 0x1A, 0x04, 0x3C, 0 },
    { 2, 0x1A, 0x04, 0x28, 0 }
};

size_t gPraccySequence[] = {
    0,  // Init -> US
    0,  // US -> Reset
    0,  // RRD -> Reset
    1,  // MHV -> B1
    0,  // B1 -> Trans
    3,  // Trans -> OW2
    4,  // OW2 -> AET
    0,  // AET -> Reset
    0,  // WC -> Reset
    0,  // PHH -> Reset
    2,  // TTT -> Trans
    3,  // Trans -> OW2
    4,  // OW2 -> SI
    5,  // SI
    6,  // MM
    7,  // B2
    8,  // BLL
    9,  // SSL
    10, // OW3
    12, // PDT
    11, // SW
    13, // Hallway
    14, // Hallway2
    15, // CS entrance
    16, // CS exit
};

size_t gWarpSeqCtr = 0;


void fuckyWarpCommon(uint8_t warpId) {
    struct WarpDest newDest = gPraccyWarps[warpId];
    if (newDest.levelNum == 0x10 &&
        newDest.areaIdx == 0x01 &&
        newDest.nodeId == 0x00) {
        warp_special(WARP_SPECIAL_FULLRESET);
    } else {
        sWarpDest = newDest;
        warp_thing();
    }
}

extern s32 gTimerOffset;
extern s32 lvl_show_time(UNUSED s16 initOrUpdate, UNUSED s32 levelNum);
extern void calc_igt();
void warpSeqCommon() {
    if (gPraccyComplete) {
        gPraccyComplete = !gPraccyComplete;
    }
    if (Config_fuckyWarpsMode == FUCKYWARPS_MODE_SEQ)  {
        if (gWarpSeqCtr == (ARRAY_SIZEE(gPraccySequence)-1)) {
            calc_igt();
            gTimerOffset = gGlobalTimer;
            gPraccyComplete = true;
        }
        gWarpSeqCtr = (gWarpSeqCtr + 1) % ARRAY_SIZEE(gPraccySequence);
    }
    fuckyWarpCommon(gPraccySequence[gWarpSeqCtr]);
    
}


void LevelReset_onNormal()
{
    if (gPraccyComplete) {
        lvl_show_time(0,0);
    }
        
    if (sTimerRunningDeferred)
    {
        sTimerRunningDeferred = false;
        miniResetCommon();
    }

    Config_ButtonAction action = Config_action();
    if (Config_ButtonAction_LEVEL_RESET == action)
    {
        resetCommon();
    }
    
    if (Config_ButtonAction_LEVEL_RESET_WARP == action)
    {
        gWarpSeqCtr = -1;
        gTimerOffset = gGlobalTimer;
        warpSeqCommon();
    }

    if (Config_ButtonAction_ACT_SELECT == action)
    {
        sWarpDest.type = 2;
        sWarpDest.areaIdx = 1;
        sWarpDest.nodeId = 0xa;
        gMarioStates->health = 0x880;
        sCurrPlayMode = 0x4;
        gHudDisplay.timer = 0;
        sTimerRunning = true;
        sTimerRunningDeferred = true;
    }

    LevelConv_PlainLevels warp = Config_warpIdAndReset();
    if (warp != LevelConv_PlainLevels_OFF)
    {
        LevelConv_SM64Levels sm64lvl = LevelConv_toSM64Level(warp);
        
        sWarpDest.levelNum = (u8) sm64lvl;
        sWarpDest.type = 2;
        sWarpDest.areaIdx = 1;
        sWarpDest.nodeId = 0xa;
        warp_thing();
    }
    uint8_t warp2 = Config_praccyWarpIdAndReset();
    if (warp2 > 0) {
        fuckyWarpCommon(warp2-1);
    }
    uint8_t warp3 = Config_praccyWarpSeqIdAndReset();
    if (warp3 > 0) {
        gWarpSeqCtr = warp3-((Config_fuckyWarpsMode == FUCKYWARPS_MODE_SEQ) ? 2 : 1);
        warpSeqCommon();
    }
}

static inline bool isScroll(struct SpawnInfo* spawnInfo)
{
    (void) spawnInfo;

#ifdef BINARY
    // TODO: This is very hacky, do a more careful check
    if (spawnInfo->behaviorScript == (void*) 0x401700)
        return true;
#endif

    return false;
}

s32 LevelReset_onSpawnObjectsFromInfoHook(struct SpawnInfo* spawnInfo)
{
    if (sTimerRunningDeferred && !isScroll(spawnInfo))
    {
        spawnInfo->behaviorArg &= ~(RESPAWN_INFO_DONT_RESPAWN << 8);
        return true;
    }

    return (spawnInfo->behaviorArg & (RESPAWN_INFO_DONT_RESPAWN << 8)) != (RESPAWN_INFO_DONT_RESPAWN << 8);
}

#ifdef BINARY
void LevelReset_setObjectRespawnInfoBits(struct Object *obj, u8 bits) 
{
    switch (obj->respawnInfoType) 
    {
        case RESPAWN_INFO_TYPE_32:
        {
            u32* info32 = (u32 *) obj->respawnInfo;
            struct SpawnInfo* spawnInfo = container_of(info32, struct SpawnInfo, behaviorArg);
            if (!isScroll(spawnInfo))
            {
                *info32 |= bits << 8;
            }
        }
        break;

        case RESPAWN_INFO_TYPE_16:
        {
            u16* info16 = (u16 *) obj->respawnInfo;
            *info16 |= bits << 8;
        }
        break;
    }
}
#endif
