#include "global.h"
#include "gflib.h"
#include "task.h"
#include "menu.h"
#include "menu_helpers.h"
#include "text_window.h"
#include "event_data.h"
#include "script.h"
#include "overworld.h"
#include "field_fadetransition.h"
#include "field_weather.h"
#include "event_object_movement.h"
#include "event_object_lock.h"
#include "field_player_avatar.h"
#include "item.h"
#include "region_map.h"
#include "map_name_popup.h"
#include "wild_encounter.h"
#include "help_system.h"
#include "pokemon_storage_system.h"
#include "save.h"
#include "quest_log_objects.h"
#include "quest_log_player.h"
#include "quest_log.h"
#include "new_menu_helpers.h"
#include "strings.h"
#include "constants/event_objects.h"
#include "constants/maps.h"
#include "constants/quest_log.h"
#include "constants/field_weather.h"
#include "constants/event_object_movement.h"

struct TrainerFanClub
{
    u8 timer:7;
    u8 gotInitialFans:1;
    u8 fanFlags;
};

#define TRAINER_FAN_CLUB ((struct TrainerFanClub *)GetVarPointer(VAR_FANCLUB_FAN_COUNTER))

#define GET_TRAINER_FAN_CLUB_FLAG(flag) (fanClub->fanFlags >> (flag) & 1)
#define SET_TRAINER_FAN_CLUB_FLAG(flag) (fanClub->fanFlags |= 1 << (flag))
#define FLIP_TRAINER_FAN_CLUB_FLAG(flag)(fanClub->fanFlags ^= 1 << (flag))

struct UnkStruct_203AE94
{
    u8 playbackSubstate:4;
    u8 playingEvent:2;
    u8 sceneEndMode:2;
    u8 cursor;
    u8 timer;
    u8 overlapTimer;
};

struct FlagOrVarRecord
{
    u16 idx:15;
    u16 isFlag:1;
    u16 value;
};

u8 gQuestLogPlaybackState;
u16 sNumEventsInLogEntry;
struct FieldInput gQuestLogFieldInput;
struct QuestLogEntry * sCurQuestLogEntry;

static struct FlagOrVarRecord * sFlagOrVarRecords;
static u16 sNumFlagsOrVars;

static EWRAM_DATA u8 sCurrentSceneNum = 0;
static EWRAM_DATA u8 sNumScenes = 0;
EWRAM_DATA u8 gQuestLogState = 0;
static EWRAM_DATA u16 gUnknown_203ADFC = 0;
static EWRAM_DATA u8 sQuestLogHeaderWindowIds[3] = {0};
EWRAM_DATA u16 *gUnknown_203AE04 = NULL;
EWRAM_DATA u16 *sEventRecordingPointer = NULL;
static EWRAM_DATA u16 *gUnknown_203AE0C[32] = {NULL};
static EWRAM_DATA void (* sQuestLogCB)(void) = NULL;
static EWRAM_DATA u16 *sPalettesBackup = NULL;
static EWRAM_DATA struct UnkStruct_203AE94 sQuestLogCurrentScene = {0};
static EWRAM_DATA struct QuestLogEntry sQuestLogSceneRecordBuffer[32] = {0};
EWRAM_DATA u16 sQuestLogCursor = 0;
static EWRAM_DATA u8 sMovementScripts[64][2] = {{0}};
static EWRAM_DATA u16 sNextStepDelay = 0;
static EWRAM_DATA u16 sLastQuestLogCursor = 0;
static EWRAM_DATA u16 sFlagOrVarPlayhead = 0;

static void QLogCB_Recording(void);
static void QLogCB_Playback(void);
static void SetPlayerInitialCoordsAtScene(u8);
static void SetNPCInitialCoordsAtScene(u8);
static void TryRecordEvent39_GoToNextScene(void);
static void BackUpTrainerRematchesToVars(void);
static void BackUpMapLayoutToVar(void);
static void SetGameStateAtScene(u8);
static u8 TryRecordQuestLogEntrySequence(struct QuestLogEntry *);
static void Task_BeginQuestLogPlayback(u8);
static void QuestLogPlaybackSetObjectEventTemplates(u8);
static void QLPlayback_InitOverworldState(void);
static void QuestLog_GetSaneMonCounts(void);
static u16 QuestLog_GetSanePartyCount(void);
static u16 QuestLog_GetSaneBoxCount(void);
static void sub_8111688(void);
static void ReadQuestLogScriptFromSav1(u8, struct QuestLogEntry *);
static void QuestLog_BeginFadeAtEndOfScene(s8 delay);
static void QuestLog_AdvancePlayhead(void);
static void QuestLog_StartFinalScene(void);
static void Task_RunPlaybackCB(u8);
static void QuestLog_PlayCurrentEvent(void);
static void HandleShowQuestLogMessage(void);
static u8 GetQuestLogTextDisplayDuration(void);
static void DrawQuestLogSceneDescription(void);
static void sub_8111D90(u8);
static void QuestLog_CloseTextWindow(void);
static void QuestLog_SkipToEndOfPlayback(s8 delay);
static void QuestLog_WaitFadeAndCancelPlayback(void);
static bool8 FieldCB2_FinalScene(void);
static void Task_FinalScene_WaitFade(u8);
static void Task_QuestLogScene_SavedGame(u8);
static void Task_WaitAtEndOfQuestLog(u8);
static void Task_EndQuestLog(u8);
static bool8 sub_81121D8(u8);
static void QL_SlightlyDarkenSomePals(void);
static void TogglePlaybackStateForOverworldLock(u8);
static void SetUpQuestLogEntry(u8, struct QuestLogEntry *, u16);
static bool8 RecordHeadAtEndOfEntryOrScriptContext2Enabled(void);
static bool8 RecordHeadAtEndOfEntry(void);
static void TryLoseFansFromPlayTimeAfterLinkBattle(struct TrainerFanClub *);
static void UpdateTrainerFanClubGameClear(struct TrainerFanClub *);
static u8 PlayerGainRandomTrainerFan(struct TrainerFanClub *);
static u16 GetNumFansOfPlayerInTrainerFanClub(struct TrainerFanClub *);
static void TryLoseFansFromPlayTime(struct TrainerFanClub *);
static bool16 IsFanClubMemberFanOfPlayer(struct TrainerFanClub *);
static void SetInitialFansOfPlayer(struct TrainerFanClub *);
static void BufferFanClubTrainerName(struct LinkBattleRecords *, u8, u8);
static void UpdateTrainerFansAfterLinkBattle(struct TrainerFanClub *);
static bool8 DidPlayerGetFirstFans(struct TrainerFanClub * );
static void SetPlayerGotFirstFans(struct TrainerFanClub *);
static bool8 InQuestLogDisabledLocation(void);
static bool8 TrySetLinkQuestLogEvent(u16, const u16 *);
static bool8 TrySetTrainerBattleQuestLogEvent(u16, const u16 *);

static const struct WindowTemplate sQuestLogHeaderWindowTemplates[3] = {
    { 0, 0,  0, 30, 2, 15, 0x0e9 },
    { 0, 0, 18, 30, 2, 15, 0x0ad },
    { 0, 0, 14, 30, 6, 15, 0x14c }
};

static const u8 sTextColors[3] = {TEXT_DYNAMIC_COLOR_6, TEXT_COLOR_WHITE, TEXT_DYNAMIC_COLOR_3};

static const u16 sUnknown_8456638[] = INCBIN_U16("graphics/unknown/unknown_8456638.bin");

static const u8 sQuestLogTextLineYCoords[] = {17, 10, 3};

