#include "toBeSorted/save_manager.h"

#include "common.h"
#include "d/d_cs_base.h"
#include "d/d_d2d.h"
#include "d/d_dvd_unk.h"
#include "d/d_heap.h"
#include "d/d_message.h"
#include "d/d_reset.h"
#include "d/d_s_boot.h"
#include "d/d_sc_game.h"
#include "d/d_sys.h"
#include "d/lyt/d_lyt_save_msg_window.h"
#include "d/lyt/d_lyt_system_window.h"
#include "d/snd/d_snd_small_effect_mgr.h"
#include "egg/core/eggHeap.h"
#include "m/m_pad.h"
#include "toBeSorted/arc_managers/layout_arc_manager.h"
#include "toBeSorted/file_manager.h"
#include "toBeSorted/nand_request_thread.h"
#include "toBeSorted/nand_result_tracker.h"

#include "rvl/NAND.h"
#include "rvl/TPL.h"

#include <cstring>

SaveMgr *SaveMgr::sInstance;

static const char sSaveFileName[] = "wiiking2.sav";
static const char sSkipDatFileName[] = "skip.dat";
static const char sBannerBinFileName[] = "banner.bin";
static const char sTmpBannerBinFileName[] = "/tmp/banner.bin";

#define FILE_PERMS (NAND_PERM_RUSR | NAND_PERM_WUSR | NAND_PERM_RGRP | NAND_PERM_WGRP)

const SaveMgr::StateFunc SaveMgr::sStateFuncs[STATE_MAX] = {
    &SaveMgr::executeCheckForSave, &SaveMgr::executeCheckForFreeSpace, &SaveMgr::executeCreateFiles,
    &SaveMgr::executeSaveBanner,   &SaveMgr::executeLoadSave,          &SaveMgr::executeWriteSave,
    &SaveMgr::executeWriteSkipDat, &SaveMgr::executeCopySave,          &SaveMgr::executeClearSelectedFile,
    &SaveMgr::executeSave,         &SaveMgr::executeDeleteAllData,     &SaveMgr::executeError,
    &SaveMgr::executeNandError,
};

SaveMgr::SaveMgr()
    : mCheckIsFileRequest(nullptr),
      mCheckRequest(nullptr),
      mCreateRequest(nullptr),
      mWriteRequest(nullptr),
      mSaveBannerRequest(nullptr),
      mLoadSaveRequest(nullptr),
      mDeleteRequest(nullptr) {
    sInstance = this;
    mpWindow = nullptr;
}
// Not used but this spawns the weak dtors
SaveMgr::~SaveMgr() {
    delete mpWindow;
}

void SaveMgr::create(EGG::Heap *heap) {
    new (heap, 4) SaveMgr();
    sInstance->init();
}

void SaveMgr::init() {
    mCurrentState = STATE_MAX;
    field_0x824 = 2;
    mCheckForFreeSpaceResult = 0;
    mCopyToFile = 0;
    mCopyFromFile = 0;
    mIsIdle = false;
    field_0x837 = 0;
    field_0x838 = 0;
    field_0x839 = 0;
    field_0x83A = 0;
    field_0x83B = 0;
    field_0x83C = 0;
    field_0x83D = false;
    field_0x83E = 0;
    mDelayTimer = 0;
    FileManager::GetInstance()->setField0xA840(1);
    if (dCsBase_c::GetInstance() != nullptr) {
        dCsBase_c::GetInstance()->setDrawDirectly(false);
    }
    if (mpWindow != nullptr) {
        mpWindow->reset();
    }
    field_0x83F = 0;
}

void SaveMgr::execute() {
    if (mCurrentState != STATE_MAX) {
        StateFunc f = sStateFuncs[mCurrentState];
        if (f != nullptr) {
            (this->*f)();
        }
    }
}

void SaveMgr::draw() {
    d2d::defaultSet();
    if (mpWindow != nullptr) {
        mpWindow->drawNow();
    }
}

void SaveMgr::createSaveMsgWindow() {
    if (mpWindow == nullptr) {
        mpWindow = new dLytSaveMsgWindow_c();
        mpWindow->build(false);
    }
}

bool SaveMgr::checkForSave() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeCheckForSave();
    return true;
}

bool SaveMgr::checkForFreeSpace() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeCheckForFreeSpace();
    return true;
}

bool SaveMgr::createFiles() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeCreateFiles();
    return true;
}

bool SaveMgr::loadSave() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeLoadSave();
    return true;
}

bool SaveMgr::save(bool entranceT1LoadFlag, bool full) {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    if (FileManager::GetInstance()->get_0xA84C()) {
        return false;
    }
    mIsIdle = false;
    if (full == 1) {
        FileManager::GetInstance()->saveT1SaveInfo(entranceT1LoadFlag);
    } else {
        FileManager::GetInstance()->setEntranceLoadFlagT1(entranceT1LoadFlag);
    }
    FileManager::GetInstance()->setFileTimes();
    initializeSave();
    field_0x83E = 0;
    return true;
}

bool SaveMgr::saveAfterCredits() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    if (FileManager::GetInstance()->get_0xA84C()) {
        return false;
    }
    mIsIdle = false;
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(true);
    FileManager::GetInstance()->setFileTimes();
    FileManager::GetInstance()->saveAfterCredits();
    initializeSave();
    field_0x83E = 0;
    return true;
}

bool SaveMgr::writeSkipDat() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeWriteSkipDat();
    return true;
}

bool SaveMgr::copySave(u8 to, u8 from) {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    mCopyToFile = to;
    mCopyFromFile = from;
    initializeCopySave();
    field_0x83F = 0;
    return true;
}

bool SaveMgr::clearSelectedFile() {
    if (mCurrentState != STATE_MAX) {
        return false;
    }
    mIsIdle = false;
    initializeClearSelectedFile();
    field_0x83F = 0;
    return true;
}

void SaveMgr::initializeCheckForSave() {
    beginState(STATE_CHECK_FOR_SAVE);
    mCheckIsFileRequest.checkIsFile(sSaveFileName);
    field_0x839 = 0;
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(false);
}

