#include "global.h"
#include "battle.h"
#include "battle_controllers.h"
#include "battle_pyramid_bag.h"
#include "bg.h"
#include "decompress.h"
#include "event_data.h"
#include "field_effect.h"
#include "field_weather.h"
#include "graphics.h"
#include "gpu_regs.h"
#include "international_string_util.h"
#include "item.h"
#include "item_icon.h"
#include "item_menu.h"
#include "item_use.h"
#include "list_menu.h"
#include "mail.h"
#include "main.h"
#include "malloc.h"
#include "menu.h"
#include "menu_helpers.h"
#include "overworld.h"
#include "palette.h"
#include "party_menu.h"
#include "task.h"
#include "text_window.h"
#include "scanline_effect.h"
#include "script.h"
#include "sound.h"
#include "string_util.h"
#include "strings.h"
#include "constants/items.h"
#include "constants/rgb.h"
#include "constants/songs.h"

#define TAG_SCROLL_ARROW  2910
#define TAG_PYRAMID_BAG   4132
#define TAG_ITEM_ICON     4133
#define TAG_ITEM_ICON_ALT 4134

#define POS_NONE ((u8)-1)

enum {
    WIN_LIST,
    WIN_INFO,
    WIN_MSG,
    WIN_TOSS_NUM,
};

EWRAM_DATA struct PyramidBagMenu *gPyramidBagMenu = NULL;
EWRAM_DATA struct PyramidBagMenuState gPyramidBagMenuState = {0};

static void Task_HandlePyramidBagInput(u8);
static void Task_ChooseItemsToTossFromPyramidBag(u8);
static void Task_ClosePyramidBag(u8);
static void Task_BeginItemSwap(u8);
static void OpenContextMenu(u8);
static void TryCloseBagToGiveItem(u8);
static void HandleMenuActionInput_2x2(u8);
static void HandleMenuActionInput_SingleRow(u8);
static void Task_WaitCloseErrorMessage(u8);
static void SetTaskToMainPyramidBagInputHandler(u8);
static void AskConfirmToss(u8);
static void Task_ChooseHowManyToToss(u8);
static void Task_TossItem(u8);
static void ShowCantHoldMessage(u8);
static void PerformItemSwap(u8);
static void Task_ItemSwapHandleInput(u8);
static void CancelItemSwap(u8);
static void SetBagItemsListTemplate(void);
static void CB2_LoadPyramidBagMenu(void);
static void InitPyramidBagBgs(void);
static void AddScrollArrows(void);
static void CreatePyramidBagInputTask(void);
static void InitPyramidBagScroll(void);
static void InitPyramidBagWindows(void);
static void CreatePyramidBagSprite(void);
static void CreateSwapLine(void);
static void LoadPyramidBagPalette(void);
static void ShakePyramidBag(void);
static void ShowNumToToss(void);
static void CloseBattlePyramidBagTextWindow(void);
static bool8 LoadPyramidBagGfx(void);
static bool8 LoadPyramidBagMenu(void);
static void ShowItemIcon(u16, u8);
static void CopyBagItemName(u8 *, u16);
static void FreeItemIconSpriteByAltId(u8);
static void PrintItemDescription(s32);
static void PrintSelectorArrowAtPos(u8, u8);
static void PyramidBagPrint(u8, const u8 *, u8, u8, u8, u8, u8, u8);
static void PyramidBagPrint_Quantity(u8, const u8 *, u8, u8, u8, u8, u8, u8);
static u8 OpenMenuActionWindowById(u8);
static void CloseMenuActionWindowById(u8);
static void PrintMenuActionText_SingleRow(u8);
static void PrintMenuActionText_MultiRow(u8, u8, u8);
static bool8 IsValidMenuAction(s8);
static void CreatePyramidBagYesNo(u8, const struct YesNoFuncTable *);
static void DrawTossNumberWindow(u8);
static void UpdateSwapLinePos(u8);
static void SetSwapLineInvisibility(bool8);
static void SpriteCB_BagWaitForShake(struct Sprite *);
static void BagAction_UseOnField(u8);
static void BagAction_Toss(u8);
static void BagAction_Give(u8);
static void BagAction_Cancel(u8);
static void BagAction_UseInBattle(u8);
static void BagCursorMoved(s32, bool8, struct ListMenu *);
static void PrintItemQuantity(u8 windowId, u32 itemId, u8 y);
static void TossItem(u8);
static void DontTossItem(u8);

static const struct BgTemplate sBgTemplates[] =
{
    {
        .bg = 0,
        .charBaseIndex = 0,
        .mapBaseIndex = 31,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 1,
        .baseTile = 0
    },
    {
        .bg = 1,
        .charBaseIndex = 0,
        .mapBaseIndex = 30,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 0,
        .baseTile = 0
    },
    {
        .bg = 2,
        .charBaseIndex = 3,
        .mapBaseIndex = 29,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 2,
        .baseTile = 0
    },
};

static const struct ListMenuTemplate sListMenuTemplate =
{
    .items = NULL,
    .moveCursorFunc = BagCursorMoved,
    .itemPrintFunc = PrintItemQuantity,
    .totalItems = 0,
    .maxShowed = 0,
    .windowId = WIN_LIST,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 0,
    .cursorShadowPal = 3,
    .lettersSpacing = 0,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = FONT_NARROW,
    .cursorKind = 0
};

enum {
    ACTION_USE_FIELD,
    ACTION_TOSS,
    ACTION_GIVE,
    ACTION_CANCEL,
    ACTION_USE_BATTLE,
    ACTION_DUMMY,
};

static const struct MenuAction sMenuActions[] =
{
    [ACTION_USE_FIELD] =    { gMenuText_Use, BagAction_UseOnField },
    [ACTION_TOSS] =         { gMenuText_Toss, BagAction_Toss },
    [ACTION_GIVE] =         { gMenuText_Give, BagAction_Give },
    [ACTION_CANCEL] =       { gText_Cancel2, BagAction_Cancel },
    [ACTION_USE_BATTLE] =   { gMenuText_Use, BagAction_UseInBattle },
    [ACTION_DUMMY] =        { gText_EmptyString2, NULL },
};

static const u8 sMenuActionIds_Field[] = {ACTION_USE_FIELD, ACTION_GIVE, ACTION_TOSS, ACTION_CANCEL};
static const u8 sMenuActionIds_ChooseToss[] = {ACTION_TOSS, ACTION_CANCEL};
static const u8 sMenuActionIds_Battle[] = {ACTION_USE_BATTLE, ACTION_CANCEL};
static const u8 sMenuActionIds_BattleCannotUse[] = {ACTION_CANCEL};

static const struct YesNoFuncTable sYesNoTossFuncions =
{
    TossItem, DontTossItem
};

enum {
    COLORID_DARK_GRAY,
    COLORID_LIGHT_GRAY,
    COLORID_WHITE_BG,
    COLORID_NONE = 0xFF
};

static const u8 sTextColors[][3] =
{
    [COLORID_DARK_GRAY]  = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_DARK_GRAY,  TEXT_COLOR_LIGHT_GRAY},
    [COLORID_LIGHT_GRAY] = {TEXT_COLOR_TRANSPARENT, TEXT_COLOR_LIGHT_GRAY, TEXT_COLOR_WHITE},
    [COLORID_WHITE_BG]   = {TEXT_COLOR_WHITE,       TEXT_COLOR_DARK_GRAY,  TEXT_COLOR_LIGHT_GRAY}, // Unused
};