void SetQuestLogRecordAndPlaybackPointers(void *oldPointer)
{
    ptrdiff_t offset = (void *)gSaveBlock1Ptr - oldPointer;
    if (gUnknown_203AE04)
        gUnknown_203AE04 = (void *)gUnknown_203AE04 + offset;
    if (gQuestLogState != 0)
    {
        if (sEventRecordingPointer)
            sEventRecordingPointer = (void *)sEventRecordingPointer + offset;
        if (gQuestLogState == QL_STATE_PLAYBACK)
        {
            int r3;
            for (r3 = 0; r3 < (int)NELEMS(gUnknown_203AE0C); r3++)
                if (gUnknown_203AE0C[r3])
                    gUnknown_203AE0C[r3] = (void *)gUnknown_203AE0C[r3] + offset;
        }
    }
}

void ResetQuestLog(void)
{
    memset(gSaveBlock1Ptr->questLog, 0, sizeof(gSaveBlock1Ptr->questLog));
    sCurrentSceneNum = 0;
    gQuestLogState = 0;
    sQuestLogCB = NULL;
    sEventRecordingPointer = NULL;
    gUnknown_203AE04 = NULL;
    sub_8113BD8();
    ResetDeferredLinkEvent();
}

static void DestroySav1QuestLogEntry(u8 a0)
{
    memset(gSaveBlock1Ptr->questLog + a0, 0, sizeof(struct QuestLog));
    gUnknown_203AE04 = NULL;
}

void sub_8110920(void)
{
    gUnknown_203AE04 = NULL;
}

void RunQuestLogCB(void)
{
    if (sQuestLogCB != NULL)
        sQuestLogCB();
}

bool8 sub_8110944(const void *a0, size_t cmdSize)
{
    void *r2 = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
    void *r0 = gSaveBlock1Ptr->questLog[sCurrentSceneNum].end;
    r0 -= cmdSize;
    if ((const void *)a0 < r2 || (const void *)a0 > r0)
        return FALSE;
    return TRUE;
}

bool8 WillCommandOfSizeFitInSav1Record(u16 *cursor, size_t size)
{
    void *start = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
    void *end = gSaveBlock1Ptr->questLog[sCurrentSceneNum].end;
    end -= size;
    if ((void *)cursor < start || (void *)cursor > end)
        return FALSE;
    return TRUE;
}

static void SetQuestLogState(u8 state)
{
    gQuestLogState = state;
    if (state == QL_STATE_RECORDING)
        sQuestLogCB = QLogCB_Recording;
    else
        sQuestLogCB = QLogCB_Playback;
}

static void QLogCB_Recording(void)
{
    if (TryRecordQuestLogEntrySequence(sQuestLogSceneRecordBuffer) != 1)
    {
        gQuestLogPlaybackState = 0;
        TryRecordEvent39_GoToNextScene();
        gQuestLogState = 0;
        sQuestLogCB = NULL;
    }
}

static void QLogCB_Playback(void)
{
    if (sQuestLogCurrentScene.playbackSubstate == 2)
        sQuestLogCurrentScene.playbackSubstate = 0;

    if (sQuestLogCurrentScene.sceneEndMode == 0)
    {
        if (gQuestLogPlaybackState != 0 
         || sQuestLogCurrentScene.playbackSubstate == 1 
         || (sQuestLogCurrentScene.cursor < NELEMS(gUnknown_203AE0C) 
          && gUnknown_203AE0C[sQuestLogCurrentScene.cursor] != NULL))
            QuestLog_PlayCurrentEvent();
        else
        {
            sQuestLogCurrentScene.sceneEndMode = 2;
            ScriptContext2_Enable();
            QuestLog_BeginFadeAtEndOfScene(0);
        }
    }
}

void GetQuestLogState(void)
{
    gSpecialVar_Result = gQuestLogState;
}

u8 GetQuestLogStartType(void)
{
    return gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType;
}

void StartRecordingQuestLogEntry(u16 eventId)
{
    if (sCurrentSceneNum >= QUEST_LOG_SCENE_COUNT)
        sCurrentSceneNum = 0;

    DestroySav1QuestLogEntry(sCurrentSceneNum);
    ResetUnk203B044();
    sEventRecordingPointer = gSaveBlock1Ptr->questLog[sCurrentSceneNum].script;
    if (IS_LINK_QL_EVENT(eventId) || eventId == QL_EVENT_DEPARTED)
        gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType = QL_START_WARP;
    else
        gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType = QL_START_NORMAL;
    QuestLog_GetSaneMonCounts();
    SetPlayerInitialCoordsAtScene(sCurrentSceneNum);
    SetNPCInitialCoordsAtScene(sCurrentSceneNum);
    BackUpTrainerRematchesToVars();
    BackUpMapLayoutToVar();
    SetGameStateAtScene(sCurrentSceneNum);
    gUnknown_203ADFC = 0;
    SetUpQuestLogEntry(2, sQuestLogSceneRecordBuffer, 0x100);
    TryRecordQuestLogEntrySequence(sQuestLogSceneRecordBuffer);
    SetQuestLogState(QL_STATE_RECORDING);
}

static void SetPlayerInitialCoordsAtScene(u8 sceneNum)
{
    struct QuestLog * questLog = &gSaveBlock1Ptr->questLog[sceneNum];
    questLog->mapGroup = gSaveBlock1Ptr->location.mapGroup;
    questLog->mapNum = gSaveBlock1Ptr->location.mapNum;
    questLog->warpId = gSaveBlock1Ptr->location.warpId;
    questLog->x = gSaveBlock1Ptr->pos.x;
    questLog->y = gSaveBlock1Ptr->pos.y;
}

static void SetNPCInitialCoordsAtScene(u8 sceneNum)
{
    struct QuestLog * questLog = &gSaveBlock1Ptr->questLog[sceneNum];
    u16 i;

    SetQuestLogObjectEventsData(questLog);

    for (i = 0; i < NELEMS(gSaveBlock1Ptr->objectEventTemplates); i++)
    {
        if (gSaveBlock1Ptr->objectEventTemplates[i].x < 0)
        {
            questLog->npcData[i].x = -1 * gSaveBlock1Ptr->objectEventTemplates[i].x;
            questLog->npcData[i].negx = TRUE;
        }
        else
        {
            questLog->npcData[i].x = (u8)gSaveBlock1Ptr->objectEventTemplates[i].x;
            questLog->npcData[i].negx = FALSE;
        }
        if (gSaveBlock1Ptr->objectEventTemplates[i].y < 0)
        {
            questLog->npcData[i].y = (-gSaveBlock1Ptr->objectEventTemplates[i].y << 24) >> 24;
            questLog->npcData[i].negy = TRUE;
        }
        else
        {
            questLog->npcData[i].y = (u8)gSaveBlock1Ptr->objectEventTemplates[i].y;
            questLog->npcData[i].negy = FALSE;
        }
        questLog->npcData[i].elevation = gSaveBlock1Ptr->objectEventTemplates[i].elevation;
        questLog->npcData[i].movementType = gSaveBlock1Ptr->objectEventTemplates[i].movementType;
    }
}

static void SetGameStateAtScene(u8 sceneNum)
{
    struct QuestLog * questLog = &gSaveBlock1Ptr->questLog[sceneNum];

    CpuCopy16(gSaveBlock1Ptr->flags, questLog->flags, NUM_FLAG_BYTES * sizeof(u8));
    CpuCopy16(gSaveBlock1Ptr->vars, questLog->vars, VARS_COUNT * sizeof(u16));
}

static void BackUpTrainerRematchesToVars(void)
{
    u16 i, j;
    u16 sp0[4];

    for (i = 0; i < 4; i++)
    {
        sp0[i] = 0;
        for (j = 0; j < 16; j++)
        {
            if (gSaveBlock1Ptr->trainerRematches[16 * i + j])
            {
                sp0[i] += (1 << j);
            }
        }
        VarSet(VAR_QLBAK_TRAINER_REMATCHES + i, sp0[i]);
    }
}

static void BackUpMapLayoutToVar(void)
{
    VarSet(VAR_QLBAK_MAP_LAYOUT, gSaveBlock1Ptr->mapLayoutId);
}