void SaveMgr::executeCheckForSave() {
    FileManager *fileManager = FileManager::GetInstance();
    NandResultTracker *t = NandResultTracker::GetInstance();
    switch (mStep) {
        case 0:
            if (mCheckIsFileRequest.isCompleted()) {
                if (t->isFailure(mCheckIsFileRequest.getResult()) && mCheckIsFileRequest.finish()) {
                    return;
                }
                if (mCheckIsFileRequest.getCheckResult()) {
                    field_0x839 |= 2;
                }
                if (mCheckIsFileRequest.finish()) {
                    mCheckIsFileRequest.checkIsFile(sSkipDatFileName);
                    mStep = 1;
                }
            }
            break;
        case 1:
            if (mCheckIsFileRequest.isCompleted()) {
                if (t->isFailure(mCheckIsFileRequest.getResult()) && mCheckIsFileRequest.finish()) {
                    return;
                }
                if (mCheckIsFileRequest.getCheckResult()) {
                    field_0x839 |= 4;
                }
                if (mCheckIsFileRequest.finish()) {
                    mCheckIsFileRequest.checkIsFile(sBannerBinFileName);
                    mStep = 2;
                }
            }
            break;
        case 2:
            if (mCheckIsFileRequest.isCompleted()) {
                if (t->isFailure(mCheckIsFileRequest.getResult()) && mCheckIsFileRequest.finish()) {
                    return;
                }
                if (mCheckIsFileRequest.getCheckResult()) {
                    field_0x839 |= 1;
                }
                if (mCheckIsFileRequest.finish()) {
                    mStep = 3;
                }
            }
            break;
        case 3:
            if (field_0x839 == (1 | 2) || field_0x839 == (1 | 2 | 4)) {
                fileManager->setField0xA841(1);
                if (field_0x839 == (1 | 2)) {
                    fileManager->setField0xA842(0);
                } else {
                    fileManager->setField0xA842(1);
                }
            } else {
                fileManager->setField0xA841(0);
                fileManager->setField0xA842(0);
                if (field_0x839) {
                    t->isFailure(NAND_RESULT_AUTHENTICATION);
                    initializeError();
                    return;
                }
            }
            endState();
            break;
    }
}

void SaveMgr::initializeCheckForFreeSpace() {
    beginState(STATE_CHECK_FOR_FREE_SPACE);
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(false);
    mCheckRequest.check(7, 3);
    mCheckForFreeSpaceResult = 0;
}

void SaveMgr::executeCheckForFreeSpace() {
    if (mCheckRequest.isCompleted()) {
        if (NandResultTracker::GetInstance()->isFailure(mCheckRequest.getResult()) && mCheckRequest.finish()) {
            return;
        }
        u32 result = mCheckRequest.getCheckResult();
        if ((result & (NAND_CHECK_TOO_MANY_APP_BLOCKS | NAND_CHECK_TOO_MANY_USER_BLOCKS)) != 0) {
            if (mCheckRequest.finish()) {
                mCheckForFreeSpaceResult = 1;
                initializeNandError();
            }
        } else if ((result & (NAND_CHECK_TOO_MANY_APP_FILES | NAND_CHECK_TOO_MANY_USER_FILES)) != 0) {
            if (mCheckRequest.finish()) {
                mCheckForFreeSpaceResult = 2;
                initializeNandError();
            }
        } else {
            if (mCheckRequest.finish()) {
                if (field_0x838 == 1 || field_0x83A == 1) {
                    initializeCreateFiles();
                } else {
                    endState();
                }
            }
        }
    }
}

void SaveMgr::initializeCreateFiles() {
    beginState(STATE_CREATE_FILES);
    field_0x838 = 1;
    FileManager::GetInstance()->setField0xA84D(1);
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(false);
}

void SaveMgr::executeCreateFiles() {
    FileManager *fileMgr = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            s32 numLines = 0;
            if (field_0x83A != 0) {
                if (field_0x837 == 1) {
                    // "Restoring save data. Please do not touch the POWER Button or RESET."
                    if (mpWindow->setProperties("SYS_INIT_06", 0, 1, field_0x83E)) {
                        numLines = 2;
                    }
                } else {
                    // "Creating save data again. Please do not touch the POWER Button or RESET."
                    if (mpWindow->setProperties("SYS_INIT_08", 0, 1, field_0x83E)) {
                        numLines = 2;
                    }
                }
            } else {
                // "Creating save data. Please do not touch the POWER Button or RESET."
                if (mpWindow->setProperties("SYS_INIT_02", 0, 1, field_0x83E)) {
                    numLines = 2;
                }
            }
            if (numLines != 0) {
                mDelayTimer = 0;
                mStep++;
                mpWindow->setNumLines(numLines);
            }
            break;
        }
        case 1: {
            if (mDelayTimer < 30) {
                mDelayTimer++;
            } else {
                mCreateRequest.create(sSaveFileName, FILE_PERMS, 0);
                mDelayTimer = 0;
                mStep++;
            }
            break;
        }
        case 2: {
            if (!mCreateRequest.isCompleted()) {
                return;
            }
            NANDResult status = mCreateRequest.getResult();
            if (!mCreateRequest.finish()) {
                return;
            }
            if (NandResultTracker::GetInstance()->isFailure(status)) {
                return;
            }
            fileMgr->setField0xA841(1);
            mStep++;
            break;
        }
        case 3: {
            mCreateRequest.create(sSkipDatFileName, FILE_PERMS, 0);
            mStep++;
            break;
        }
        case 4: {
            if (!mCreateRequest.isCompleted()) {
                return;
            }
            NANDResult status = mCreateRequest.getResult();
            if (!mCreateRequest.finish()) {
                return;
            }
            if (NandResultTracker::GetInstance()->isFailure(status)) {
                return;
            }
            if (dScGame_c::GetInstance() != nullptr && dScGame_c::GetInstance()->mProfileName == fProfile::GAME) {
                fileMgr->copySelectedFileSkipData();
            }
            fileMgr->setField0xA842(1);
            initializeWriteSave();
            mStep = 1; // Skips something in other state!
            break;
        }
    }
    mpWindow->execute();
}

void SaveMgr::initializeSaveBanner() {
    beginState(STATE_SAVE_BANNER);
    // "Zelda: Skyward Sword"
    const wchar_t *title = dMessage_c::getTextMessageByLabel("SYS_ZELDA_01", true, nullptr, 0);
    // Need to stash title as next call to getTextMessageByLabel will reuse internal buffer
    // Missed optimization: Could've passed mTextBuf directly
    for (s32 i = 0; i < 0x3FF; i++) {
        mTextBuf[i] = title[i];
    }
    // "A story of Link and Zelda."
    const wchar_t *subtitle = dMessage_c::getTextMessageByLabel("SYS_ZELDA_02", true, nullptr, 0);
    void *data = LayoutArcManager::GetInstance()->getData("saveBannerU", "tmp/saveBanner.tpl");
    mSaveBannerRequest.saveBanner(sTmpBannerBinFileName, (TPLPalette *)data, mTextBuf, subtitle, FILE_PERMS, 0);
    FileManager::GetInstance()->setField0xA84D(1);
}