static const struct WindowTemplate sWindowTemplates[] =
{
    [WIN_LIST] = {
        .bg = 0,
        .tilemapLeft = 14,
        .tilemapTop = 2,
        .width = 15,
        .height = 16,
        .paletteNum = 15,
        .baseBlock = 30
    },
    [WIN_INFO] = {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 13,
        .width = 14,
        .height = 6,
        .paletteNum = 15,
        .baseBlock = 270
    },
    [WIN_MSG] = {
        .bg = 1,
        .tilemapLeft = 2,
        .tilemapTop = 15,
        .width = 27,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 354
    },
    [WIN_TOSS_NUM] = {
        .bg = 1,
        .tilemapLeft = 24,
        .tilemapTop = 17,
        .width = 5,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 462
    },
    DUMMY_WIN_TEMPLATE,
};

enum {
    MENU_WIN_1x1,
    MENU_WIN_1x2,
    MENU_WIN_2x2,
    MENU_WIN_2x3,
    MENU_WIN_YESNO,
};

static const struct WindowTemplate sWindowTemplates_MenuActions[] =
{
    [MENU_WIN_1x1] = {
        .bg = 1,
        .tilemapLeft = 22,
        .tilemapTop = 17,
        .width = 7,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 472
    },
    [MENU_WIN_1x2] = {
        .bg = 1,
        .tilemapLeft = 22,
        .tilemapTop = 15,
        .width = 7,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 472
    },
    [MENU_WIN_2x2] = {
        .bg = 1,
        .tilemapLeft = 15,
        .tilemapTop = 15,
        .width = 14,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 472
    },
    [MENU_WIN_2x3] = { // Unused
        .bg = 1,
        .tilemapLeft = 15,
        .tilemapTop = 13,
        .width = 14,
        .height = 6,
        .paletteNum = 15,
        .baseBlock = 472
    },
    [MENU_WIN_YESNO] = {
        .bg = 1,
        .tilemapLeft = 24,
        .tilemapTop = 15,
        .width = 5,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 472
    },
};

static const struct OamData sOamData_PyramidBag =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_NORMAL,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = 0,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(64x64),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(64x64),
    .tileNum = 0,
    .priority = 1,
    .paletteNum = 0,
    .affineParam = 0,
};

static const union AnimCmd sAnim_PyramidBag[] =
{
    ANIMCMD_FRAME(0, 4),
    ANIMCMD_END,
};

static const union AnimCmd * const sAnims_PyramidBag[] =
{
    sAnim_PyramidBag,
};

static const union AffineAnimCmd sAffineAnim_PyramidBag_Still[] =
{
    AFFINEANIMCMD_FRAME(256, 256, 0, 0),
    AFFINEANIMCMD_END,
};

static const union AffineAnimCmd sAffineAnim_PyramidBag_Shake[] =
{
    AFFINEANIMCMD_FRAME(0, 0, 254, 2),
    AFFINEANIMCMD_FRAME(0, 0, 2, 4),
    AFFINEANIMCMD_FRAME(0, 0, 254, 4),
    AFFINEANIMCMD_FRAME(0, 0, 2, 2),
    AFFINEANIMCMD_END,
};

enum {
    ANIM_BAG_STILL,
    ANIM_BAG_SHAKE,
};

static const union AffineAnimCmd * const sAffineAnims_PyramidBag[] =
{
    [ANIM_BAG_STILL] = sAffineAnim_PyramidBag_Still,
    [ANIM_BAG_SHAKE] = sAffineAnim_PyramidBag_Shake,
};

static const struct CompressedSpriteSheet sSpriteSheet_PyramidBag = {gBattlePyramidBag_Gfx, 0x0800, TAG_PYRAMID_BAG};

static const struct SpriteTemplate sSpriteTemplate_PyramidBag =
{
    .tileTag = TAG_PYRAMID_BAG,
    .paletteTag = TAG_PYRAMID_BAG,
    .oam = &sOamData_PyramidBag,
    .anims = sAnims_PyramidBag,
    .images = NULL,
    .affineAnims = sAffineAnims_PyramidBag,
    .callback = SpriteCallbackDummy
};

void InitBattlePyramidBagCursorPosition(void)
{
    gPyramidBagMenuState.cursorPosition = 0;
    gPyramidBagMenuState.scrollPosition = 0;
}

void CB2_PyramidBagMenuFromStartMenu(void)
{
    GoToBattlePyramidBagMenu(PYRAMIDBAG_LOC_FIELD, CB2_ReturnToFieldWithOpenMenu);
}

// Unused, CB2_BagMenuFromBattle is used instead
static void OpenBattlePyramidBagInBattle(void)
{
    GoToBattlePyramidBagMenu(PYRAMIDBAG_LOC_BATTLE, CB2_SetUpReshowBattleScreenAfterMenu2);
}

// If the player finishes a round at the Battle Pyramid with insufficient space in their
// Pyramid Bag to store the party's held items, they may choose items to toss in order to
// make room.
void ChooseItemsToTossFromPyramidBag(void)
{
    ScriptContext2_Enable();
    FadeScreen(FADE_TO_BLACK, 0);
    CreateTask(Task_ChooseItemsToTossFromPyramidBag, 10);
}

static void Task_ChooseItemsToTossFromPyramidBag(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        CleanupOverworldWindowsAndTilemaps();
        gFieldCallback2 = CB2_FadeFromPartyMenu;
        GoToBattlePyramidBagMenu(PYRAMIDBAG_LOC_CHOOSE_TOSS, CB2_ReturnToField);
        DestroyTask(taskId);
    }
}

void CB2_ReturnToPyramidBagMenu(void)
{
    GoToBattlePyramidBagMenu(PYRAMIDBAG_LOC_PREV, gPyramidBagMenuState.exitCallback);
}

void GoToBattlePyramidBagMenu(u8 location, void (*exitCallback)(void))
{
    gPyramidBagMenu = AllocZeroed(sizeof(*gPyramidBagMenu));

    if (location != PYRAMIDBAG_LOC_PREV)
        gPyramidBagMenuState.location = location;

    if (exitCallback != NULL)
        gPyramidBagMenuState.exitCallback = exitCallback;

    gPyramidBagMenu->newScreenCallback = NULL;
    gPyramidBagMenu->toSwapPos = POS_NONE;
    gPyramidBagMenu->scrollIndicatorsTaskId = TASK_NONE;

    memset(gPyramidBagMenu->spriteIds, SPRITE_NONE, sizeof(gPyramidBagMenu->spriteIds));
    memset(gPyramidBagMenu->windowIds, WINDOW_NONE, sizeof(gPyramidBagMenu->windowIds));

    SetMainCallback2(CB2_LoadPyramidBagMenu);
}

static void CB2_PyramidBag(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    DoScheduledBgTilemapCopiesToVram();
    UpdatePaletteFade();
}

static void VBlankCB_PyramidBag(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
}

static void CB2_LoadPyramidBagMenu(void)
{
    while (MenuHelpers_ShouldWaitForLinkRecv() != TRUE
        && LoadPyramidBagMenu() != TRUE
        && MenuHelpers_IsLinkActive() != TRUE);
}