static void TryRecordEvent39_GoToNextScene(void)
{
    TryRecordEvent39_NoParams(sEventRecordingPointer);
    if (++sCurrentSceneNum >= QUEST_LOG_SCENE_COUNT)
        sCurrentSceneNum = 0;
}

static bool8 TryRecordQuestLogEntrySequence(struct QuestLogEntry * entry)
{
    u16 i;

    for (i = gUnknown_203ADFC; i < sQuestLogCursor; i++)
    {
        if (sEventRecordingPointer == NULL)
            return FALSE;
        switch (entry[i].command)
        {
        case 0:
        case 1:
            sEventRecordingPointer = sub_8113D48(sEventRecordingPointer, &entry[i]);
            break;
        default:
            sEventRecordingPointer = sub_8113CC8(sEventRecordingPointer, &entry[i]);
            break;
        }
        if (sEventRecordingPointer == NULL)
        {
            gQuestLogPlaybackState = 0;
            return FALSE;
        }
    }

    if (gQuestLogPlaybackState == 0)
    {
        sEventRecordingPointer = TryRecordEvent39_NoParams(sEventRecordingPointer);
        return FALSE;
    }
    gUnknown_203ADFC = sQuestLogCursor;
    return TRUE;
}

void TrySetUpQuestLogScenes_ElseContinueFromSave(u8 taskId)
{
    u8 i;

    QL_EnableRecordingSteps();
    sNumScenes = 0;
    for (i = 0; i < QUEST_LOG_SCENE_COUNT; i++)
    {
        if (gSaveBlock1Ptr->questLog[i].startType != 0)
            sNumScenes++;
    }

    if (sNumScenes != 0)
    {
        gHelpSystemEnabled = FALSE;
        Task_BeginQuestLogPlayback(taskId);
        DestroyTask(taskId);
    }
    else
    {
        SetMainCallback2(CB2_ContinueSavedGame);
        DestroyTask(taskId);
    }
}

static void Task_BeginQuestLogPlayback(u8 taskId)
{
    gSaveBlock1Ptr->location.mapGroup = MAP_GROUP(ROUTE1);
    gSaveBlock1Ptr->location.mapNum =  MAP_NUM(ROUTE1);
    gSaveBlock1Ptr->location.warpId = -1;
    sCurrentSceneNum = 0;
    gDisableMapMusicChangeOnMapLoad = 1;
    DisableWildEncounters(TRUE);
    QLPlayback_InitOverworldState();
}

void sub_8110FCC(void)
{
    ReadQuestLogScriptFromSav1(sCurrentSceneNum, sQuestLogSceneRecordBuffer);
    ResetUnk203B044();
    SetUpQuestLogEntry(1, sQuestLogSceneRecordBuffer, 0x100);
    QuestLogPlaybackSetObjectEventTemplates(sCurrentSceneNum);
}

static bool8 FieldCB2_QuestLogStartPlaybackWithWarpExit(void)
{
    LoadPalette(stdpal_get(4), 0xF0, 0x20);
    SetQuestLogState(QL_STATE_PLAYBACK);
    FieldCB_DefaultWarpExit();
    sQuestLogCurrentScene = (struct UnkStruct_203AE94){};
    sQuestLogCurrentScene.playbackSubstate = 2;
    return 1;
}

static bool8 FieldCB2_QuestLogStartPlaybackStandingInPlace(void)
{
    LoadPalette(stdpal_get(4), 0xF0, 0x20);
    SetQuestLogState(QL_STATE_PLAYBACK);
    FieldCB_WarpExitFadeFromBlack();
    sQuestLogCurrentScene = (struct UnkStruct_203AE94){};
    sQuestLogCurrentScene.playbackSubstate = 2;
    return 1;
}

void DrawPreviouslyOnQuestHeader(u8 sceneNum)
{
    u8 i;

    for (i = 0; i < NELEMS(sQuestLogHeaderWindowTemplates); i++)
    {
        sQuestLogHeaderWindowIds[i] = AddWindow(&sQuestLogHeaderWindowTemplates[i]);
        FillWindowPixelRect(sQuestLogHeaderWindowIds[i], 15, 0, 0, sQuestLogHeaderWindowTemplates[i].width * 8, sQuestLogHeaderWindowTemplates[i].height * 8);
    }

    StringExpandPlaceholders(gStringVar4, gText_QuestLog_PreviouslyOnYourQuest);

    // Scene numbers count from 4 to 0, 0 being where the player saved
    if (sceneNum != 0)
    {
        ConvertIntToDecimalStringN(gStringVar1, sceneNum, STR_CONV_MODE_LEFT_ALIGN, 1);
        StringAppend(gStringVar4, gStringVar1);
    }

    AddTextPrinterParameterized4(sQuestLogHeaderWindowIds[0], FONT_2, 2, 2, 1, 2, sTextColors, 0, gStringVar4);
    PutWindowTilemap(sQuestLogHeaderWindowIds[0]);
    PutWindowTilemap(sQuestLogHeaderWindowIds[1]);
    CopyWindowToVram(sQuestLogHeaderWindowIds[0], COPYWIN_GFX);
    CopyWindowToVram(sQuestLogHeaderWindowIds[2], COPYWIN_GFX);
    CopyWindowToVram(sQuestLogHeaderWindowIds[1], COPYWIN_FULL);
}

void CommitQuestLogWindow1(void)
{
    PutWindowTilemap(sQuestLogHeaderWindowIds[1]);
    CopyWindowToVram(sQuestLogHeaderWindowIds[1], COPYWIN_MAP);
}

static void QuestLogPlaybackSetObjectEventTemplates(u8 sceneNum)
{
    struct QuestLog *questLog = &gSaveBlock1Ptr->questLog[sceneNum];
    u16 i;
    
    for (i = 0; i < 64; i++)
    {
        if (questLog->npcData[i].negx)
            gSaveBlock1Ptr->objectEventTemplates[i].x = -questLog->npcData[i].x;
        else
            gSaveBlock1Ptr->objectEventTemplates[i].x = questLog->npcData[i].x;
        if (questLog->npcData[i].negy)
            gSaveBlock1Ptr->objectEventTemplates[i].y = -(u8)questLog->npcData[i].y;
        else
            gSaveBlock1Ptr->objectEventTemplates[i].y = questLog->npcData[i].y;
        gSaveBlock1Ptr->objectEventTemplates[i].elevation = questLog->npcData[i].elevation;
        gSaveBlock1Ptr->objectEventTemplates[i].movementType = questLog->npcData[i].movementType;
    }

    SetSav1ObjectEventsFromQuestLog(questLog, gSaveBlock1Ptr->objectEventTemplates);
}

static void QLPlayback_SetInitialPlayerPosition(u8 sceneNum, bool8 isWarp)
{
    struct WarpData sp0;
    
    if (!isWarp)
    {
        gSaveBlock1Ptr->location.mapGroup = gSaveBlock1Ptr->questLog[sceneNum].mapGroup;
        gSaveBlock1Ptr->location.mapNum = gSaveBlock1Ptr->questLog[sceneNum].mapNum;
        gSaveBlock1Ptr->location.warpId = gSaveBlock1Ptr->questLog[sceneNum].warpId;
        gSaveBlock1Ptr->pos.x = gSaveBlock1Ptr->questLog[sceneNum].x;
        gSaveBlock1Ptr->pos.y = gSaveBlock1Ptr->questLog[sceneNum].y;
    }
    else
    {
        sp0.mapGroup = gSaveBlock1Ptr->questLog[sceneNum].mapGroup;
        sp0.mapNum = gSaveBlock1Ptr->questLog[sceneNum].mapNum;
        sp0.warpId = gSaveBlock1Ptr->questLog[sceneNum].warpId;
        sp0.x = gSaveBlock1Ptr->questLog[sceneNum].x;
        sp0.y = gSaveBlock1Ptr->questLog[sceneNum].y;
        Overworld_SetWarpDestinationFromWarp(&sp0);
    }
}