void SaveMgr::executeSaveBanner() {
    dDvdUnk::FontUnk *fontUnk = dDvdUnk::FontUnk::GetInstance();
    switch (mStep) {
        case 0: {
            mDelayTimer++;
            if (!mSaveBannerRequest.isCompleted()) {
                return;
            }
            FileManager::GetInstance()->setField0xA84D(0);
            NANDResult result = mSaveBannerRequest.getResult();
            bool bFailed = mSaveBannerRequest.hasFailed();
            if (!mSaveBannerRequest.finish()) {
                return;
            }
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                mStep = 20;
                return;
            }
            if (bFailed) {
                // retry
                mStep = 10;
                return;
            }
            mStep = 1;
            break;
        }
        case 1: {
            if (mDelayTimer < getFrameRate()) {
                mDelayTimer++;
            } else {
                if (mpWindow->checkIsWait()) {
                    mDelayTimer = 0;
                    mStep++;
                }
            }
            break;
        }
        case 2: {
            if (mpWindow->getWillFinishOut() == 1) {
                mStep++;
            }
            break;
        }
        case 3: {
            bool b = false;
            if (field_0x83A == 1) {
                if (field_0x837 != 0) {
                    // "Save data is restored."
                    if (mpWindow->setProperties("SYS_INIT_07", 0, 1, field_0x83E) == true) {
                        b = true;
                    }
                } else {
                    // "New save data has been created."
                    if (mpWindow->setProperties("SYS_INIT_09", 0, 1, field_0x83E) == true) {
                        b = true;
                    }
                }
            } else if (field_0x838 == 1) {
                // "Save data for The Legend of Zelda: Skyward Sword has been created."
                if (mpWindow->setProperties("SYS_INIT_03", 0, 1, field_0x83E) == true) {
                    b = true;
                    mpWindow->setNumLines(2);
                }
            } else if (field_0x83B == 1) {
                // "Copying complete."
                if (mpWindow->setProperties("SYS_COPY_02", 0, 1, field_0x83E) == true) {
                    b = true;
                }
            } else if (field_0x83C == 1) {
                // "Deleting complete."
                if (mpWindow->setProperties("SYS_DELETE_02", 0, 1, field_0x83E) == true) {
                    b = true;
                }
            } else {
                // "Saved."
                if (mpWindow->setProperties("SYS_SAVE_02", 0, 1, field_0x83E) == true) {
                    b = true;
                }
            }

            if (b == 1) {
                if (!dDvdUnk::FontUnk::GetInstance()->isDiskError()) {
                    dSndSmallEffectMgr_c::GetInstance()->playSound(SE_S_SAVE_FINISH);
                }
                mDelayTimer = 0;
                mStep++;
            }
            break;
        }

        case 4: {
            if (mDelayTimer < getFrameRate() + 30) {
                mDelayTimer++;
            } else {
                if (mpWindow->checkIsWait() == true) {
                    mDelayTimer = 0;
                    mStep++;
                }
            }
            break;
        }
        case 5: {
            if (mpWindow->getWillFinishOut() == 1) {
                if (fontUnk->isNandError() == 1) {
                    fontUnk->clearNandTrackerError();
                }
                field_0x838 = 0;
                field_0x83A = 0;
                field_0x837 = 1;
                endState();
            }
            break;
        }
        case 10: {
            // retry
            initializeSaveBanner();
            break;
        }
        case 20: {
            if (mpWindow->checkIsWait()) {
                mStep++;
            }
            break;
        }
        case 21: {
            initializeError();
            break;
        }
    }
    mpWindow->execute();
}

void SaveMgr::initializeLoadSave() {
    beginState(STATE_LOAD_SAVE);
    mLoadSaveRequest.loadSave(sSaveFileName, sizeof(SavedSaveFiles), 0, nullptr);
}

void SaveMgr::executeLoadSave() {
    FileManager *fileManager = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            if (!mLoadSaveRequest.isCompleted()) {
                return;
            }
            NANDResult result = mLoadSaveRequest.getResult();
            NandResultTracker *tracker = NandResultTracker::GetInstance();
            if (tracker->isFailure(result)) {
                mLoadSaveRequest.remove();
                if (mLoadSaveRequest.finish()) {
                    initializeError();
                }
                return;
            }
            if (mLoadSaveRequest.getBuf() != nullptr) {
                fileManager->saveSaveData(&mLoadSaveRequest, false);
            }
            mLoadSaveRequest.remove();
            if (!mLoadSaveRequest.finish()) {
                return;
            }
            fileManager->checkFileStatus();
            if (fileManager->isFileInvalid() == true) {
                tracker->isFailure(NAND_RESULT_AUTHENTICATION);
                initializeError();
            } else if (fileManager->getField_0xA842() != 1) {
                mStep = 10;
            } else {
                mStep = 1;
            }
            break;
        }
        case 1: {
            mLoadSaveRequest.loadSave(sSkipDatFileName, 0x80, 0, nullptr);
            mStep = 2;
            break;
        }
        case 2: {
            if (!mLoadSaveRequest.isCompleted()) {
                return;
            }
            NANDResult result = mLoadSaveRequest.getResult();
            NandResultTracker *tracker = NandResultTracker::GetInstance();
            if (tracker->isFailure(result)) {
                mLoadSaveRequest.remove();
                if (mLoadSaveRequest.finish()) {
                    mStep = 30;
                }
                return;
            }
            if (mLoadSaveRequest.getBuf() != nullptr) {
                fileManager->saveSaveData(&mLoadSaveRequest, true);
            }
            mLoadSaveRequest.remove();
            if (mLoadSaveRequest.finish()) {
                if (fileManager->checkSkipDataCRCs() == 1) {
                    mStep = 3;
                } else {
                    fileManager->updateEmptyFiles();
                    field_0x837 = 1;
                    endState();
                }
            }
            break;
        }
        case 3: {
            for (int i = 0; i < 3; i++) {
                fileManager->copyFileSkipData(i);
            }
            mStep++;
            break;
        }
        case 4: {
            fileManager->updateEmptyFiles();
            field_0x837 = 1;
            initializeWriteSkipDat();
            break;
        }
        case 10: {
            mCheckRequest.check(1, 1);
            mStep++;
            break;
        }
        case 11: {
            if (!mCheckRequest.isCompleted()) {
                return;
            }
            NANDResult result = mCheckRequest.getResult();
            if (!NandResultTracker::GetInstance()->isFailure(result) || !mCheckRequest.finish()) {
                u32 check = mCheckRequest.getCheckResult();
                if (mCheckRequest.finish()) {
                    if (check) {
                        fileManager->updateEmptyFiles();
                        field_0x837 = 1;
                        endState();
                    } else {
                        mStep += 1;
                    }
                }
            }
            break;
        }
        case 12: {
            fileManager->setField0xA84D(1);
            mCreateRequest.create(sSkipDatFileName, FILE_PERMS, 0);
            mStep += 1;
            break;
        }
        case 13: {
            if (mCreateRequest.isCompleted()) {
                NANDResult result = mCreateRequest.getResult();
                if (mCreateRequest.finish()) {
                    if (!NandResultTracker::GetInstance()->isFailure(result)) {
                        for (int i = 0; i < 3; i++) {
                            fileManager->copyFileSkipData(i);
                        }
                        fileManager->setField0xA842(1);
                        mStep = 4;
                    }
                }
            }
            break;
        }
        case 30: {
            fileManager->setField0xA84D(1);
            mDeleteRequest.doDelete(sSkipDatFileName);
            mStep += 1;
            break;
        }
        case 31: {
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                if (mDeleteRequest.finish()) {
                    initializeError();
                }

            } else {
                if (mDeleteRequest.finish()) {
                    mStep = 10;
                }
            }

            break;
        }
    }
}