static bool8 LoadPyramidBagMenu(void)
{
    switch (gMain.state)
    {
        case 0:
            SetVBlankHBlankCallbacksToNull();
            ClearScheduledBgCopiesToVram();
            gMain.state++;
            break;
        case 1:
            ScanlineEffect_Stop();
            gMain.state++;
            break;
        case 2:
            FreeAllSpritePalettes();
            gMain.state++;
            break;
        case 3:
            ResetPaletteFade();
            gPaletteFade.bufferTransferDisabled = TRUE;
            gMain.state++;
            break;
        case 4:
            ResetSpriteData();
            gMain.state++;
            break;
        case 5:
            if (!MenuHelpers_IsLinkActive())
                ResetTasks();
            gMain.state++;
            break;
        case 6:
            InitPyramidBagBgs();
            gPyramidBagMenu->state = 0;
            gMain.state++;
            break;
        case 7:
            if (LoadPyramidBagGfx())
                gMain.state++;
            break;
        case 8:
            InitPyramidBagWindows();
            gMain.state++;
            break;
        case 9:
            UpdatePyramidBagList();
            UpdatePyramidBagCursorPos();
            InitPyramidBagScroll();
            gMain.state++;
            break;
        case 10:
            SetBagItemsListTemplate();
            gMain.state++;
            break;
        case 11:
            CreatePyramidBagInputTask();
            gMain.state++;
            break;
        case 12:
            CreatePyramidBagSprite();
            gMain.state++;
            break;
        case 13:
            AddScrollArrows();
            gMain.state++;
            break;
        case 14:
            CreateSwapLine();
            gMain.state++;
            break;
        case 15:
            BlendPalettes(PALETTES_ALL, 16, 0);
            gMain.state++;
            break;
        case 16:
            BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
            gPaletteFade.bufferTransferDisabled = FALSE;
            gMain.state++;
            break;
        default:
            SetVBlankCallback(VBlankCB_PyramidBag);
            SetMainCallback2(CB2_PyramidBag);
            return TRUE;
    }
    return FALSE;
}

static void InitPyramidBagBgs(void)
{
    ResetVramOamAndBgCntRegs();
    ResetBgsAndClearDma3BusyFlags(0);
    InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
    SetBgTilemapBuffer(2, gPyramidBagMenu->tilemapBuffer);
    ResetAllBgsCoordinates();
    ScheduleBgCopyTilemapToVram(2);
    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0 |
                                  DISPCNT_OBJ_1D_MAP |
                                  DISPCNT_OBJ_ON);
    ShowBg(0);
    ShowBg(1);
    ShowBg(2);
    SetGpuReg(REG_OFFSET_BLDCNT, 0);
}

static bool8 LoadPyramidBagGfx(void)
{
    switch (gPyramidBagMenu->state)
    {
    case 0:
        ResetTempTileDataBuffers();
        DecompressAndCopyTileDataToVram(2, gBagScreen_Gfx, 0, 0, 0);
        gPyramidBagMenu->state++;
        break;
    case 1:
        if (FreeTempTileDataBuffersIfPossible() != TRUE)
        {
            LZDecompressWram(gBattlePyramidBagTilemap, gPyramidBagMenu->tilemapBuffer);
            gPyramidBagMenu->state++;
        }
        break;
    case 2:
        LoadCompressedPalette(gBattlePyramidBagInterface_Pal, 0, 32);
        gPyramidBagMenu->state++;
        break;
    case 3:
        LoadCompressedSpriteSheet(&sSpriteSheet_PyramidBag);
        gPyramidBagMenu->state++;
        break;
    case 4:
        LoadPyramidBagPalette();
        gPyramidBagMenu->state++;
        break;
    default:
        LoadListMenuSwapLineGfx();
        gPyramidBagMenu->state = 0;
        return TRUE;
    }

    return FALSE;
}

static void SetBagItemsListTemplate(void)
{
    u16 i;
    u16 *itemIds = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode];

    for (i = 0; i < gPyramidBagMenu->listMenuCount - 1; i++)
    {
        CopyBagItemName(gPyramidBagMenu->itemStrings[i], itemIds[i]);
        gPyramidBagMenu->bagListItems[i].name = gPyramidBagMenu->itemStrings[i];
        gPyramidBagMenu->bagListItems[i].id = i;
    }
    StringCopy(gPyramidBagMenu->itemStrings[i], gText_CloseBag);
    gPyramidBagMenu->bagListItems[i].name = gPyramidBagMenu->itemStrings[i];
    gPyramidBagMenu->bagListItems[i].id = LIST_CANCEL;
    gMultiuseListMenuTemplate = sListMenuTemplate;
    gMultiuseListMenuTemplate.totalItems = gPyramidBagMenu->listMenuCount;
    gMultiuseListMenuTemplate.items = gPyramidBagMenu->bagListItems;
    gMultiuseListMenuTemplate.maxShowed = gPyramidBagMenu->listMenuMaxShown;
}

static void CopyBagItemName(u8 *dst, u16 itemId)
{
    if (ItemId_GetPocket(itemId) == POCKET_BERRIES)
    {
        ConvertIntToDecimalStringN(gStringVar1, ITEM_TO_BERRY(itemId), STR_CONV_MODE_LEADING_ZEROS, 2);
        CopyItemName(itemId, gStringVar2);
        StringExpandPlaceholders(dst, gText_NumberItem_TMBerry);
    }
    else
    {
        CopyItemName(itemId, dst);
    }
}

static void BagCursorMoved(s32 itemIndex, bool8 onInit, struct ListMenu *list)
{
    if (onInit != TRUE)
    {
        PlaySE(SE_SELECT);
        ShakePyramidBag();
    }
    if (gPyramidBagMenu->toSwapPos == POS_NONE)
    {
        FreeItemIconSpriteByAltId(gPyramidBagMenu->isAltIcon ^ 1);
        if (itemIndex != LIST_CANCEL)
            ShowItemIcon(gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode][itemIndex], gPyramidBagMenu->isAltIcon);
        else
            ShowItemIcon(0xFFFF, gPyramidBagMenu->isAltIcon); // Show exit arrow if on Cancel
        gPyramidBagMenu->isAltIcon ^= 1;
        PrintItemDescription(itemIndex);
    }
}

static void PrintItemQuantity(u8 windowId, u32 itemIndex, u8 y)
{
    s32 xAlign;
    if (itemIndex == LIST_CANCEL)
        return;

    if (gPyramidBagMenu->toSwapPos != POS_NONE)
    {
        // Performing a swap. Keep a gray selector arrow on the position to swap to
        // and erase the selector arrow anywhere else
        if (gPyramidBagMenu->toSwapPos == (u8)(itemIndex))
            PrintSelectorArrowAtPos(y, COLORID_LIGHT_GRAY);
        else
            PrintSelectorArrowAtPos(y, COLORID_NONE);
    }

    ConvertIntToDecimalStringN(gStringVar1,
                               gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode][itemIndex],
                               STR_CONV_MODE_RIGHT_ALIGN,
                               2);
    StringExpandPlaceholders(gStringVar4, gText_xVar1);
    xAlign = GetStringRightAlignXOffset(FONT_NARROW, gStringVar4, 119);
    PyramidBagPrint_Quantity(windowId, gStringVar4, xAlign, y, 0, 0, TEXT_SKIP_DRAW, COLORID_DARK_GRAY);
}

static void PrintItemDescription(s32 listMenuId)
{
    const u8 *desc;
    if (listMenuId != LIST_CANCEL)
    {
        desc = ItemId_GetDescription(gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode][listMenuId]);
    }
    else
    {
        StringCopy(gStringVar1, gPyramidBagMenu_ReturnToStrings[gPyramidBagMenuState.location]);
        StringExpandPlaceholders(gStringVar4, gText_ReturnToVar1);
        desc = gStringVar4;
    }
    FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
    PyramidBagPrint(WIN_INFO, desc, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
}