static void QLPlayback_InitOverworldState(void)
{
    gQuestLogState = QL_STATE_PLAYBACK;
    ResetSpecialVars();
    ClearBag();
    ClearPCItemSlots();
    if (GetQuestLogStartType() == QL_START_NORMAL)
    {
        QLPlayback_SetInitialPlayerPosition(sCurrentSceneNum, FALSE);
        gFieldCallback2 = FieldCB2_QuestLogStartPlaybackStandingInPlace;
        SetMainCallback2(CB2_SetUpOverworldForQLPlayback);
    }
    else
    {
        QLPlayback_SetInitialPlayerPosition(sCurrentSceneNum, TRUE);
        WarpIntoMap();
        gFieldCallback2 = FieldCB2_QuestLogStartPlaybackWithWarpExit;
        SetMainCallback2(CB2_SetUpOverworldForQLPlaybackWithWarpExit);
    }
}

void sub_81113E4(void)
{
    struct QuestLog * questLog = &gSaveBlock1Ptr->questLog[sCurrentSceneNum];

    CpuCopy16(questLog->flags, gSaveBlock1Ptr->flags, NUM_FLAG_BYTES * sizeof(u8));
    CpuCopy16(questLog->vars, gSaveBlock1Ptr->vars, VARS_COUNT * sizeof(u16));
    sub_8111688();
}

struct PokemonAndSomethingElse
{
    struct Pokemon mon;
    u16 sanePartyCount;
    u16 saneBoxesCount;
};

void sub_8111438(void)
{
    struct PokemonAndSomethingElse *r9 = AllocZeroed(sizeof(struct PokemonAndSomethingElse));
    u16 r0, r3, r5, r6;

    CreateMon(&r9->mon, SPECIES_RATTATA, 1, 0x20, FALSE, 0, 0, 0);
    r0 = VarGet(VAR_QUEST_LOG_MON_COUNTS);
    r9->sanePartyCount = r0 >> 12;
    r9->saneBoxesCount = r0 % 0x1000;

    r5 = QuestLog_GetSanePartyCount();
    if (r5 > r9->sanePartyCount)
    {
        for (r3 = 0; r3 < r5 - r9->sanePartyCount; r3++)
        {
            ZeroMonData(&gPlayerParty[5 - r3]);
        }
    }
    else if (r5 < r9->sanePartyCount)
    {
        for (r3 = 0; r3 < 5; r3++)
        {
            ZeroBoxMonAt(0, r3);
        }
        for (r3 = r5; r3 < r9->sanePartyCount; r3++)
        {
            CopyMon(&gPlayerParty[r3], &r9->mon, sizeof(struct Pokemon));
        }
    }

    r5 = QuestLog_GetSaneBoxCount();
    if (r5 > r9->saneBoxesCount)
    {
        for (r3 = 0; r3 < 14; r3++)
        {
            for (r6 = 0; r6 < 30; r6++)
            {
                if (GetBoxMonDataAt(r3, r6, MON_DATA_SANITY_HAS_SPECIES))
                {
                    ZeroBoxMonAt(r3, r6);
                    r5--;
                    if (r5 == r9->saneBoxesCount)
                        break;
                }
            }
            if (r5 == r9->saneBoxesCount)
                break;
        }
    }
    else if (r5 < r9->saneBoxesCount)
    {
        for (r3 = 0; r3 < TOTAL_BOXES_COUNT; r3++)
        {
            for (r6 = 0; r6 < IN_BOX_COUNT; r6++)
            {
                struct BoxPokemon * boxMon = GetBoxedMonPtr(r3, r6);
                if (!GetBoxMonData(boxMon, MON_DATA_SANITY_HAS_SPECIES))
                {
                    CopyMon(boxMon, &r9->mon.box, sizeof(struct BoxPokemon));
                    r5++;
                    if (r5 == r9->saneBoxesCount)
                        break;
                }
            }
            if (r5 == r9->saneBoxesCount)
                break;
        }
    }

    Free(r9);
}

static void QuestLog_GetSaneMonCounts(void)
{
    u16 partyCount = QuestLog_GetSanePartyCount();
    u16 boxesCount = QuestLog_GetSaneBoxCount();
    VarSet(VAR_QUEST_LOG_MON_COUNTS, (partyCount << 12) + boxesCount);
}

static u16 QuestLog_GetSanePartyCount(void)
{
    u16 count = 0;
    u16 i;

    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (GetMonData(&gPlayerParty[i], MON_DATA_SANITY_HAS_SPECIES))
            count++;
    }

    return count;
}

static u16 QuestLog_GetSaneBoxCount(void)
{
    u16 count = 0;
    u16 i, j;

    for (i = 0; i < TOTAL_BOXES_COUNT; i++)
    {
        for (j = 0; j < IN_BOX_COUNT; j++)
        {
            if (GetBoxMonDataAt(i, j, MON_DATA_SANITY_HAS_SPECIES))
                count++;
        }
    }

    return count;
}

static void sub_8111688(void)
{
    u16 i, j;
    u16 sp0[4];

    for (i = 0; i < 4; i++)
    {
        sp0[i] = VarGet(VAR_QLBAK_TRAINER_REMATCHES + i);

        for (j = 0; j < 16; j++)
        {
            if (sp0[i] & 1)
                gSaveBlock1Ptr->trainerRematches[16 * i + j] = 30;
            else
                gSaveBlock1Ptr->trainerRematches[16 * i + j] = 0;
            sp0[i] >>= 1;
        }
    }
}

void sub_8111708(void)
{
    struct MapHeader sp0;

    gSaveBlock1Ptr->mapLayoutId = VarGet(VAR_QLBAK_MAP_LAYOUT);
    if (gSaveBlock1Ptr->mapLayoutId == 0)
    {
        sp0 = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
        gSaveBlock1Ptr->mapLayoutId = sp0.mapLayoutId;
    }
}

static void ReadQuestLogScriptFromSav1(u8 sceneNum, struct QuestLogEntry * a1)
{
    u16 i;
    u16 *r4;
    u16 r6 = 0;
    u16 r9 = 0;

    memset(a1, 0, 32 * sizeof(struct QuestLogEntry));
    for (i = 0; i < NELEMS(gUnknown_203AE0C); i++)
    {
        gUnknown_203AE0C[i] = NULL;
    }

    r4 = gSaveBlock1Ptr->questLog[sceneNum].script;
    for (i = 0; i < 32; i++)
    {
        switch (r4[0] & 0xFFF)
        {
        case QL_EVENT_0:
            r4 = sub_8113D08(r4, &a1[r6]);
            r6++;
            break;
        case QL_EVENT_1:
        case QL_EVENT_2:
            r4 = sub_8113D94(r4, &a1[r6]);
            r6++;
            break;
        case QL_EVENT_39:
            r4 = sub_8113C20(r4, &a1[r6]);
            r6++;
            break;
        case QL_EVENT_41:
            r4 = sub_8113C8C(r4, &a1[r6]);
            r6++;
            break;
        default:
            r4 = QuestLog_SkipCommand(r4, &gUnknown_203AE0C[r9]);
            if (r9 == 0)
                sub_8113ABC(gUnknown_203AE0C[0]);
            r9++;
            break;
        }
        if (r4 == NULL)
            break;
    }
}

static void QuestLog_BeginFadeAtEndOfScene(s8 delay)
{
    FadeScreen(FADE_TO_BLACK, delay);
    sQuestLogCB = QuestLog_AdvancePlayhead;
}