void SaveMgr::initializeWriteSave() {
    beginState(STATE_WRITE);
    mDelayTimer = 0;
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(false);
    FileManager::GetInstance()->setField0xA84D(1);
    field_0x83D = true;
}

void SaveMgr::executeWriteSave() {
    FileManager *fileManager = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            bool anyAction = false;
            if (field_0x83B == 1) {
                // "Copying... Please do not touch the POWER Button or RESET."
                if (mpWindow->setProperties("SYS_COPY_01", 0, 1, field_0x83E)) {
                    anyAction = true;
                }
            } else if (field_0x83C == 1) {
                // "Deleting... Please do not touch the POWER Button or RESET."
                if (mpWindow->setProperties("SYS_DELETE_01", 0, 1, field_0x83E)) {
                    anyAction = true;
                }
            } else {
                // "Saving in progress. Please do not\\ntouch the POWER Button or RESET."
                if (mpWindow->setProperties("SYS_SAVE_01", 0, 1, field_0x83E)) {
                    anyAction = true;
                }
            }

            if (anyAction == true) {
                mStep = 1;
                mDelayTimer = 0;
                mpWindow->setNumLines(2);
            }
            break;
        }
        case 1: {
            if (mDelayTimer >= 30) {
                fileManager->loadSaveData(&mWriteRequest, sSaveFileName, false);
                mDelayTimer = 0;
                mStep++;
            } else {
                mDelayTimer++;
            }
            break;
        }
        case 2: {
            mDelayTimer++;
            if (!mWriteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mWriteRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                if (mWriteRequest.finish()) {
                    mStep = 10;
                }
                return;
            }
            if (mWriteRequest.failedWrite() == 1) {
                if (mWriteRequest.finish()) {
                    mStep = 5;
                }
                return;
            }
            if (!mWriteRequest.finish()) {
                return;
            }
            if (fileManager->getField_0xA842() == 1) {
                mStep = 3;
            } else {
                mStep = 40;
            }
            break;
        }
        case 3: {
            fileManager->loadSaveData(&mWriteRequest, sSkipDatFileName, true);
            mStep = 4;
            break;
        }
        case 4: {
            mDelayTimer++;
            if (!mWriteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mWriteRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                if (mWriteRequest.finish()) {
                    mStep = 30;
                }
                return;
            }
            if (mWriteRequest.failedWrite() == 1) {
                if (mWriteRequest.finish()) {
                    mStep = 6;
                }
                return;
            }
            if (!mWriteRequest.finish()) {
                return;
            }
            mStep = 40;
            break;
        }
        case 5: {
            fileManager->loadSaveData(&mWriteRequest, sSaveFileName, false);
            mStep = 2;
            mDelayTimer = 0;
            break;
        }
        case 6: {
            fileManager->loadSaveData(&mWriteRequest, sSkipDatFileName, true);
            mStep = 4;
            mDelayTimer = 0;
            break;
        }
        case 10: {
            if (mpWindow->checkIsWait()) {
                initializeError();
            }
            break;
        }
        case 20: {
            if (mpWindow->checkIsWait()) {
                mStep++;
            }
            break;
        }
        case 21: {
            if (mpWindow->getWillFinishOut() == true) {
                fileManager->checkFileStatus();
                if (fileManager->isFileInvalid() == 1) {
                    NandResultTracker::GetInstance()->isFailure(NAND_RESULT_AUTHENTICATION);
                    initializeError();
                    return;
                }
                initializeSaveBanner();
            }
            break;
        }
        case 30: {
            mDelayTimer++;
            mDeleteRequest.doDelete(sSkipDatFileName);
            mStep++;
            break;
        }
        case 31: {
            mDelayTimer++;
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                if (!mDeleteRequest.finish()) {
                    return;
                }
                mpWindow->checkIsWait();
                initializeError();
                return;
            }
            if (!mDeleteRequest.finish()) {
                return;
            }
            fileManager->setField0xA842(0);
            mStep++;
            break;
        }
        case 32: {
            mDelayTimer++;
            mCheckRequest.check(1, 1);
            mStep++;
            break;
        }
        case 33: {
            mDelayTimer++;
            if (!mCheckRequest.isCompleted()) {
                return;
            }
            NANDResult result = mCheckRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result) && mCheckRequest.finish()) {
                mpWindow->checkIsWait();
                initializeError();
            } else {
                u32 result = mCheckRequest.getCheckResult();
                if (!mCheckRequest.finish()) {
                    return;
                }
                if (result) {
                    mStep = 40;
                } else {
                    mStep++;
                }
            }
            break;
        }
        case 34: {
            mDelayTimer++;
            mCreateRequest.create(sSkipDatFileName, FILE_PERMS, 0);
            mStep++;
            break;
        }
        case 35: {
            mDelayTimer++;
            if (!mCreateRequest.isCompleted()) {
                return;
            }
            NANDResult result = mCreateRequest.getResult();
            if (!mCreateRequest.finish()) {
                return;
            }
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                mpWindow->checkIsWait();
                initializeError();
                return;
            }
            for (int i = 0; i < 3; i++) {
                fileManager->copyFileSkipData(i);
            }
            mStep = 3;
            fileManager->setField0xA842(1);
            break;
        }
        case 40: {
            fileManager->checkFileStatus();
            if (fileManager->isFileInvalid() == true) {
                NandResultTracker::GetInstance()->isFailure(NAND_RESULT_UNKNOWN);
                mpWindow->checkIsWait();
                initializeError();
                return;
            }
            u32 oldTimer = mDelayTimer;
            initializeSaveBanner();
            mDelayTimer = oldTimer;
            break;
        }
    }
    mpWindow->execute();
}