static void AddScrollArrows(void)
{
    if (gPyramidBagMenu->scrollIndicatorsTaskId == TASK_NONE)
        gPyramidBagMenu->scrollIndicatorsTaskId = AddScrollIndicatorArrowPairParameterized(SCROLL_ARROW_UP, 172, 12, 148,
                                                                                            gPyramidBagMenu->listMenuCount - gPyramidBagMenu->listMenuMaxShown,
                                                                                            TAG_SCROLL_ARROW, TAG_SCROLL_ARROW,
                                                                                            &gPyramidBagMenuState.scrollPosition);
}

static void RemoveScrollArrow(void)
{
    if (gPyramidBagMenu->scrollIndicatorsTaskId != TASK_NONE)
    {
        RemoveScrollIndicatorArrowPair(gPyramidBagMenu->scrollIndicatorsTaskId);
        gPyramidBagMenu->scrollIndicatorsTaskId = TASK_NONE;
    }
}

#define tListTaskId data[0]
#define tListPos    data[1]
#define tQuantity   data[2]
#define tNumToToss  data[8]

static void CreatePyramidBagInputTask(void)
{
    u8 taskId = CreateTask(Task_HandlePyramidBagInput, 0);
    s16 *data = gTasks[taskId].data;
    tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, gPyramidBagMenuState.scrollPosition, gPyramidBagMenuState.cursorPosition);
}

static void SwapItems(u8 id1, u8 id2)
{
    u16 temp;
    u16 *itemIds = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode];
    u8 *quantities = gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode];

    SWAP(itemIds[id1], itemIds[id2], temp);
    SWAP(quantities[id1], quantities[id2], temp);
}

static void MovePyramidBagItemSlotInList(u8 from, u8 to)
{
    u16 *itemIds = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode];
    u8 *quantities = gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode];

    if (from != to)
    {
        s16 i;
        u16 firstSlotItemId = itemIds[from];
        u8 firstSlotQuantity = quantities[from];

        if (to > from)
        {
            to--;
            for (i = from; i < to; i++)
            {
                itemIds[i] = itemIds[i + 1];
                quantities[i] = quantities[i + 1];
            }
        }
        else
        {
            for (i = from; i > to; i--)
            {
                itemIds[i] = itemIds[i - 1];
                quantities[i] = quantities[i - 1];
            }
        }
        itemIds[to] = firstSlotItemId;
        quantities[to] = firstSlotQuantity;
    }
}

static void CompactItems(void)
{
    u8 i, j;
    u16 *itemIds = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode];
    u8 *quantities = gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode];

    for (i = 0; i < PYRAMID_BAG_ITEMS_COUNT; i++)
    {
        if (itemIds[i] == ITEM_NONE || quantities[i] == 0)
        {
            itemIds[i] = ITEM_NONE;
            quantities[i] = 0;
        }
    }
    for (i = 0; i < PYRAMID_BAG_ITEMS_COUNT - 1; i++)
    {
        for (j = i + 1; j < PYRAMID_BAG_ITEMS_COUNT; j++)
        {
            if (itemIds[i] == ITEM_NONE || quantities[i] == 0)
                SwapItems(i, j);
        }
    }
}

void UpdatePyramidBagList(void)
{
    u16 i;
    u16 *itemIds = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode];

    CompactItems();
    gPyramidBagMenu->listMenuCount = 0;
    for (i = 0; i < PYRAMID_BAG_ITEMS_COUNT; i++)
    {
        if (itemIds[i] != ITEM_NONE)
            gPyramidBagMenu->listMenuCount++;
    }
    gPyramidBagMenu->listMenuCount++;
    if (gPyramidBagMenu->listMenuCount > 8)
        gPyramidBagMenu->listMenuMaxShown = 8;
    else
        gPyramidBagMenu->listMenuMaxShown = gPyramidBagMenu->listMenuCount;
}

void UpdatePyramidBagCursorPos(void)
{
    if (gPyramidBagMenuState.scrollPosition != 0 && gPyramidBagMenuState.scrollPosition + gPyramidBagMenu->listMenuMaxShown > gPyramidBagMenu->listMenuCount)
        gPyramidBagMenuState.scrollPosition = gPyramidBagMenu->listMenuCount - gPyramidBagMenu->listMenuMaxShown;

    if (gPyramidBagMenuState.scrollPosition + gPyramidBagMenuState.cursorPosition >= gPyramidBagMenu->listMenuCount)
    {
        if (gPyramidBagMenu->listMenuCount == 0)
            gPyramidBagMenuState.cursorPosition = 0;
        else
            gPyramidBagMenuState.cursorPosition = gPyramidBagMenu->listMenuCount - 1;
    }
}

static void InitPyramidBagScroll(void)
{
    u8 i;

    if (gPyramidBagMenuState.cursorPosition > 4)
    {
        for (i = 0; i <= gPyramidBagMenuState.cursorPosition - 4; i++)
        {
            if (gPyramidBagMenuState.scrollPosition + gPyramidBagMenu->listMenuMaxShown == gPyramidBagMenu->listMenuCount)
                break;

            gPyramidBagMenuState.cursorPosition--;
            gPyramidBagMenuState.scrollPosition++;
        }
    }
}

static void PrintSelectorArrow(u8 listMenuTaskId, u8 colorId)
{
    u8 y = ListMenuGetYCoordForPrintingArrowCursor(listMenuTaskId);
    PrintSelectorArrowAtPos(y, colorId);
}

static void PrintSelectorArrowAtPos(u8 y, u8 colorId)
{
    if (colorId == COLORID_NONE) // If 'no color', erase arrow
        FillWindowPixelRect(WIN_LIST, PIXEL_FILL(0), 0, y, GetMenuCursorDimensionByFont(FONT_NORMAL, 0), GetMenuCursorDimensionByFont(FONT_NORMAL, 1));
    else
        PyramidBagPrint(WIN_LIST, gText_SelectorArrow2, 0, y, 0, 0, 0, colorId);
}

void CloseBattlePyramidBag(u8 taskId)
{
    BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
    gTasks[taskId].func = Task_ClosePyramidBag;
}

static void Task_ClosePyramidBag(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (!gPaletteFade.active)
    {
        DestroyListMenuTask(tListTaskId, &gPyramidBagMenuState.scrollPosition, &gPyramidBagMenuState.cursorPosition);

        // If ready for a new screen (e.g. party menu for giving an item) go to that screen
        // Otherwise exit the bag and use callback set up when the bag was first opened
        if (gPyramidBagMenu->newScreenCallback != NULL)
            SetMainCallback2(gPyramidBagMenu->newScreenCallback);
        else
            SetMainCallback2(gPyramidBagMenuState.exitCallback);
        RemoveScrollArrow();
        ResetSpriteData();
        FreeAllSpritePalettes();
        FreeAllWindowBuffers();
        Free(gPyramidBagMenu);
        DestroyTask(taskId);
    }
}