static void QuestLog_AdvancePlayhead(void)
{
    if (!gPaletteFade.active)
    {
        ScriptContext2_Enable();
        if (++sCurrentSceneNum < QUEST_LOG_SCENE_COUNT && gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType != 0)
        {
            sNumScenes--;
            QLPlayback_InitOverworldState();
        }
        else
        {
            gQuestLogPlaybackState = 0;
            QuestLog_StartFinalScene();
        }
    }
}

static void QuestLog_StartFinalScene(void)
{
    ResetSpecialVars();
    Save_ResetSaveCounters();
    LoadGameSave(SAVE_NORMAL);
    SetMainCallback2(CB2_EnterFieldFromQuestLog);
    gFieldCallback2 = FieldCB2_FinalScene;
    FreeAllWindowBuffers();
    gQuestLogState = QL_STATE_PLAYBACK_LAST;
    sQuestLogCB = NULL;
}

void QuestLog_AdvancePlayhead_(void)
{
    QuestLog_AdvancePlayhead();
}

bool8 QuestLog_SchedulePlaybackCB(void (*callback)(void))
{
    u8 taskId;

    switch (gQuestLogState)
    {
        case QL_STATE_RECORDING:
            QuestLog_CutRecording();
            break;
        case QL_STATE_PLAYBACK:
            gQuestLogPlaybackState = 3;
            taskId = CreateTask(Task_RunPlaybackCB, 80);
            gTasks[taskId].data[0] = 0;
            gTasks[taskId].data[1] = 0;
            SetWordTaskArg(taskId, 14, (uintptr_t)callback);
            return TRUE;
    }
    return FALSE;
}

static void Task_RunPlaybackCB(u8 taskId)
{
    void (*routine)(void);
    s16 *data = gTasks[taskId].data;

    switch (data[1])
    {
    case 0:
        if (++data[0] == 0x7F)
        {
            BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, 0);
            sQuestLogCurrentScene.sceneEndMode = 2;
            data[1]++;
        }
        break;
    case 1:
        if (!gPaletteFade.active)
        {
            gQuestLogPlaybackState = 0;
            routine = (void (*)(void)) GetWordTaskArg(taskId, 14);
            if (routine != NULL)
                routine();
            DestroyTask(taskId);
            sQuestLogCB = QuestLog_AdvancePlayhead;
        }
        break;
    }
}

static void QuestLog_PlayCurrentEvent(void)
{
    if (sQuestLogCurrentScene.playbackSubstate == 1)
    {
        if (--sQuestLogCurrentScene.timer != 0)
            return;
        sQuestLogCurrentScene.playbackSubstate = 0;
        sQuestLogCurrentScene.playingEvent = 1;
        TogglePlaybackStateForOverworldLock(2);
    }

    if (sQuestLogCurrentScene.playingEvent == 1)
    {
        if (++sQuestLogCurrentScene.overlapTimer > 15)
        {
            QuestLog_CloseTextWindow();
            sQuestLogCurrentScene.playingEvent = 0;
            sQuestLogCurrentScene.overlapTimer = 0;
        }
    }
    if (sQuestLogCurrentScene.cursor < NELEMS(gUnknown_203AE0C))
    {
        if (sub_8113B44(gUnknown_203AE0C[sQuestLogCurrentScene.cursor]) == 1)
            HandleShowQuestLogMessage();
        else if (sub_8113AE8(gUnknown_203AE0C[sQuestLogCurrentScene.cursor]) == 1)
            HandleShowQuestLogMessage();
    }
}

static void HandleShowQuestLogMessage(void)
{
    if (sQuestLogCurrentScene.playbackSubstate == 0)
    {
        sQuestLogCurrentScene.playbackSubstate = 1;
        sQuestLogCurrentScene.playingEvent = 0;
        sQuestLogCurrentScene.overlapTimer = 0;
        sQuestLogCurrentScene.timer = GetQuestLogTextDisplayDuration();
        if (gUnknown_203B044.unk_2 == 0)
            sQuestLogCurrentScene.cursor++;
        if (sQuestLogCurrentScene.cursor > 32)
            return;
        DrawQuestLogSceneDescription();
    }
    TogglePlaybackStateForOverworldLock(1); // lock
}

static u8 GetQuestLogTextDisplayDuration(void)
{
    u16 i;
    u16 count = 0;

    for (i = 0; i < 0x400 && gStringVar4[i] != EOS; i++)
    {
        if (gStringVar4[i] != CHAR_NEWLINE)
            count++;
    }

    if (count < 20)
        return 0x5F;
    if (count < 36)
        return 0x7F;
    if (count < 46)
        return 0xBF;
    return 0xFF;
}

bool8 sub_8111C2C(void)
{
    if (gQuestLogState != QL_STATE_PLAYBACK)
        return FALSE;
    if (gQuestLogPlaybackState == 0 || sQuestLogCurrentScene.playbackSubstate == 1 || sQuestLogCurrentScene.playbackSubstate == 2)
        return TRUE;
    return FALSE;
}

void sub_8111C68(void)
{
    if (sQuestLogCurrentScene.sceneEndMode == 0)
    {
        if (JOY_NEW(A_BUTTON))
        {
            sQuestLogCurrentScene.sceneEndMode = 2;
            gQuestLogPlaybackState = 0;
            QuestLog_BeginFadeAtEndOfScene(-3);
        }
        else if (JOY_NEW(B_BUTTON))
        {
            sQuestLogCurrentScene.sceneEndMode = 1;
            gQuestLogPlaybackState = 0;
            QuestLog_SkipToEndOfPlayback(-3);
        }
    }
}

bool8 QuestLogScenePlaybackIsEnding(void)
{
    if (sQuestLogCurrentScene.sceneEndMode != 0)
        return TRUE;
    return FALSE;
}

void QuestLog_DrawPreviouslyOnQuestHeaderIfInPlaybackMode(void)
{
    if (gQuestLogState == QL_STATE_PLAYBACK)
        DrawPreviouslyOnQuestHeader(sNumScenes);
}

static void DrawQuestLogSceneDescription(void)
{
    u16 i;
    u8 numLines = 0;

    for (i = 0; i < 0x100 && gStringVar4[i] != EOS; i++)
    {
        if (gStringVar4[i] == CHAR_NEWLINE)
            numLines++;
    }

    PutWindowTilemap(sQuestLogHeaderWindowIds[2]);
    sub_8111D90(sQuestLogHeaderWindowIds[2]);
    AddTextPrinterParameterized4(sQuestLogHeaderWindowIds[2], FONT_2, 2, sQuestLogTextLineYCoords[numLines], 1, 0, sTextColors, 0, gStringVar4);
    ScheduleBgCopyTilemapToVram(0);
}

static void sub_8111D90(u8 a0)
{
    const u16 *src = sUnknown_8456638;
    u16 *buffer = Alloc(0x1680);
    u8 i, j, y;

    if (buffer)
    {
        for (i = 0; i < 6; i++)
        {
            switch (i)
            {
            default:
                y = 1;
                break;
            case 0:
                y = 0;
                break;
            case 5:
                y = 2;
                break;
            }

            // r6 = y * 32
            // r5 = 2 * (i % 16)
            // r4 = j
            for (j = 0; j < 30; j++)
            {
                CpuCopy32(src + 16 * y, buffer + 16 * (2 * (15 * i) + j), 32);
            }
        }

        CopyToWindowPixelBuffer(a0, (const u8 *)buffer, 0x1680, 0);
        Free(buffer);
    }
}

static void QuestLog_CloseTextWindow(void)
{
    ClearWindowTilemap(sQuestLogHeaderWindowIds[2]);
    FillWindowPixelRect(sQuestLogHeaderWindowIds[2], 15, 0, 0, 0xf0, 0x30);
    CopyWindowToVram(sQuestLogHeaderWindowIds[2], COPYWIN_GFX);
    PutWindowTilemap(sQuestLogHeaderWindowIds[1]);
    CopyWindowToVram(sQuestLogHeaderWindowIds[1], COPYWIN_MAP);
}