void SaveMgr::initializeWriteSkipDat() {
    beginState(STATE_WRITE_SKIP_DAT);
    if (FileManager::GetInstance()->get_0xA84C() || FileManager::GetInstance()->getField_0xA842() != 1) {
        mStep = 10;
    } else {
        FileManager::GetInstance()->setField0xA84D(1);
        FileManager::GetInstance()->copySelectedFileSkipData();
        FileManager::GetInstance()->loadSaveData(&mWriteRequest, sSkipDatFileName, true);
    }
}

void SaveMgr::executeWriteSkipDat() {
    FileManager *fileManager = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            if (mWriteRequest.isCompleted()) {
                FileManager::GetInstance()->setField0xA84D(0);
                NANDResult result = mWriteRequest.getResult();
                if (NandResultTracker::GetInstance()->isFailure(result)) {
                    if (mWriteRequest.finish()) {
                        mStep = 30;
                    }
                } else {
                    if (mWriteRequest.failedWrite() == true) {
                        if (mWriteRequest.finish()) {
                            mStep = 1;
                        }
                    } else {
                        if (mWriteRequest.finish()) {
                            endState();
                        }
                    }
                }
            }
            break;
        }
        case 1: {
            fileManager->loadSaveData(&mWriteRequest, sSkipDatFileName, true);
            FileManager::GetInstance()->setField0xA84D(1);
            mStep = 0;
            mDelayTimer = 0;
            break;
        }
        case 10: {
            fileManager->setField0xA84D(0);
            endState();
            break;
        }
        case 30: {
            fileManager->setField0xA84D(1);
            mDeleteRequest.doDelete(sSkipDatFileName);
            mStep++;
            break;
        }
        case 31: {
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                if (mDeleteRequest.finish()) {
                    initializeError();
                }
            } else {
                if (mDeleteRequest.finish()) {
                    fileManager->setField0xA842(0);
                    mStep++;
                }
            }
            break;
        }
        case 32: {
            mCheckRequest.check(1, 1);
            mStep++;
            break;
        }
        case 33: {
            if (mCheckRequest.isCompleted()) {
                NANDResult result = mCheckRequest.getResult();
                if (NandResultTracker::GetInstance()->isFailure(result) && mCheckRequest.finish()) {
                    initializeError();
                } else {
                    u32 result = mCheckRequest.getCheckResult();
                    if (mCheckRequest.finish()) {
                        if (result) {
                            mStep = 10;
                        } else {
                            mStep++;
                        }
                    }
                }
            }
            break;
        }
        case 34: {
            mDelayTimer++;
            mCreateRequest.create(sSkipDatFileName, FILE_PERMS, 0);
            mStep++;
            break;
        }
        case 35: {
            if (!mCreateRequest.isCompleted()) {
                return;
            }
            NANDResult result = mCreateRequest.getResult();
            if (!mCreateRequest.finish()) {
                return;
            }
            if (NandResultTracker::GetInstance()->isFailure(result)) {
                initializeError();
                return;
            }
            for (int i = 0; i < 3; i++) {
                fileManager->copyFileSkipData(i);
            }
            fileManager->setField0xA842(1);
            FileManager::GetInstance()->loadSaveData(&mWriteRequest, sSkipDatFileName, true);
            mStep = 0;
            break;
        }
    }
}

void SaveMgr::initializeCopySave() {
    beginState(STATE_COPY_SAVE);
    FileManager::GetInstance()->setField0xA840(0);
    field_0x83B = 1;
}

void SaveMgr::executeCopySave() {
    FileManager *fileManager = FileManager::GetInstance();
    fileManager->copyFile(mCopyFromFile, mCopyToFile);
    fileManager->setSelectedFileNum(mCopyToFile);
    initializeWriteSave();
    FileManager::GetInstance()->setField0xA840(1);
}

void SaveMgr::initializeClearSelectedFile() {
    beginState(STATE_CLEAR_SELECTED_FILE);
    FileManager::GetInstance()->setField0xA840(0);
    field_0x83C = 1;
}

void SaveMgr::executeClearSelectedFile() {
    FileManager::GetInstance()->clearFileA();
    FileManager::GetInstance()->saveFileAToSelectedFile();
    initializeWriteSave();
    FileManager::GetInstance()->setField0xA840(1);
}

void SaveMgr::initializeSave() {
    beginState(STATE_SAVE);
    FileManager::GetInstance()->setField0xA840(0);
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(false);
    FileManager::GetInstance()->setField_0xA843(1);
}

void SaveMgr::executeSave() {
    FileManager *fileManager = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            if (fileManager->get_0xA84C() != 1 && fileManager->getField_0xA843()) {
                if (fileManager->getField_0xA841() == 1) {
                    FileManager::GetInstance()->unsetFileANewFile();
                    FileManager::GetInstance()->copyFileAToSelectedFile();
                    initializeWriteSave();
                    FileManager::GetInstance()->setField0xA840(1);
                } else {
                    FileManager::GetInstance()->copyFileAToSelectedFile();
                    FileManager::GetInstance()->setField0xA840(1);
                    initializeCheckForFreeSpace();
                    field_0x838 = 1;
                }
            }
            break;
        }
    }
}

void SaveMgr::initializeDeleteAllData() {
    beginState(STATE_DELETE_ALL_DATA);
    field_0x83A = 1;
}