static void Task_HandlePyramidBagInput(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (MenuHelpers_ShouldWaitForLinkRecv() == TRUE || gPaletteFade.active)
        return;

    if (JOY_NEW(SELECT_BUTTON))
    {
        if (gPyramidBagMenuState.location != PYRAMIDBAG_LOC_PARTY)
        {
            ListMenuGetScrollAndRow(tListTaskId, &gPyramidBagMenuState.scrollPosition, &gPyramidBagMenuState.cursorPosition);
            if (gPyramidBagMenuState.scrollPosition + gPyramidBagMenuState.cursorPosition != gPyramidBagMenu->listMenuCount - 1)
            {
                PlaySE(SE_SELECT);
                Task_BeginItemSwap(taskId);
            }
        }
    }
    else
    {
        s32 listId = ListMenu_ProcessInput(tListTaskId);
        ListMenuGetScrollAndRow(tListTaskId, &gPyramidBagMenuState.scrollPosition, &gPyramidBagMenuState.cursorPosition);
        switch (listId)
        {
        case LIST_NOTHING_CHOSEN:
            break;
        case LIST_CANCEL:
            PlaySE(SE_SELECT);
            gSpecialVar_ItemId = ITEM_NONE;
            CloseBattlePyramidBag(taskId);
            break;
        default:
            PlaySE(SE_SELECT);
            gSpecialVar_ItemId = gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode][listId];
            tListPos = listId;
            tQuantity = gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode][listId];
            if (gPyramidBagMenuState.location == PYRAMIDBAG_LOC_PARTY)
                TryCloseBagToGiveItem(taskId);
            else
                OpenContextMenu(taskId);
            break;
        }
    }
}

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

    RemoveScrollArrow();
    PrintSelectorArrow(tListTaskId, COLORID_LIGHT_GRAY);
    switch (gPyramidBagMenuState.location)
    {
    default:
//  case PYRAMIDBAG_LOC_FIELD:
//  case PYRAMIDBAG_LOC_PARTY:
        gPyramidBagMenu->menuActionIds = sMenuActionIds_Field;
        gPyramidBagMenu->menuActionsCount = ARRAY_COUNT(sMenuActionIds_Field);
        break;
    case PYRAMIDBAG_LOC_BATTLE:
        if (ItemId_GetBattleUsage(gSpecialVar_ItemId))
        {
            gPyramidBagMenu->menuActionIds = sMenuActionIds_Battle;
            gPyramidBagMenu->menuActionsCount = ARRAY_COUNT(sMenuActionIds_Battle);
        }
        else
        {
            gPyramidBagMenu->menuActionIds = sMenuActionIds_BattleCannotUse;
            gPyramidBagMenu->menuActionsCount = ARRAY_COUNT(sMenuActionIds_BattleCannotUse);
        }
        break;
    case PYRAMIDBAG_LOC_CHOOSE_TOSS:
        gPyramidBagMenu->menuActionIds = sMenuActionIds_ChooseToss;
        gPyramidBagMenu->menuActionsCount = ARRAY_COUNT(sMenuActionIds_ChooseToss);
        break;
    }

    CopyItemName(gSpecialVar_ItemId, gStringVar1);
    StringExpandPlaceholders(gStringVar4, gText_Var1IsSelected);
    FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
    PyramidBagPrint(WIN_INFO, gStringVar4, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
    if (gPyramidBagMenu->menuActionsCount == 1)
        PrintMenuActionText_SingleRow(OpenMenuActionWindowById(MENU_WIN_1x1));
    else if (gPyramidBagMenu->menuActionsCount == 2)
        PrintMenuActionText_SingleRow(OpenMenuActionWindowById(MENU_WIN_1x2));
    else
        PrintMenuActionText_MultiRow(OpenMenuActionWindowById(MENU_WIN_2x2), 2, 2);

    if (gPyramidBagMenu->menuActionsCount == 2 * 2) // Assumes any non 2x2 menu is single-row
        gTasks[taskId].func = HandleMenuActionInput_2x2;
    else
        gTasks[taskId].func = HandleMenuActionInput_SingleRow;
}

static void PrintMenuActionText_SingleRow(u8 windowId)
{
    PrintMenuActionTexts(windowId, FONT_NARROW, 8, 1, 0, 0x10, gPyramidBagMenu->menuActionsCount, sMenuActions, gPyramidBagMenu->menuActionIds);
    InitMenuInUpperLeftCornerNormal(windowId, gPyramidBagMenu->menuActionsCount, 0);
}

static void PrintMenuActionText_MultiRow(u8 windowId, u8 horizontalCount, u8 verticalCount)
{
    PrintMenuActionGrid(windowId, FONT_NARROW, 8, 1, 56, horizontalCount, verticalCount, sMenuActions, gPyramidBagMenu->menuActionIds);
    InitMenuActionGrid(windowId, 56, horizontalCount, verticalCount, 0);
}

static void HandleMenuActionInput_SingleRow(u8 taskId)
{
    if (MenuHelpers_ShouldWaitForLinkRecv() != TRUE)
    {
        s32 id = Menu_ProcessInputNoWrap();
        switch (id)
        {
        case MENU_NOTHING_CHOSEN:
            break;
        case MENU_B_PRESSED:
            PlaySE(SE_SELECT);
            sMenuActions[ACTION_CANCEL].func.void_u8(taskId);
            break;
        default:
            PlaySE(SE_SELECT);
            if (sMenuActions[gPyramidBagMenu->menuActionIds[id]].func.void_u8 != NULL)
                sMenuActions[gPyramidBagMenu->menuActionIds[id]].func.void_u8(taskId);
            break;
        }
    }
}

static void HandleMenuActionInput_2x2(u8 taskId)
{
    if (MenuHelpers_ShouldWaitForLinkRecv() != TRUE)
    {
        s8 id = Menu_GetCursorPos();
        if (JOY_NEW(DPAD_UP))
        {
            if (id > 0 && IsValidMenuAction(id - 2))
            {
                PlaySE(SE_SELECT);
                ChangeMenuGridCursorPosition(MENU_CURSOR_DELTA_NONE, MENU_CURSOR_DELTA_UP);
            }
        }
        else if (JOY_NEW(DPAD_DOWN))
        {
            if (id < gPyramidBagMenu->menuActionsCount - 2 && IsValidMenuAction(id + 2))
            {
                PlaySE(SE_SELECT);
                ChangeMenuGridCursorPosition(MENU_CURSOR_DELTA_NONE, MENU_CURSOR_DELTA_DOWN);
            }
        }
        else if (JOY_NEW(DPAD_LEFT) || GetLRKeysPressed() == MENU_L_PRESSED)
        {
            if (id & 1 && IsValidMenuAction(id - 1))
            {
                PlaySE(SE_SELECT);
                ChangeMenuGridCursorPosition(MENU_CURSOR_DELTA_LEFT, MENU_CURSOR_DELTA_NONE);
            }
        }
        else if (JOY_NEW(DPAD_RIGHT) || GetLRKeysPressed() == MENU_R_PRESSED)
        {
            if (!(id & 1) && IsValidMenuAction(id + 1))
            {
                PlaySE(SE_SELECT);
                ChangeMenuGridCursorPosition(MENU_CURSOR_DELTA_RIGHT, MENU_CURSOR_DELTA_NONE);
            }
        }
        else if (JOY_NEW(A_BUTTON))
        {
            PlaySE(SE_SELECT);
            if (sMenuActions[gPyramidBagMenu->menuActionIds[id]].func.void_u8 != NULL)
                sMenuActions[gPyramidBagMenu->menuActionIds[id]].func.void_u8(taskId);
        }
        else if (JOY_NEW(B_BUTTON))
        {
            PlaySE(SE_SELECT);
            sMenuActions[ACTION_CANCEL].func.void_u8(taskId);
        }
    }
}