static void QuestLog_SkipToEndOfPlayback(s8 delay)
{
    FadeScreen(FADE_TO_BLACK, delay);
    sQuestLogCB = QuestLog_WaitFadeAndCancelPlayback;
}

static void QuestLog_WaitFadeAndCancelPlayback(void)
{
    if (!gPaletteFade.active)
    {
        ScriptContext2_Enable();
        for (sCurrentSceneNum = sCurrentSceneNum; sCurrentSceneNum < QUEST_LOG_SCENE_COUNT; sCurrentSceneNum++)
        {
            if (gSaveBlock1Ptr->questLog[sCurrentSceneNum].startType == 0)
                break;
            ReadQuestLogScriptFromSav1(sCurrentSceneNum, sQuestLogSceneRecordBuffer);
        }
        gQuestLogPlaybackState = 0;
        QuestLog_StartFinalScene();
    }
}

void QuestLog_InitPalettesBackup(void)
{
    if (gQuestLogState == QL_STATE_PLAYBACK_LAST)
        sPalettesBackup = AllocZeroed(PLTT_SIZE);
}

void QuestLog_BackUpPalette(u16 offset, u16 size)
{
    CpuCopy16(gPlttBufferUnfaded + offset, sPalettesBackup + offset, size * 2);
}

static bool8 FieldCB2_FinalScene(void)
{
    LoadPalette(stdpal_get(4), 0xF0, 0x20);
    DrawPreviouslyOnQuestHeader(0);
    FieldCB_WarpExitFadeFromBlack();
    CreateTask(Task_FinalScene_WaitFade, 0xFF);
    return TRUE;
}

static void Task_FinalScene_WaitFade(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    if (ScriptContext2_IsEnabled() != TRUE)
    {
        FreezeObjectEvents();
        HandleEnforcedLookDirectionOnPlayerStopMoving();
        StopPlayerAvatar();
        ScriptContext2_Enable();
        task->func = Task_QuestLogScene_SavedGame;
    }
}

static void Task_QuestLogScene_SavedGame(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    if (!gPaletteFade.active)
    {
        if (sQuestLogCurrentScene.sceneEndMode != 1)
        {
            GetMapNameGeneric(gStringVar1, gMapHeader.regionMapSectionId);
            StringExpandPlaceholders(gStringVar4, gText_QuestLog_SavedGameAtLocation);
            DrawQuestLogSceneDescription();
        }
        task->data[0] = 0;
        task->data[1] = 0;
        task->func = Task_WaitAtEndOfQuestLog;
        FreezeObjectEvents();
        ScriptContext2_Enable();
    }
}

#define tTimer data[0]

static void Task_WaitAtEndOfQuestLog(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    if (JOY_NEW(A_BUTTON | B_BUTTON) || task->tTimer >= 127 || sQuestLogCurrentScene.sceneEndMode == 1)
    {
        QuestLog_CloseTextWindow();
        task->tTimer = 0;
        task->func = Task_EndQuestLog;
        gQuestLogState = 0;
    }
    else
        task->tTimer++;
}

#undef tTimer

#define tState data[0]
#define tTimer data[1]

static void Task_EndQuestLog(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u8 i;

    switch (tState)
    {
    case 0:
        gDisableMapMusicChangeOnMapLoad = 0;
        Overworld_PlaySpecialMapMusic();
        QL_SlightlyDarkenSomePals();
        FillWindowPixelRect(sQuestLogHeaderWindowIds[0], 0xF, 0, 0, sQuestLogHeaderWindowTemplates[0].width * 8, sQuestLogHeaderWindowTemplates[0].height * 8);
        tState++;
        break;
    case 1:
        if (sub_81121D8(taskId))
        {
            for (i = 0; i < 3; i++)
            {
                ClearWindowTilemap(sQuestLogHeaderWindowIds[i]);
                CopyWindowToVram(sQuestLogHeaderWindowIds[i], COPYWIN_MAP);
                RemoveWindow(sQuestLogHeaderWindowIds[i]);
            }
            tTimer = 0;
            tState++;
        }
        break;
    case 2:
        if (tTimer < 32)
            tTimer++;
        else
            tState++;
        break;
    default:
        if (sQuestLogCurrentScene.sceneEndMode == 1)
            ShowMapNamePopup(TRUE);
        CpuCopy16(sPalettesBackup, gPlttBufferUnfaded, PLTT_SIZE);
        Free(sPalettesBackup);
        sQuestLogCurrentScene = (struct UnkStruct_203AE94){};
        ClearPlayerHeldMovementAndUnfreezeObjectEvents();
        ScriptContext2_Disable();
        gTextFlags.autoScroll = FALSE;
        gGlobalFieldTintMode = QL_TINT_NONE;
        DisableWildEncounters(FALSE);
        gHelpSystemEnabled = TRUE;
        DestroyTask(taskId);
        break;
    }
}

#undef tState
#undef tTimer

static bool8 sub_81121D8(u8 taskId)
{
    s16 *data = gTasks[taskId].data;

    if (data[1] > 15)
        return TRUE;

    sub_80716F8(gPlttBufferUnfaded + 0x01, gPlttBufferFaded + 0x01, 0xDF, 0x0F - data[1]);
    sub_80716F8(gPlttBufferUnfaded + 0x100, gPlttBufferFaded + 0x100, 0x100, 0x0F - data[1]);
    FillWindowPixelRect(sQuestLogHeaderWindowIds[0], 0x00, 0, sQuestLogHeaderWindowTemplates[0].height * 8 - 1 - data[1], sQuestLogHeaderWindowTemplates[0].width * 8, 1);
    FillWindowPixelRect(sQuestLogHeaderWindowIds[1], 0x00, 0, data[1], sQuestLogHeaderWindowTemplates[1].width * 8, 1);
    CopyWindowToVram(sQuestLogHeaderWindowIds[0], COPYWIN_GFX);
    CopyWindowToVram(sQuestLogHeaderWindowIds[1], COPYWIN_GFX);
    data[1]++;
    return FALSE;
}

static void QL_SlightlyDarkenSomePals(void)
{
    u16 *buffer = Alloc(PLTT_SIZE);
    CpuCopy16(sPalettesBackup, buffer, PLTT_SIZE);
    SlightlyDarkenPalsInWeather(sPalettesBackup, sPalettesBackup, 13 * 16);
    SlightlyDarkenPalsInWeather(sPalettesBackup + 17 * 16, sPalettesBackup + 17 * 16, 1 * 16);
    SlightlyDarkenPalsInWeather(sPalettesBackup + 22 * 16, sPalettesBackup + 22 * 16, 4 * 16);
    SlightlyDarkenPalsInWeather(sPalettesBackup + 27 * 16, sPalettesBackup + 27 * 16, 5 * 16);
    CpuCopy16(sPalettesBackup, gPlttBufferUnfaded, PLTT_SIZE);
    CpuCopy16(buffer, sPalettesBackup, PLTT_SIZE);
    Free(buffer);
}

void FinishRecordingQuestLogScene(void)
{
    if (gQuestLogState == QL_STATE_RECORDING)
    {
        TryRecordQuestLogEntrySequence(sQuestLogSceneRecordBuffer);
        TryRecordEvent39_GoToNextScene();
        gQuestLogState = 0;
        sQuestLogCB = NULL;
        gUnknown_203AE04 = NULL;
        sEventRecordingPointer = NULL;
        gQuestLogPlaybackState = 0;
    }
}

void QuestLog_CutRecording(void)
{
    if (gQuestLogPlaybackState != 0 && gQuestLogState == QL_STATE_RECORDING)
    {
        TryRecordQuestLogEntrySequence(sQuestLogSceneRecordBuffer);
        TryRecordEvent41_IncCursor(1);
        TryRecordEvent39_GoToNextScene();
        gQuestLogPlaybackState = 0;
        gQuestLogState = 0;
        sQuestLogCB = NULL;
    }
    gUnknown_203AE04 = NULL;
    sEventRecordingPointer = NULL;
}