void SaveMgr::executeDeleteAllData() {
    NandResultTracker *tracker = NandResultTracker::GetInstance();
    switch (mStep) {
        case 0: {
            FileManager::GetInstance()->setField0xA84D(1);
            mDeleteRequest.doDelete(sBannerBinFileName);
            if (field_0x837 != 1 || FileManager::GetInstance()->isFileInvalid() == 1) {
                FileManager::GetInstance()->initBlankSaveFiles();
            }
            mStep = 1;
            break;
        }
        case 1: {
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (tracker->isFailure(result)) {
                if (mDeleteRequest.finish()) {
                    initializeError();
                }
            } else {
                if (mDeleteRequest.finish()) {
                    mStep = 2;
                }
            }
            break;
        }
        case 2: {
            mDeleteRequest.doDelete(sSaveFileName);
            mStep = 3;
            break;
        }
        case 3: {
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (tracker->isFailure(result)) {
                if (mDeleteRequest.finish()) {
                    initializeError();
                }
            } else {
                if (mDeleteRequest.finish()) {
                    mStep = 4;
                }
            }
            break;
        }
        case 4: {
            mDeleteRequest.doDelete(sSkipDatFileName);
            mStep = 5;
            break;
        }
        case 5: {
            if (!mDeleteRequest.isCompleted()) {
                return;
            }
            NANDResult result = mDeleteRequest.getResult();
            if (tracker->isFailure(result)) {
                if (mDeleteRequest.finish()) {
                    initializeError();
                }
            } else {
                if (mDeleteRequest.finish()) {
                    initializeCheckForFreeSpace();
                }
            }
            break;
        }
    }
}

void SaveMgr::initializeError() {
    FileManager::GetInstance()->setField0xA84D(0);
    field_0x83D = false;
    beginState(STATE_ERROR);
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(true);
    field_0x83F = 0;
}

void SaveMgr::executeError() {
    dLytSystemWindow_c *systemWindow = dLytSystemWindow_c::GetInstance();
    FileManager *fileManager = FileManager::GetInstance();
    switch (mStep) {
        case 0: {
            if (dDvdUnk::FontUnk::GetInstance()->getNandError() == NandResultTracker::ERR_CAT_SAVE_MGR) {
                if (field_0x837 == true) {
                    // "The save data is corrupted. Save data will be restored."
                    if (systemWindow->setProperties("SYS_INIT_05", false, nullptr) == true) {
                        systemWindow->showMaybe();
                        mStep = 20;
                    }
                } else if (field_0x838 == true) {
                    // "Save data could not be created. Try again?[1]Yes[2]No"
                    if (systemWindow->setProperties("SYS_INIT_04", false, nullptr) == true) {
                        systemWindow->showMaybe(4);
                        mStep = 1;
                        dCsBase_c::GetInstance()->setDrawDirectly(true);
                    }
                } else {
                    // "The file cannot be used because nthe data is corrupted. All data saved up to this point will be
                    // lost. Create new save data?[1]Yes[2]No"
                    if (systemWindow->setProperties("SYS_NAND_07", false, nullptr) == true) {
                        systemWindow->showMaybe(4);
                        mStep = 1;
                        dCsBase_c::GetInstance()->setDrawDirectly(true);
                    }
                }
            }
            break;
        }
        case 1: {
            if (systemWindow->getField_0xE10() != 0 && mPad::g_currentCore->downTrigger(/* A */ 0x800) &&
                systemWindow->getField_0xDE0() != 2) {
                if (systemWindow->getField_0xDE0() == 0) {
                    if (systemWindow->fn_80152F60() == true) {
                        mStep = 2;
                    }
                } else {
                    if (systemWindow->fn_80152F50() == true) {
                        mStep = 2;
                    }
                }
            }
            break;
        }
        case 2: {
            if (systemWindow->fn_80152F70() == true) {
                if (systemWindow->getField_0xDE0() == false) {
                    mStep = 3;
                } else {
                    initializeDeleteAllData();
                    dCsBase_c::GetInstance()->setDrawDirectly(false);
                }
            }
            break;
        }
        case 3: {
            if (dScBoot_c::GetInstance() != nullptr) {
                // "You will not be able to save. Do you want to begin your game anyway? [1]Begin[2-]Quit"
                if (systemWindow->setProperties("SYS_SAVE_10", false, nullptr) == true) {
                    systemWindow->showMaybe(4);
                    mpWindow->setNumLines(2);
                    mStep = 4;
                }
            } else {
                // "It is not possible to save. Continue your adventure anyway? [1]Continue[2]Quit"
                if (systemWindow->setProperties("SYS_SAVE_11", false, nullptr) == true) {
                    systemWindow->showMaybe(4);
                    mpWindow->setNumLines(2);
                    mStep = 4;
                }
            }
            break;
        }
        case 4: {
            if (mPad::g_currentCore->downTrigger(/* A */ 0x800) && systemWindow->getField_0xDE0() != 2) {
                if (systemWindow->getField_0xDE0() == 0) {
                    if (systemWindow->fn_80152F60() == true) {
                        mStep = 5;
                    }
                } else {
                    if (systemWindow->fn_80152F50()) {
                        mStep = 10;
                    }
                }
            }
            break;
        }
        case 5: {
            if (systemWindow->fn_80152F70() == true) {
                mStep = 6;
            }
            break;
        }
        case 6: {
            if (field_0x837 == true) {
                // "The save data is corrupted. Save data will be restored."
                if (systemWindow->setProperties("SYS_INIT_05", false, nullptr) == true) {
                    systemWindow->showMaybe(4);
                    mStep = 1;
                }
            } else {
                // "The file cannot be used because nthe data is corrupted. All data saved up to this point will be
                // lost. Create new save data?[1]Yes[2]No"
                if (systemWindow->setProperties("SYS_NAND_07", false, nullptr) == true) {
                    systemWindow->showMaybe(4);
                    mStep = 1;
                }
            }
            break;
        }
        case 10: {
            if (systemWindow->fn_80152F70() == true) {
                dDvdUnk::FontUnk::GetInstance()->clearNandTrackerError();
                if (field_0x837 != true) {
                    fileManager->setField0xA84C(1);
                }
                dCsBase_c::GetInstance()->setDrawDirectly(false);
                endState();
            }
            break;
        }
        case 20: {
            if (systemWindow->fn_80152F70() == true) {
                mStep++;
            }
            break;
        }
        case 21: {
            if (systemWindow->getField_0xDFC() == true && mPad::g_currentCore->downTrigger(/* A */ 0x800) &&
                systemWindow->fn_80152F50() == true) {
                mStep++;
            }
            break;
        }
        case 22: {
            if (systemWindow->fn_80152F70() == true) {
                initializeDeleteAllData();
            }
        }
    }
    if (mpWindow != nullptr) {
        mpWindow->execute();
    }
}