static bool8 IsValidMenuAction(s8 actionTableId)
{
    if (actionTableId < 0)
        return FALSE;
    else if (actionTableId > gPyramidBagMenu->menuActionsCount)
        return FALSE;
    else if (gPyramidBagMenu->menuActionIds[actionTableId] == ACTION_DUMMY)
        return FALSE;
    else
        return TRUE;
}

static void CloseMenuActionWindow(void)
{
    if (gPyramidBagMenu->menuActionsCount == 1)
        CloseMenuActionWindowById(MENU_WIN_1x1);
    else if (gPyramidBagMenu->menuActionsCount == 2)
        CloseMenuActionWindowById(MENU_WIN_1x2);
    else
        CloseMenuActionWindowById(MENU_WIN_2x2);
}

static void BagAction_UseOnField(u8 taskId)
{
    u8 pocketId = ItemId_GetPocket(gSpecialVar_ItemId);

    if (pocketId == POCKET_KEY_ITEMS
        || pocketId == POCKET_POKE_BALLS
        || pocketId == POCKET_TM_HM
        || ItemIsMail(gSpecialVar_ItemId) == TRUE)
    {
        CloseMenuActionWindow();
        DisplayItemMessageInBattlePyramid(taskId, gText_DadsAdvice, Task_CloseBattlePyramidBagMessage);
    }
    else if (ItemId_GetFieldFunc(gSpecialVar_ItemId) != NULL)
    {
        CloseMenuActionWindow();
        FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
        ScheduleBgCopyTilemapToVram(0);
        ItemId_GetFieldFunc(gSpecialVar_ItemId)(taskId);
    }
}

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

    CloseMenuActionWindow();
    PrintItemDescription(tListPos);
    ScheduleBgCopyTilemapToVram(0);
    ScheduleBgCopyTilemapToVram(1);
    PrintSelectorArrow(tListTaskId, COLORID_DARK_GRAY);
    SetTaskToMainPyramidBagInputHandler(taskId);
}

static void SetTaskToMainPyramidBagInputHandler(u8 taskId)
{
    AddScrollArrows();
    gTasks[taskId].func = Task_HandlePyramidBagInput;
}

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

    CloseMenuActionWindow();
    tNumToToss = 1;
    if (tQuantity == 1)
    {
        AskConfirmToss(taskId);
    }
    else
    {
        CopyItemName(gSpecialVar_ItemId, gStringVar1);
        StringExpandPlaceholders(gStringVar4, gText_TossHowManyVar1s);
        FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
        PyramidBagPrint(WIN_INFO, gStringVar4, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
        ShowNumToToss();
        gTasks[taskId].func = Task_ChooseHowManyToToss;
    }
}

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

    CopyItemName(gSpecialVar_ItemId, gStringVar1);
    ConvertIntToDecimalStringN(gStringVar2, tNumToToss, STR_CONV_MODE_LEFT_ALIGN, 2);
    StringExpandPlaceholders(gStringVar4, gText_ConfirmTossItems);
    FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
    PyramidBagPrint(WIN_INFO, gStringVar4, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
    CreatePyramidBagYesNo(taskId, &sYesNoTossFuncions);
}

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

    PrintItemDescription(tListPos);
    PrintSelectorArrow(tListTaskId, COLORID_DARK_GRAY);
    SetTaskToMainPyramidBagInputHandler(taskId);
}

static void ShowNumToToss(void)
{
    s32 x;
    ConvertIntToDecimalStringN(gStringVar1, 1, STR_CONV_MODE_LEADING_ZEROS, 2);
    StringExpandPlaceholders(gStringVar4, gText_xVar1);
    DrawTossNumberWindow(WIN_TOSS_NUM);
    x = GetStringCenterAlignXOffset(FONT_NORMAL, gStringVar4, 0x28);
    AddTextPrinterParameterized(WIN_TOSS_NUM, FONT_NORMAL, gStringVar4, x, 2, 0, NULL);
}

static void UpdateNumToToss(s16 num)
{
    s32 x;
    ConvertIntToDecimalStringN(gStringVar1, num, STR_CONV_MODE_LEADING_ZEROS, 2);
    StringExpandPlaceholders(gStringVar4, gText_xVar1);
    x = GetStringCenterAlignXOffset(FONT_NORMAL, gStringVar4, 0x28);
    AddTextPrinterParameterized(WIN_TOSS_NUM, FONT_NORMAL, gStringVar4, x, 2, 0, NULL);
}

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

    if (AdjustQuantityAccordingToDPadInput(&tNumToToss, tQuantity) == TRUE)
    {
        UpdateNumToToss(tNumToToss);
    }
    else if (JOY_NEW(A_BUTTON))
    {
        // Toss
        PlaySE(SE_SELECT);
        ClearStdWindowAndFrameToTransparent(WIN_TOSS_NUM, 0);
        ClearWindowTilemap(WIN_TOSS_NUM);
        ScheduleBgCopyTilemapToVram(1);
        AskConfirmToss(taskId);
    }
    else if (JOY_NEW(B_BUTTON))
    {
        // Cancel tossing
        PlaySE(SE_SELECT);
        ClearStdWindowAndFrameToTransparent(WIN_TOSS_NUM, 0);
        ClearWindowTilemap(WIN_TOSS_NUM);
        ScheduleBgCopyTilemapToVram(1);
        DontTossItem(taskId);
    }
}

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

    CopyItemName(gSpecialVar_ItemId, gStringVar1);
    ConvertIntToDecimalStringN(gStringVar2, tNumToToss, STR_CONV_MODE_LEFT_ALIGN, 2);
    StringExpandPlaceholders(gStringVar4, gText_ThrewAwayVar2Var1s);
    FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
    PyramidBagPrint(WIN_INFO, gStringVar4, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
    gTasks[taskId].func = Task_TossItem;
}

static void Task_TossItem(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 *scrollOffset = &gPyramidBagMenuState.scrollPosition;
    u16 *selectedRow = &gPyramidBagMenuState.cursorPosition;

    if (JOY_NEW(A_BUTTON | B_BUTTON))
    {
        PlaySE(SE_SELECT);
        RemovePyramidBagItem(gSpecialVar_ItemId, tNumToToss);
        DestroyListMenuTask(tListTaskId, scrollOffset, selectedRow);
        UpdatePyramidBagList();
        UpdatePyramidBagCursorPos();
        SetBagItemsListTemplate();
        tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollOffset, *selectedRow);
        ScheduleBgCopyTilemapToVram(0);
        SetTaskToMainPyramidBagInputHandler(taskId);
    }
}

static void BagAction_Give(u8 taskId)
{
    CloseMenuActionWindow();
    if (ItemIsMail(gSpecialVar_ItemId) == TRUE)
    {
        DisplayItemMessageInBattlePyramid(taskId, gText_CantWriteMail, Task_WaitCloseErrorMessage);
    }
    else if (!ItemId_GetImportance(gSpecialVar_ItemId))
    {
        gPyramidBagMenu->newScreenCallback = CB2_ChooseMonToGiveItem;
        CloseBattlePyramidBag(taskId);
    }
    else
    {
        ShowCantHoldMessage(taskId);
    }
}

static void ShowCantHoldMessage(u8 taskId)
{
    CopyItemName(gSpecialVar_ItemId, gStringVar1);
    StringExpandPlaceholders(gStringVar4, gText_Var1CantBeHeld);
    DisplayItemMessageInBattlePyramid(taskId, gStringVar4, Task_WaitCloseErrorMessage);
}