static void SortQuestLogInSav1(void)
{
    struct QuestLog * buffer = AllocZeroed(QUEST_LOG_SCENE_COUNT * sizeof(struct QuestLog));
    u8 i;
    u8 sceneNum = sCurrentSceneNum;
    u8 count = 0;
    for (i = 0; i < QUEST_LOG_SCENE_COUNT; i++)
    {
        if (sceneNum >= QUEST_LOG_SCENE_COUNT)
            sceneNum = 0;
        if (gSaveBlock1Ptr->questLog[sceneNum].startType != 0)
        {
            buffer[count] = gSaveBlock1Ptr->questLog[sceneNum];
            count++;
        }
        sceneNum++;
    }
    sCurrentSceneNum = count % QUEST_LOG_SCENE_COUNT;
    CpuCopy16(buffer, gSaveBlock1Ptr->questLog, QUEST_LOG_SCENE_COUNT * sizeof(struct QuestLog));
    Free(buffer);
}

void SaveQuestLogData(void)
{
    if (MenuHelpers_LinkSomething() != TRUE)
    {
        QuestLog_CutRecording();
        SortQuestLogInSav1();
    }
}

void sub_811246C(struct Sprite *sprite)
{
    struct ObjectEvent *objectEvent = &gObjectEvents[sprite->data[0]];
    if (objectEvent->localId == OBJ_EVENT_ID_PLAYER)
    {
        if (sMovementScripts[0][0] != 0xFF)
        {
            ObjectEventSetHeldMovement(objectEvent, sMovementScripts[0][0]);
            sMovementScripts[0][0] = 0xFF;
        }
        if (sMovementScripts[0][1] != 0xFF)
        {
            QuestLogUpdatePlayerSprite(sMovementScripts[0][1]);
            sMovementScripts[0][1] = 0xFF;
        }
        sub_8063E28(objectEvent, sprite);
    }
    else
    {
        if (sMovementScripts[objectEvent->localId][0] != 0xFF)
        {
            ObjectEventSetHeldMovement(objectEvent, sMovementScripts[objectEvent->localId][0]);
            sMovementScripts[objectEvent->localId][0] = 0xFF;
        }
        sub_8063E28(objectEvent, sprite);
    }
}

void QuestLogRecordNPCStep(u8 localId, u8 mapNum, u8 mapGroup, u8 movementActionId)
{
    if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
    {
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 0;
        sCurQuestLogEntry[sQuestLogCursor].localId = localId;
        sCurQuestLogEntry[sQuestLogCursor].mapNum = mapNum;
        sCurQuestLogEntry[sQuestLogCursor].mapGroup = mapGroup;
        sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
        sQuestLogCursor++;
        sNextStepDelay = 0;
    }
}

void QuestLogRecordNPCStepWithDuration(u8 localId, u8 mapNum, u8 mapGroup, u8 movementActionId, u8 duration)
{
    if (!RecordHeadAtEndOfEntry())
    {
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 0;
        sCurQuestLogEntry[sQuestLogCursor].localId = localId;
        sCurQuestLogEntry[sQuestLogCursor].mapNum = mapNum;
        sCurQuestLogEntry[sQuestLogCursor].mapGroup = mapGroup;
        sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
        sQuestLogCursor++;
        sNextStepDelay = duration;
    }
}

void QuestLogRecordPlayerStep(u8 movementActionId)
{
    if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
    {
        if (movementActionId != sCurQuestLogEntry[sLastQuestLogCursor].animId || movementActionId > MOVEMENT_ACTION_FACE_RIGHT)
        {
            sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
            sCurQuestLogEntry[sQuestLogCursor].command = 0;
            sCurQuestLogEntry[sQuestLogCursor].localId = 0;
            sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
            sLastQuestLogCursor = sQuestLogCursor;
            sQuestLogCursor++;
            sNextStepDelay = 0;
        }
    }
}

void QuestLogRecordPlayerStepWithDuration(u8 movementActionId, u8 duration)
{
    if (!RecordHeadAtEndOfEntry())
    {
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 0;
        sCurQuestLogEntry[sQuestLogCursor].localId = 0;
        sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
        sLastQuestLogCursor = sQuestLogCursor;
        sQuestLogCursor++;
        sNextStepDelay = duration;
    }
}

void QuestLogRecordPlayerAvatarGfxTransition(u8 movementActionId)
{
    if (!RecordHeadAtEndOfEntry())
    {
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 1;
        sCurQuestLogEntry[sQuestLogCursor].localId = 0;
        sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
        sQuestLogCursor++;
        sNextStepDelay = 0;
    }
}

void QuestLogRecordPlayerAvatarGfxTransitionWithDuration(u8 movementActionId, u8 duration)
{
    if (!RecordHeadAtEndOfEntry())
    {
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 1;
        sCurQuestLogEntry[sQuestLogCursor].localId = 0;
        sCurQuestLogEntry[sQuestLogCursor].animId = movementActionId;
        sQuestLogCursor++;
        sNextStepDelay = duration;
    }
}

void sub_81127F8(struct FieldInput * a0)
{
    if (sQuestLogCursor < sNumEventsInLogEntry)
    {
        // Retain only the following fields:
        // - pressedAButton
        // - checkStandardWildEncounter
        // - heldDirection
        // - heldDirection2
        // - tookStep
        // - pressedBButton
        // - dpadDirection
        u32 r2 = *(u32 *)a0 & 0x00FF00F3;
        sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
        sCurQuestLogEntry[sQuestLogCursor].command = 2;
        sCurQuestLogEntry[sQuestLogCursor].localId = r2;
        sCurQuestLogEntry[sQuestLogCursor].mapNum = r2 >> 8; // always 0
        sCurQuestLogEntry[sQuestLogCursor].mapGroup = r2 >> 16;
        sCurQuestLogEntry[sQuestLogCursor].animId = r2 >> 24; // always 0
        sQuestLogCursor++;
        if (ScriptContext2_IsEnabled())
            sNextStepDelay = TRUE;
        else
            sNextStepDelay = FALSE;
    }
}

static void TogglePlaybackStateForOverworldLock(u8 a0)
{
    switch (a0)
    {
    case 1:
        if (gQuestLogPlaybackState == 1)
            gQuestLogPlaybackState = 3; // Message visible, overworld locked
        break;
    case 2:
        if (gQuestLogPlaybackState == 3)
            gQuestLogPlaybackState = 1; // Overworld unlocked
        break;
    }
}

void QuestLog_OnEscalatorWarp(u8 direction)
{
    u8 r1 = sub_8112CAC();

    switch (direction)
    {
    case QL_ESCALATOR_OUT: // warp out
        if (r1 == 1)
            gQuestLogPlaybackState = 3;
        else if (r1 == 2)
        {
            sCurQuestLogEntry[sQuestLogCursor].duration = sNextStepDelay;
            sCurQuestLogEntry[sQuestLogCursor].command = 3;
            sQuestLogCursor++;
            sNextStepDelay = 0;
            gQuestLogPlaybackState = 4;
        }
        break;
    case QL_ESCALATOR_IN: // warp in
        if (r1 == 1)
            gQuestLogPlaybackState = 1;
        else if (r1 == 2)
            gQuestLogPlaybackState = 2;
        break;
    }
}