void SaveMgr::initializeNandError() {
    FileManager::GetInstance()->setField0xA84D(0);
    field_0x83D = false;
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(true);
    beginState(STATE_NAND_ERROR);
}

void SaveMgr::executeNandError() {
    FileManager *fileManager = FileManager::GetInstance();
    dLytSystemWindow_c *systemWindow = dLytSystemWindow_c::GetInstance();
    switch (mStep) {
        case 0: {
            bool anyAction = false;
            if (mCheckForFreeSpaceResult == 1) {
                // "There is not enough available space in Wii system memory. Create 1 block of free space either by
                // moving data to an SD Card or deleting data in the Wii console's Data Management screen."
                if (systemWindow->setProperties("SYS_NAND_02", false, nullptr) == true) {
                    anyAction = true;
                }
            } else {
                // "There is not enough available space in Wii system memory. Either move data to an SD Card or delete
                // data in the Wii console's Data Management screen."
                if (systemWindow->setProperties("SYS_NAND_03", false, nullptr) == true) {
                    anyAction = true;
                }
            }
            if (anyAction) {
                systemWindow->showMaybe();
                mStep = 1;
            }
            break;
        }
        case 1: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 2;
            }
            break;
        }
        case 2: {
            if (systemWindow->getField_0xDFC() == 1 && mPad::g_currentCore->downTrigger(/* A */ 0x800) &&
                systemWindow->fn_80152F60() == 1) {
                mStep = 3;
            }
            break;
        }
        case 3: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 4;
            }
            break;
        }
        case 4: {
            // "Do you want to go to the Wii console's Data Management screen to edit data? [1]Yes[2]No"
            if (systemWindow->setProperties("SYS_NAND_04", false, nullptr) == true) {
                systemWindow->showMaybe(4);
                dCsBase_c::GetInstance()->setDrawDirectly(true);
                mStep = 5;
            }
            break;
        }
        case 5: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 6;
            }
            break;
        }
        case 6: {
            if (systemWindow->getField_0xE10() != 0) {
                if (!dReset::Manage_c::GetInstance()->FadeOutCalc() &&
                    !dReset::Manage_c::GetInstance()->IsFaderBlank() && mPad::getCore()->downTrigger(mPad::BUTTON_A) &&
                    systemWindow->getField_0xDE0() != 2 && systemWindow->fn_80152F60() == true) {
                    mStep = 7;
                }
            }
            break;
        }
        case 7: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 8;
            }
            break;
        }
        case 8: {
            if (systemWindow->getField_0xDE0() == 0) {
                mStep = 9;
            } else {
                dCsBase_c::GetInstance()->setDrawDirectly(false);
                dReset::Manage_c::GetInstance()->SetInteriorReturnDataManager();
                endState();
                mIsIdle = false;
            }
            break;
        }
        case 9: {
            // "You will not be able to save. Do you want to begin your game anyway? [1]Begin[2-]Quit"
            if (systemWindow->setProperties("SYS_SAVE_10", false, nullptr) == true) {
                systemWindow->showMaybe(4);
                // Why save mgs window and not system window?
                mpWindow->setNumLines(2);
                mStep = 10;
            }
            break;
        }
        case 10: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 11;
            }
            break;
        }
        case 11: {
            if (systemWindow->getField_0xE10() != 0 && mPad::g_currentCore->downTrigger(/* A */ 0x800) &&
                systemWindow->getField_0xDE0() != 2) {
                if (systemWindow->getField_0xDE0() == 0) {
                    if (systemWindow->fn_80152F60() == true) {
                        mStep = 12;
                    }
                } else {
                    if (systemWindow->fn_80152F50()) {
                        mStep = 13;
                    }
                }
            }
            break;
        }
        case 12: {
            if (systemWindow->fn_80152F70() == 1) {
                mStep = 0;
            }
            break;
        }
        case 13: {
            if (systemWindow->fn_80152F70() == 1) {
                dCsBase_c::GetInstance()->setDrawDirectly(false);
                dDvdUnk::FontUnk::GetInstance()->clearNandTrackerError();
                fileManager->setField0xA84C(1);
                endState();
            }
            break;
        }
    }
}

void SaveMgr::beginState(SaveMgrState_e state) {
    mCurrentState = state;
    field_0x824 = 2;
    mStep = 0;
    mDelayTimer = 0;
}

void SaveMgr::endState() {
    mIsIdle = true;
    FileManager::GetInstance()->setField0xA840(1);
    field_0x83D = false;
    dDvdUnk::FontUnk::GetInstance()->setNeedsPad(true);
    field_0x83B = 0;
    field_0x83C = 0;
    mCurrentState = STATE_MAX;
    field_0x83E = 0;
    field_0x83F = 0;
}

s32 SaveMgr::getFrameRate() {
    return 60 / dSys_c::getFrameRate();
}

NandRequestCheckIsFile::NandRequestCheckIsFile(const char *path) : mIsFile(0) {
    mPath = path;
}

bool NandRequestCheckIsFile::execute() {
    u8 type = 0;
    mStatus = NANDGetType(mPath, &type);
    if (mStatus == NAND_RESULT_OK && type == NAND_FILE_TYPE_FILE) {
        mIsFile = 1;
    } else {
        mIsFile = 0;
    }
    return true;
}

bool NandRequestCheckIsFileHolder::checkIsFile(const char *path) {
    if (mpRequest != nullptr) {
        return false;
    }
    NandRequestCheckIsFile *req = new NandRequestCheckIsFile(path);
    if (req == nullptr) {
        return false;
    }
    NandRequestThread::GetInstance()->enqueueRequest(req);
    mpRequest = req;
    return true;
}

bool NandRequestCheckIsFileHolder::getCheckResult() const {
    return static_cast<NandRequestCheckIsFile *>(mpRequest)->mIsFile;
}

NandRequestSaveBanner::NandRequestSaveBanner(
    const char *filePath, TPLPalette *palette, const wchar_t *title, const wchar_t *subtitle, u8 perm, u8 attr
)
    : NandRequestCreate(filePath, perm, attr),
      mpPalette(palette),
      mpTitle(title),
      mpSubTitle(subtitle),
      mFailed(false) {}