static void Task_WaitCloseErrorMessage(u8 taskId)
{
    if (JOY_NEW(A_BUTTON))
    {
        PlaySE(SE_SELECT);
        Task_CloseBattlePyramidBagMessage(taskId);
    }
}

void Task_CloseBattlePyramidBagMessage(u8 taskId)
{
    s16 *data = gTasks[taskId].data;

    CloseBattlePyramidBagTextWindow();
    PrintItemDescription(tListPos);
    PrintSelectorArrow(tListTaskId, COLORID_DARK_GRAY);
    SetTaskToMainPyramidBagInputHandler(taskId);
}

static void TryCloseBagToGiveItem(u8 taskId)
{
    if (!IsWritingMailAllowed(gSpecialVar_ItemId))
        DisplayItemMessageInBattlePyramid(taskId, gText_CantWriteMail, Task_WaitCloseErrorMessage);
    else if (!ItemId_GetImportance(gSpecialVar_ItemId))
        CloseBattlePyramidBag(taskId);
    else
        ShowCantHoldMessage(taskId);
}

static void BagAction_UseInBattle(u8 taskId)
{
    if (ItemId_GetBattleFunc(gSpecialVar_ItemId) != NULL)
    {
        CloseMenuActionWindow();
        ItemId_GetBattleFunc(gSpecialVar_ItemId)(taskId);
    }
}

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

    tListPos = gPyramidBagMenuState.scrollPosition + gPyramidBagMenuState.cursorPosition;
    gPyramidBagMenu->toSwapPos = tListPos;
    ListMenuSetUnkIndicatorsStructField(tListTaskId, 0x10, 1);
    CopyItemName(gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode][tListPos], gStringVar1);
    StringExpandPlaceholders(gStringVar4, gText_MoveVar1Where);
    FillWindowPixelBuffer(WIN_INFO, PIXEL_FILL(0));
    PyramidBagPrint(WIN_INFO, gStringVar4, 3, 0, 0, 1, 0, COLORID_DARK_GRAY);
    PrintSelectorArrow(tListTaskId, COLORID_LIGHT_GRAY);
    UpdateSwapLinePos(tListPos);
    gTasks[taskId].func = Task_ItemSwapHandleInput;
}

static void Task_ItemSwapHandleInput(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (MenuHelpers_ShouldWaitForLinkRecv() != TRUE)
    {
        if (JOY_NEW(SELECT_BUTTON))
        {
            PlaySE(SE_SELECT);
            ListMenuGetScrollAndRow(tListTaskId, &gPyramidBagMenuState.scrollPosition, &gPyramidBagMenuState.cursorPosition);
            PerformItemSwap(taskId);
        }
        else
        {
            s32 id = ListMenu_ProcessInput(tListTaskId);
            ListMenuGetScrollAndRow(tListTaskId, &gPyramidBagMenuState.scrollPosition, &gPyramidBagMenuState.cursorPosition);
            SetSwapLineInvisibility(FALSE);
            UpdateSwapLinePos(gPyramidBagMenuState.cursorPosition);
            switch (id)
            {
            case LIST_NOTHING_CHOSEN:
                break;
            case LIST_CANCEL:
                PlaySE(SE_SELECT);
                if (JOY_NEW(A_BUTTON))
                    PerformItemSwap(taskId);
                else
                    CancelItemSwap(taskId);
                break;
            default:
                PlaySE(SE_SELECT);
                PerformItemSwap(taskId);
                break;
            }
        }
    }
}

static void PerformItemSwap(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 *scrollOffset = &gPyramidBagMenuState.scrollPosition;
    u16 *selectedRow = &gPyramidBagMenuState.cursorPosition;
    u16 swapPos = *scrollOffset + *selectedRow;

    if (tListPos == swapPos || tListPos == swapPos - 1)
    {
        CancelItemSwap(taskId);
    }
    else
    {
        MovePyramidBagItemSlotInList(tListPos, swapPos);
        gPyramidBagMenu->toSwapPos = POS_NONE;
        SetSwapLineInvisibility(TRUE);
        DestroyListMenuTask(tListTaskId, scrollOffset, selectedRow);
        if (tListPos < swapPos)
            gPyramidBagMenuState.cursorPosition--;
        SetBagItemsListTemplate();
        tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollOffset, *selectedRow);
        SetTaskToMainPyramidBagInputHandler(taskId);
    }
}

static void CancelItemSwap(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 *scrollOffset = &gPyramidBagMenuState.scrollPosition;
    u16 *selectedRow = &gPyramidBagMenuState.cursorPosition;

    gPyramidBagMenu->toSwapPos = POS_NONE;
    SetSwapLineInvisibility(TRUE);
    DestroyListMenuTask(tListTaskId, scrollOffset, selectedRow);
    if (tListPos < *scrollOffset + *selectedRow)
        gPyramidBagMenuState.cursorPosition--;
    SetBagItemsListTemplate();
    tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, *scrollOffset, *selectedRow);
    SetTaskToMainPyramidBagInputHandler(taskId);
}

void TryStoreHeldItemsInPyramidBag(void)
{
    u8 i;
    struct Pokemon *party = gPlayerParty;
    u16 *newItems = Alloc(PYRAMID_BAG_ITEMS_COUNT * sizeof(u16));
    u8 *newQuantities = Alloc(PYRAMID_BAG_ITEMS_COUNT * sizeof(u8));
    u16 heldItem;

    memcpy(newItems, gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode], PYRAMID_BAG_ITEMS_COUNT * sizeof(u16));
    memcpy(newQuantities, gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode], PYRAMID_BAG_ITEMS_COUNT * sizeof(u8));
    for (i = 0; i < 3; i++)
    {
        heldItem = GetMonData(&party[i], MON_DATA_HELD_ITEM);
        if (heldItem != ITEM_NONE && !AddBagItem(heldItem, 1))
        {
            // Cant store party held items in pyramid bag because bag is full
            memcpy(gSaveBlock2Ptr->frontier.pyramidBag.itemId[gSaveBlock2Ptr->frontier.lvlMode], newItems, PYRAMID_BAG_ITEMS_COUNT * sizeof(u16));
            memcpy(gSaveBlock2Ptr->frontier.pyramidBag.quantity[gSaveBlock2Ptr->frontier.lvlMode], newQuantities, PYRAMID_BAG_ITEMS_COUNT * sizeof(u8));
            Free(newItems);
            Free(newQuantities);
            gSpecialVar_Result = 1;
            return;
        }
    }

    heldItem = ITEM_NONE;
    for (i = 0; i < 3; i++)
    {
        SetMonData(&party[i], MON_DATA_HELD_ITEM, &heldItem);
    }
    gSpecialVar_Result = 0;
    Free(newItems);
    Free(newQuantities);
}

static void InitPyramidBagWindows(void)
{
    u8 i;

    InitWindows(sWindowTemplates);
    DeactivateAllTextPrinters();
    LoadUserWindowBorderGfx(0, 0x1, 0xE0);
    LoadMessageBoxGfx(0, 0xA, 0xD0);
    LoadPalette(gStandardMenuPalette, 0xF0, 0x20);

    for (i = 0; i < ARRAY_COUNT(sWindowTemplates); i++)
        FillWindowPixelBuffer(i, PIXEL_FILL(0));

    PutWindowTilemap(WIN_LIST);
    PutWindowTilemap(WIN_INFO);
    ScheduleBgCopyTilemapToVram(0);
    ScheduleBgCopyTilemapToVram(1);
}