static void SetUpQuestLogEntry(u8 kind, struct QuestLogEntry *entry, u16 size)
{
    int i;

    switch (kind)
    {
    default:
        gQuestLogPlaybackState = 0;
        break;
    case 1:
        sCurQuestLogEntry = entry;
        sNumEventsInLogEntry = size / sizeof(*sCurQuestLogEntry);
        for (i = 0; i < (s32)NELEMS(sMovementScripts); i++)
        {
            sMovementScripts[i][0] |= 0xFF;
            sMovementScripts[i][1] |= 0xFF;
        }
        sQuestLogCursor = 0;
        sLastQuestLogCursor = 0;
        gQuestLogFieldInput = (struct FieldInput){};
        sNextStepDelay = sCurQuestLogEntry[sQuestLogCursor].duration;
        sMovementScripts[0][0] = sCurQuestLogEntry[sQuestLogCursor].animId;
        sMovementScripts[0][1] = 0xFF;
        gQuestLogPlaybackState = 1;
        break;
    case 2:
        sCurQuestLogEntry = entry;
        sNumEventsInLogEntry = size / sizeof(*sCurQuestLogEntry);
        for (i = 0; i < sNumEventsInLogEntry; i++)
        {
            sCurQuestLogEntry[i] = (struct QuestLogEntry){ 0, 0, 0, 0, 0xFFFF, 0xFF };
        }
        sQuestLogCursor = 0;
        sNextStepDelay = 0;
        sCurQuestLogEntry[sQuestLogCursor].duration = 0;
        sCurQuestLogEntry[sQuestLogCursor].command = 0;
        sCurQuestLogEntry[sQuestLogCursor].localId = 0;
        switch (GetPlayerFacingDirection())
        {
        case DIR_NONE:
        case DIR_SOUTH:
            sCurQuestLogEntry[sQuestLogCursor].animId = MOVEMENT_ACTION_FACE_DOWN;
            break;
        case DIR_EAST:
            sCurQuestLogEntry[sQuestLogCursor].animId = MOVEMENT_ACTION_FACE_RIGHT;
            break;
        case DIR_NORTH:
            sCurQuestLogEntry[sQuestLogCursor].animId = MOVEMENT_ACTION_FACE_UP;
            break;
        case DIR_WEST:
            sCurQuestLogEntry[sQuestLogCursor].animId = MOVEMENT_ACTION_FACE_LEFT;
            break;
        }
        sLastQuestLogCursor = 0;
        sQuestLogCursor++;
        sCurQuestLogEntry[sQuestLogCursor].duration = 0;
        sCurQuestLogEntry[sQuestLogCursor].command = 2;
        sCurQuestLogEntry[sQuestLogCursor].localId = 0;
        sCurQuestLogEntry[sQuestLogCursor].mapNum = 0;
        sCurQuestLogEntry[sQuestLogCursor].mapGroup = 0;
        sCurQuestLogEntry[sQuestLogCursor].animId = 0;
        sQuestLogCursor++;
        gQuestLogPlaybackState = 2;
        break;
    }
}

void sub_8112B3C(void)
{
    switch (gQuestLogPlaybackState)
    {
    case 0:
        break;
    case 1:
        if (!RecordHeadAtEndOfEntryOrScriptContext2Enabled())
        {
            if (sNextStepDelay != 0)
                sNextStepDelay--;
            else
            {
                do
                {
                    switch (sCurQuestLogEntry[sQuestLogCursor].command)
                    {
                    case 0:
                        // NPC movement action
                        sMovementScripts[sCurQuestLogEntry[sQuestLogCursor].localId][0] = sCurQuestLogEntry[sQuestLogCursor].animId;
                        break;
                    case 1:
                        // State transition
                        sMovementScripts[sCurQuestLogEntry[sQuestLogCursor].localId][1] = sCurQuestLogEntry[sQuestLogCursor].animId;
                        break;
                    case 2:
                        // Player input command
                        *(u32 *)&gQuestLogFieldInput = ((sCurQuestLogEntry[sQuestLogCursor].animId << 24) | (sCurQuestLogEntry[sQuestLogCursor].mapGroup << 16) | (sCurQuestLogEntry[sQuestLogCursor].mapNum << 8) | (sCurQuestLogEntry[sQuestLogCursor].localId << 0));
                        break;
                    case 3:
                        // End
                        gQuestLogPlaybackState = 3;
                        break;
                    case 0xFE:
                        break;
                    case 0xFF:
                        gQuestLogPlaybackState = 0;
                        break;
                    }
                    if (gQuestLogPlaybackState == 0)
                        break;
                    if (++sQuestLogCursor >= sNumEventsInLogEntry)
                    {
                        gQuestLogPlaybackState = 0;
                        break;
                    }
                    sNextStepDelay = sCurQuestLogEntry[sQuestLogCursor].duration;
                } while (gQuestLogPlaybackState != 3
                      && (sNextStepDelay == 0 || sNextStepDelay == 0xFFFF));
            }
        }
        else if (sQuestLogCursor >= sNumEventsInLogEntry)
        {
            gQuestLogPlaybackState = 0;
        }
        break;
    case 2:
        if (ScriptContext2_IsEnabled() != TRUE)
        {
            sNextStepDelay++;
            if (sQuestLogCursor >= sNumEventsInLogEntry)
                gQuestLogPlaybackState = 0;
        }
        break;
    case 3:
        break;
    case 4:
        break;
    }
}

void QL_AfterRecordFishActionSuccessful(void)
{
    sNextStepDelay++;
}

u8 sub_8112CAC(void)
{
    switch (gQuestLogPlaybackState)
    {
    case 0:
    default:
        return 0;
    case 1:
    case 3:
        return 1;
    case 2:
    case 4:
        return 2;
    }
}

static bool8 RecordHeadAtEndOfEntryOrScriptContext2Enabled(void)
{
    if (sQuestLogCursor >= sNumEventsInLogEntry || ScriptContext2_IsEnabled() == TRUE)
        return TRUE;
    return FALSE;
}

static bool8 RecordHeadAtEndOfEntry(void)
{
    if (sQuestLogCursor >= sNumEventsInLogEntry)
        return TRUE;
    return FALSE;
}

static const struct FlagOrVarRecord sDummyFlagOrVarRecord = {
    0,
    FALSE,
    0x7FFF
};

void *QuestLogGetFlagOrVarPtr(bool8 isFlag, u16 idx)
{
    void *response;
    if (sQuestLogCursor == 0)
        return NULL;
    if (sQuestLogCursor >= sNumEventsInLogEntry)
        return NULL;
    if (sFlagOrVarPlayhead >= sNumFlagsOrVars)
        return NULL;
    if (sFlagOrVarRecords[sFlagOrVarPlayhead].idx == idx && sFlagOrVarRecords[sFlagOrVarPlayhead].isFlag == isFlag)
    {
        response = &sFlagOrVarRecords[sFlagOrVarPlayhead].value;
        sFlagOrVarPlayhead++;
    }
    else
        response = NULL;
    return response;
}

void QuestLogSetFlagOrVar(bool8 isFlag, u16 idx, u16 value)
{
    if (sQuestLogCursor == 0)
        return;
    if (sQuestLogCursor >= sNumEventsInLogEntry)
        return;
    if (sFlagOrVarPlayhead >= sNumFlagsOrVars)
        return;
    sFlagOrVarRecords[sFlagOrVarPlayhead].idx = idx;
    sFlagOrVarRecords[sFlagOrVarPlayhead].isFlag = isFlag;
    sFlagOrVarRecords[sFlagOrVarPlayhead].value = value;
    sFlagOrVarPlayhead++;
}

void sub_8112E3C(u8 state, struct FlagOrVarRecord * records, u16 size)
{
    s32 i;

    if (state == 0 || state > QL_STATE_PLAYBACK)
        gQuestLogPlaybackState = 0;
    else
    {
        sFlagOrVarRecords = records;
        sNumFlagsOrVars = size >> 2;
        sFlagOrVarPlayhead = 0;
        if (state == QL_STATE_PLAYBACK)
        {
            for (i = 0; i < sNumEventsInLogEntry; i++)
            {
                sFlagOrVarRecords[i] = sDummyFlagOrVarRecord;
            }
        }
    }
}