bool NandRequestSaveBanner::execute() {
    if (!NandRequestCreate::execute()) {
        return true;
    }

    char homeDir[64];
    NANDFileInfo info;
    mStatus = NANDOpen(mFilePath, &info, NAND_ACCESS_WRITE);
    if (mStatus != NAND_RESULT_OK) {
        return mStatus;
    }

    if (writeBannerTpl(&info) != 0x72A0) {
        NANDResult res = NANDClose(&info);
        if (res != NAND_RESULT_OK) {
            mStatus = res;
        }
        mFailed = true;
        return false;
    }

    mStatus = NANDClose(&info);
    mStatus = NANDGetHomeDir(homeDir);
    if (mStatus != NAND_RESULT_OK) {
        return mStatus;
    }

    mStatus = NANDMove(mFilePath, homeDir);
    return true;
}

#define NUM_BANNER_ICONS 1

u32 NandRequestSaveBanner::writeBannerTpl(NANDFileInfo *info) {
    static NANDBanner sBanner;
    NANDInitBanner(&sBanner, 0, getTitle(), getSubTitle());
    TPLPalette *palette = getPalette();
    TPLDescriptor *tpl = TPLGet(palette, 0);
    std::memcpy(&sBanner.bannerTexture, tpl->texHeader->data, NAND_BANNER_TEXTURE_SIZE);
    for (s32 i = 0; i < NUM_BANNER_ICONS;) {
        TPLDescriptor *tpl = TPLGet(palette, i + 1);
        std::memcpy(sBanner.iconTexture[0], tpl->texHeader->data, NAND_BANNER_ICON_TEXTURE_SIZE);
        // ah yes
        s32 idx = i++;
        NAND_SET_ICON_SPEED(&sBanner, idx, 2);
    }
    NAND_SET_ICON_SPEED(&sBanner, NUM_BANNER_ICONS, 0);
    // How is this calculated?
    return NANDWrite(info, &sBanner, 0x72A0);
}

bool NandRequestSaveBannerHolder::saveBanner(
    const char *filePath, TPLPalette *palette, const wchar_t *title, const wchar_t *subtitle, u8 perm, u8 attr
) {
    if (mpRequest != nullptr) {
        return false;
    }
    NandRequestSaveBanner *req = new NandRequestSaveBanner(filePath, palette, title, subtitle, perm, attr);
    if (req == nullptr) {
        return false;
    }
    NandRequestThread::GetInstance()->enqueueRequest(req);
    mpRequest = req;
    return true;
}

bool NandRequestSaveBannerHolder::hasFailed() const {
    return static_cast<NandRequestSaveBanner *>(mpRequest)->mFailed;
}

NandRequestLoadSaveFile::NandRequestLoadSaveFile(const char *path, s32 readSize, s32 mountDirection, EGG::Heap *heap)
    : mpHeap(heap), mpBuf(nullptr), mBufLen(0), mMountDirection(mountDirection), mReadSize(readSize), mUnused(false) {
    mPath = path;
    if (mpHeap == nullptr) {
        mpHeap = dHeap::work2Heap.heap;
    }
}

bool NandRequestLoadSaveFile::execute() {
    NANDFileInfo info;
    mStatus = NANDOpen(mPath, &info, NAND_ACCESS_READ);
    if (mStatus != NAND_RESULT_OK) {
        return true;
    }

    u32 length;
    mStatus = NANDGetLength(&info, &length);
    if (mStatus != NAND_RESULT_OK) {
        mStatus = NAND_RESULT_AUTHENTICATION; // ??
        NANDResult res = NANDClose(&info);
        if (res != NAND_RESULT_OK) {
            mStatus = res;
        }
        return true;
    }

    if (length != mReadSize) {
        mStatus = NAND_RESULT_AUTHENTICATION; // ??
        NANDResult res = NANDClose(&info);
        if (res != NAND_RESULT_OK) {
            mStatus = res;
        }
        return true;
    }
    s32 align = mMountDirection == 0 ? 0x20 : -0x20;
    mpBuf = mpHeap->alloc(length, align);
    if (mpBuf == nullptr) {
        mStatus = NAND_RESULT_AUTHENTICATION; // ??
        NANDResult res = NANDClose(&info);
        if (res != NAND_RESULT_OK) {
            mStatus = res;
        }
        return false;
    }

    mBufLen = length;
    mStatus = NANDRead(&info, mpBuf, mBufLen);
    NANDResult res = NANDClose(&info);
    if (mStatus != mBufLen && mStatus >= NAND_RESULT_OK) {
        mStatus = NAND_RESULT_AUTHENTICATION; // ??
    } else if (mStatus != NAND_RESULT_ECC_CRIT && mStatus != NAND_RESULT_AUTHENTICATION) {
        mStatus = res;
    }
    return true;
}

void NandRequestLoadSaveFile::remove() {
    if (mpBuf != nullptr) {
        mpHeap->free(mpBuf);
        mpBuf = nullptr;
        mBufLen = 0;
    }
}

bool NandRequestLoadSaveFileHolder::loadSave(const char *path, s32 readSize, s32 mountDirection, EGG::Heap *heap) {
    if (mpRequest != nullptr) {
        return false;
    }
    NandRequestLoadSaveFile *req = new NandRequestLoadSaveFile(path, readSize, mountDirection, heap);
    if (req == nullptr) {
        return false;
    }
    NandRequestThread::GetInstance()->enqueueRequest(req);
    mpRequest = req;
    return true;
}

void *NandRequestLoadSaveFileHolder::getBuf() const {
    return static_cast<NandRequestLoadSaveFile *>(mpRequest)->mpBuf;
}

u32 NandRequestLoadSaveFileHolder::getLen() const {
    return static_cast<NandRequestLoadSaveFile *>(mpRequest)->mBufLen;
}

void NandRequestLoadSaveFileHolder::remove() {
    static_cast<NandRequestLoadSaveFile *>(mpRequest)->remove();
}

NandRequestDelete::NandRequestDelete(const char *path) {
    mPath = path;
}

bool NandRequestDelete::execute() {
    mStatus = NANDDelete(mPath);
    return true;
}

bool NandRequestDeleteHolder::doDelete(const char *path) {
    if (mpRequest != nullptr) {
        return false;
    }
    NandRequestDelete *req = new NandRequestDelete(path);
    if (req == nullptr) {
        return false;
    }
    NandRequestThread::GetInstance()->enqueueRequest(req);
    mpRequest = req;
    return true;
}