static void PyramidBagPrint(u8 windowId, const u8 *src, u8 x, u8 y, u8 letterSpacing, u8 lineSpacing, u8 speed, u8 colorTableId)
{
    AddTextPrinterParameterized4(windowId, FONT_NORMAL, x, y, letterSpacing, lineSpacing, sTextColors[colorTableId], speed, src);
}

static void PyramidBagPrint_Quantity(u8 windowId, const u8 *src, u8 x, u8 y, u8 letterSpacing, u8 lineSpacing, u8 speed, u8 colorTableId)
{
    AddTextPrinterParameterized4(windowId, FONT_NARROW, x, y, letterSpacing, lineSpacing, sTextColors[colorTableId], speed, src);
}

static void DrawTossNumberWindow(u8 windowId)
{
    DrawStdFrameWithCustomTileAndPalette(windowId, 0, 1, 0xE);
    ScheduleBgCopyTilemapToVram(1);
}

// Unused
static u8 GetMenuActionWindowId(u8 windowArrayId)
{
    return gPyramidBagMenu->windowIds[windowArrayId];
}

static u8 OpenMenuActionWindowById(u8 windowArrayId)
{
    u8 *windowId = &gPyramidBagMenu->windowIds[windowArrayId];
    if (*windowId == WINDOW_NONE)
    {
        *windowId = AddWindow(&sWindowTemplates_MenuActions[windowArrayId]);
        DrawStdFrameWithCustomTileAndPalette(*windowId, FALSE, 1, 0xE);
        ScheduleBgCopyTilemapToVram(1);
    }
    return *windowId;
}

static void CloseMenuActionWindowById(u8 windowArrayId)
{
    u8 *windowId = &gPyramidBagMenu->windowIds[windowArrayId];
    if (*windowId != WINDOW_NONE)
    {
        ClearStdWindowAndFrameToTransparent(*windowId, FALSE);
        ClearWindowTilemap(*windowId);
        RemoveWindow(*windowId);
        ScheduleBgCopyTilemapToVram(1);
        *windowId = WINDOW_NONE;
    }
}

static void CreatePyramidBagYesNo(u8 taskId, const struct YesNoFuncTable *yesNoTable)
{
    CreateYesNoMenuWithCallbacks(taskId, &sWindowTemplates_MenuActions[MENU_WIN_YESNO], 1, 0, 2, 1, 0xE, yesNoTable);
}

void DisplayItemMessageInBattlePyramid(u8 taskId, const u8 *str, void (*callback)(u8 taskId))
{
    FillWindowPixelBuffer(WIN_MSG, PIXEL_FILL(1));
    DisplayMessageAndContinueTask(taskId, WIN_MSG, 0xA, 0xD, FONT_NORMAL, GetPlayerTextSpeedDelay(), str, callback);
    ScheduleBgCopyTilemapToVram(1);
}

static void CloseBattlePyramidBagTextWindow(void)
{
    ClearDialogWindowAndFrameToTransparent(WIN_MSG, FALSE);
    // This ClearWindowTilemap call is redundant, since ClearDialogWindowAndFrameToTransparent already calls it.
    ClearWindowTilemap(WIN_MSG);
    ScheduleBgCopyTilemapToVram(1);
}

static void FreeItemIconSprite(u8 spriteArrId)
{
    u8 *spriteId = &gPyramidBagMenu->spriteIds[spriteArrId];
    if (*spriteId != SPRITE_NONE)
    {
        // spriteArrId is PBAG_SPRITE_ITEM_ICON / PBAG_SPRITE_ITEM_ICON_ALT here (1-2)
        // so tag will be TAG_ITEM_ICON / TAG_ITEM_ICON_ALT
        FreeSpriteTilesByTag(TAG_ITEM_ICON - 1 + spriteArrId);
        FreeSpritePaletteByTag(TAG_ITEM_ICON - 1 + spriteArrId);
        FreeSpriteOamMatrix(&gSprites[*spriteId]);
        DestroySprite(&gSprites[*spriteId]);
        *spriteId = SPRITE_NONE;
    }
}

static void LoadPyramidBagPalette(void)
{
    struct SpritePalette spritePalette;
    u16 *palPtr = Alloc(0x40);

    LZDecompressWram(gBattlePyramidBag_Pal, palPtr);
    spritePalette.data = palPtr + (gSaveBlock2Ptr->frontier.lvlMode * 16);
    spritePalette.tag = TAG_PYRAMID_BAG;
    LoadSpritePalette(&spritePalette);
    Free(palPtr);
}

static void CreatePyramidBagSprite(void)
{
    u8 *spriteId = &gPyramidBagMenu->spriteIds[PBAG_SPRITE_BAG];
    *spriteId = CreateSprite(&sSpriteTemplate_PyramidBag, 68, 56, 0);
}

static void ShakePyramidBag(void)
{
    struct Sprite *sprite = &gSprites[gPyramidBagMenu->spriteIds[PBAG_SPRITE_BAG]];
    if (sprite->affineAnimEnded)
    {
        StartSpriteAffineAnim(sprite, ANIM_BAG_SHAKE);
        sprite->callback = SpriteCB_BagWaitForShake;
    }
}

static void SpriteCB_BagWaitForShake(struct Sprite *sprite)
{
    if (sprite->affineAnimEnded)
    {
        StartSpriteAffineAnim(sprite, ANIM_BAG_STILL);
        sprite->callback = SpriteCallbackDummy;
    }
}

static void ShowItemIcon(u16 itemId, bool8 isAlt)
{
    u8 itemSpriteId;
    u8 *spriteId = &gPyramidBagMenu->spriteIds[isAlt + PBAG_SPRITE_ITEM_ICON];
    if (*spriteId == SPRITE_NONE)
    {
        FreeSpriteTilesByTag(TAG_ITEM_ICON + isAlt);
        FreeSpritePaletteByTag(TAG_ITEM_ICON + isAlt);
        itemSpriteId = AddItemIconSprite(TAG_ITEM_ICON + isAlt, TAG_ITEM_ICON + isAlt, itemId);
        if (itemSpriteId != MAX_SPRITES)
        {
            *spriteId = itemSpriteId;
            gSprites[itemSpriteId].x2 = 24;
            gSprites[itemSpriteId].y2 = 88;
        }
    }
}

static void FreeItemIconSpriteByAltId(bool8 isAlt)
{
    FreeItemIconSprite(isAlt + PBAG_SPRITE_ITEM_ICON);
}

static void CreateSwapLine(void)
{
    CreateSwapLineSprites(&gPyramidBagMenu->spriteIds[PBAG_SPRITE_SWAP_LINE_START], NUM_SWAP_LINE_SPRITES);
}

static void SetSwapLineInvisibility(bool8 invisible)
{
    SetSwapLineSpritesInvisibility(&gPyramidBagMenu->spriteIds[PBAG_SPRITE_SWAP_LINE_START], NUM_SWAP_LINE_SPRITES, invisible);
}

static void UpdateSwapLinePos(u8 y)
{
    UpdateSwapLineSpritesPos(&gPyramidBagMenu->spriteIds[PBAG_SPRITE_SWAP_LINE_START], NUM_SWAP_LINE_SPRITES | SWAP_LINE_HAS_MARGIN, 120, (y + 1) * 16);
}
