#include "global.h"
#include "constants/songs.h"
#include "bg.h"
#include "decoration.h"
#include "event_scripts.h"
#include "event_object_movement.h"
#include "field_screen_effect.h"
#include "field_weather.h"
#include "international_string_util.h"
#include "item.h"
#include "item_icon.h"
#include "item_menu.h"
#include "constants/items.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 "player_pc.h"
#include "script.h"
#include "sound.h"
#include "sprite.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "window.h"
#include "menu_specialized.h"

// Top level PC menu options
enum {
    MENU_ITEMSTORAGE,
    MENU_MAILBOX,
    MENU_DECORATION,
    MENU_TURNOFF
};

// Item storage menu options
enum {
    MENU_WITHDRAW,
    MENU_DEPOSIT,
    MENU_TOSS,
    MENU_EXIT
};

// Windows for the main menus (top level menu, and item storage menu)
enum {
    WIN_MAIN_MENU,
    WIN_MAIN_MENU_BEDROOM,
    WIN_ITEM_STORAGE_MENU,
};

// Windows for item storage (while viewing the PC's item inventory)
enum {
    ITEMPC_WIN_LIST,
    ITEMPC_WIN_MESSAGE,
    ITEMPC_WIN_ICON,
    ITEMPC_WIN_TITLE,
    ITEMPC_WIN_QUANTITY,
    ITEMPC_WIN_YESNO,
    ITEMPC_WIN_COUNT
};
 // When showing the main list, the first window to this window are drawn
#define ITEMPC_WIN_LIST_END ITEMPC_WIN_TITLE

// Message IDs for Item Storage
enum {
    MSG_SWITCH_WHICH_ITEM = 0xFFF7,
    MSG_OKAY_TO_THROW_AWAY,
    MSG_TOO_IMPORTANT,
    MSG_NO_MORE_ROOM,
    MSG_THREW_AWAY_ITEM,
    MSG_HOW_MANY_TO_TOSS,
    MSG_WITHDREW_ITEM,
    MSG_HOW_MANY_TO_WITHDRAW,
    MSG_GO_BACK_TO_PREV
};

#define TAG_ITEM_ICON    5110
#define TAG_SCROLL_ARROW 5112

// Item list ID for toSwapPos to indicate an item is not currently being swapped
#define NOT_SWAPPING 0xFF

#define SWAP_LINE_LENGTH 7

struct ItemStorageMenu
{
    struct ListMenuItem listItems[PC_ITEMS_COUNT + 1];
    u8 itemNames[PC_ITEMS_COUNT + 1][ITEM_NAME_LENGTH + 10];
    u8 windowIds[ITEMPC_WIN_COUNT];
    u8 toSwapPos;
    u8 spriteId;
    u8 swapLineSpriteIds[SWAP_LINE_LENGTH];
};

static void InitPlayerPCMenu(u8 taskId);
static void PlayerPCProcessMenuInput(u8 taskId);
static void InitItemStorageMenu(u8 taskId, u8 var);

static u8 GetMailboxMailCount(void);
static void Mailbox_CompactMailList(void);
static void Mailbox_DrawMailboxMenu(u8 taskId);
static void Mailbox_ProcessInput(u8 taskId);
static void Mailbox_PrintWhatToDoWithPlayerMailText(u8 taskId);
static void Mailbox_ReturnToPlayerPC(u8);
static void Mailbox_PrintMailOptions(u8 taskId);
static void Mailbox_MailOptionsProcessInput(u8 taskId);

static void PlayerPC_ItemStorage(u8 taskId);
static void PlayerPC_Mailbox(u8 taskId);
static void PlayerPC_Decoration(u8 var);
static void PlayerPC_TurnOff(u8 taskId);

static void Mailbox_DoMailMoveToBag(u8 taskId);
static void Mailbox_DoMailRead(u8 taskId);
static void Mailbox_MoveToBag(u8 taskId);
static void Mailbox_Give(u8 taskId);
static void Mailbox_Cancel(u8 taskId);

static void Mailbox_CancelMoveToBag(u8 taskId);
static void Mailbox_HandleConfirmMoveToBag(u8 taskId);
static void Mailbox_AskConfirmMoveToBag(u8 taskId);
static void Mailbox_DoGiveMailPokeMenu(u8 taskId);
static void Mailbox_NoPokemonForMail(u8 taskId);

static void Mailbox_FadeAndReadMail(u8 taskId);
static void Mailbox_ReturnToFieldFromReadMail(void);
static void Mailbox_ReshowAfterMail(void);
static void Mailbox_HandleReturnToProcessInput(u8 taskId);
static void Mailbox_UpdateMailListAfterDeposit(void);

static void ItemStorage_Withdraw(u8 taskId);
static void ItemStorage_Deposit(u8 taskId);
static void ItemStorage_Toss(u8 taskId);
static void ItemStorage_Exit(u8 taskId);
static void ItemStorage_TossItemYes(u8 taskId);
static void ItemStorage_TossItemNo(u8 taskId);

static void ItemStorageMenuPrint(const u8 *);
static void ItemStorageMenuProcessInput(u8 taskId);
static void SetPlayerPCListCount(u8);
static void ItemStorage_HandleReturnToProcessInput(u8 taskId);

static void ItemStorage_Enter(u8 taskId, bool8 toss);
static void ItemStorage_CreateListMenu(u8 taskId);
static void ItemStorage_ProcessInput(u8 taskId);
static void Task_ItemStorage_Deposit(u8 taskId);
static void ItemStorage_ReshowAfterBagMenu(void);
static void ItemStorage_DoItemWithdraw(u8 taskId);
static void ItemStorage_DoItemToss(u8 taskid);
static void ItemStorage_HandleQuantityRolling(u8 taskid);
static void ItemStorage_ExitItemList(u8 taskId);
static void ItemStorage_StartItemSwap(u8 taskId);
static void ItemStorage_DoItemAction(u8 taskId);
static void ItemStorage_FinishItemSwap(u8 taskId, bool8 a);
static void ItemStorage_HandleRemoveItem(u8 taskId);
static void ItemStorage_HandleErrorMessageInput(u8 taskId);
static void ItemStorage_ReturnToListInput(u8 taskId);

static const u8* ItemStorage_GetMessage(u16);
static void CopyItemName_PlayerPC(u8 *string, u16 itemId);

static void ItemStorage_Init(void);
static void ItemStorage_DrawSwapArrow(u8 y, u8, u8 speed);
static void ItemStorage_RemoveWindow(u8);
static void ItemStorage_UpdateSwapLinePos(u8);
static void ItemStorage_ProcessItemSwapInput(u8 taskId);
static void ItemStorage_EraseItemIcon(void);
static void ItemStorage_DrawItemIcon(u16 itemId);
static void ItemStorage_PrintDescription(s32 id);
static void ItemStorage_EraseMainMenu(u8 taskId);
static void ItemStorage_MoveCursor(s32 id, bool8 b, struct ListMenu * thisMenu);
static void ItemStorage_PrintMenuItem(u8 windowId, u32 id, u8 yOffset);

static EWRAM_DATA const u8 *sTopMenuOptionOrder = NULL;
static EWRAM_DATA u8 sTopMenuNumOptions = 0;
EWRAM_DATA struct PlayerPCItemPageStruct gPlayerPCItemPageInfo = {};
static EWRAM_DATA struct ItemStorageMenu *sItemStorageMenu = NULL;

static const u8 *const sItemStorage_OptionDescriptions[] =
{
    [MENU_WITHDRAW] = gText_TakeOutItemsFromPC,
    [MENU_DEPOSIT]  = gText_StoreItemsInPC,
    [MENU_TOSS]     = gText_ThrowAwayItemsInPC,
    [MENU_EXIT]     = gText_GoBackPrevMenu,
};

static const struct MenuAction sPlayerPCMenuActions[] =
{
    [MENU_ITEMSTORAGE] = { gText_ItemStorage, PlayerPC_ItemStorage },
    [MENU_MAILBOX]     = { gText_Mailbox,     PlayerPC_Mailbox },
    [MENU_DECORATION]  = { gText_Decoration,  PlayerPC_Decoration },
    [MENU_TURNOFF]     = { gText_TurnOff,     PlayerPC_TurnOff }
};

static const u8 sBedroomPC_OptionOrder[] =
{
    MENU_ITEMSTORAGE,
    MENU_MAILBOX,
    MENU_DECORATION,
    MENU_TURNOFF
};
#define NUM_BEDROOM_PC_OPTIONS ARRAY_COUNT(sBedroomPC_OptionOrder)

static const u8 sPlayerPC_OptionOrder[] =
{
    MENU_ITEMSTORAGE,
    MENU_MAILBOX,
    MENU_TURNOFF
};
#define NUM_PLAYER_PC_OPTIONS ARRAY_COUNT(sPlayerPC_OptionOrder)

static const struct MenuAction sItemStorage_MenuActions[] =
{
    [MENU_WITHDRAW] = { gText_WithdrawItem, ItemStorage_Withdraw },
    [MENU_DEPOSIT]  = { gText_DepositItem,  ItemStorage_Deposit },
    [MENU_TOSS]     = { gText_TossItem,     ItemStorage_Toss },
    [MENU_EXIT]     = { gText_Cancel,       ItemStorage_Exit }
};

static const struct ItemSlot sNewGamePCItems[] =
{
    { ITEM_POTION, 1 },
    { ITEM_NONE, 0 }
};

const struct MenuAction gMailboxMailOptions[] =
{
    { gText_Read,      Mailbox_DoMailRead },
    { gText_MoveToBag, Mailbox_MoveToBag },
    { gText_Give2,     Mailbox_Give },
    { gText_Cancel2,   Mailbox_Cancel }
};

static const struct WindowTemplate sWindowTemplates_MainMenus[] =
{
    [WIN_MAIN_MENU] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 1,
        .width = 9,
        .height = 6,
        .paletteNum = 15,
        .baseBlock = 1
    },
    [WIN_MAIN_MENU_BEDROOM] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 1,
        .width = 9,
        .height = 8,
        .paletteNum = 15,
        .baseBlock = 1
    },
    [WIN_ITEM_STORAGE_MENU] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 1,
        .width = 10,
        .height = 8,
        .paletteNum = 15,
        .baseBlock = 1
    }
};

static const struct YesNoFuncTable ItemTossYesNoFuncs =
{
    ItemStorage_TossItemYes,
    ItemStorage_TossItemNo
};

static const struct ListMenuTemplate sListMenuTemplate_ItemStorage =
{
    .items = NULL,
    .moveCursorFunc = ItemStorage_MoveCursor,
    .itemPrintFunc = ItemStorage_PrintMenuItem,
    .totalItems = 0,
    .maxShowed = 0,
    .windowId = 0,
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 9,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = FALSE,
    .itemVerticalPadding = 0,
    .scrollMultiple = FALSE,
    .fontId = FONT_NARROW
};

static const struct WindowTemplate sWindowTemplates_ItemStorage[ITEMPC_WIN_COUNT] =
{
    [ITEMPC_WIN_LIST] = {
        .bg = 0,
        .tilemapLeft = 16,
        .tilemapTop = 1,
        .width = 13,
        .height = 18,
        .paletteNum = 15,
        .baseBlock = 0x0001
    },
    [ITEMPC_WIN_MESSAGE] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 13,
        .width = 13,
        .height = 6,
        .paletteNum = 15,
        .baseBlock = 0x00EB
    },
    [ITEMPC_WIN_ICON] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 8,
        .width = 3,
        .height = 3,
        .paletteNum = 15,
        .baseBlock = 0x0153
    },
    [ITEMPC_WIN_TITLE] = {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 1,
        .width = 13,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 0x0139
    },
    [ITEMPC_WIN_QUANTITY] = {
        .bg = 0,
        .tilemapLeft = 8,
        .tilemapTop = 9,
        .width = 6,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 0x015C
    },
    [ITEMPC_WIN_YESNO] = {
        .bg = 0,
        .tilemapLeft = 9,
        .tilemapTop = 7,
        .width = 5,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 0x0168
    }
};

static const u8 sSwapArrowTextColors[] = {TEXT_COLOR_WHITE, TEXT_COLOR_LIGHT_GRAY, TEXT_COLOR_DARK_GRAY};

// Macro below is likely a fakematch, equivalent to sNewGamePCItems[i].quantity
#define GET_QUANTITY(i) ((u16)((u16 *)sNewGamePCItems + 1)[i * 2])
void NewGameInitPCItems(void)
{
    u8 i = 0;
    ClearItemSlots(gSaveBlock1Ptr->pcItems, PC_ITEMS_COUNT);
    for(; sNewGamePCItems[i].itemId != ITEM_NONE && GET_QUANTITY(i) &&
        AddPCItem(sNewGamePCItems[i].itemId, GET_QUANTITY(i)) == TRUE; i++);
}
#undef GET_QUANTITY

void BedroomPC(void)
{
    sTopMenuOptionOrder = sBedroomPC_OptionOrder;
    sTopMenuNumOptions = NUM_BEDROOM_PC_OPTIONS;
    DisplayItemMessageOnField(CreateTask(TaskDummy, 0), gText_WhatWouldYouLike, InitPlayerPCMenu);
}

void PlayerPC(void)
{
    sTopMenuOptionOrder = sPlayerPC_OptionOrder;
    sTopMenuNumOptions = NUM_PLAYER_PC_OPTIONS;
    DisplayItemMessageOnField(CreateTask(TaskDummy, 0), gText_WhatWouldYouLike, InitPlayerPCMenu);
}

#define tUsedSlots  data[1]
#define tQuantity   data[2]
#define tInTossMenu data[3]
#define tWindowId   data[4]
#define tListTaskId data[5]

static void InitPlayerPCMenu(u8 taskId)
{
    u16 *data;
    struct WindowTemplate windowTemplate;
    data = gTasks[taskId].data;

    if (sTopMenuNumOptions == NUM_PLAYER_PC_OPTIONS)
        windowTemplate = sWindowTemplates_MainMenus[WIN_MAIN_MENU];
    else // Bedroom PC
        windowTemplate = sWindowTemplates_MainMenus[WIN_MAIN_MENU_BEDROOM];

    windowTemplate.width = GetMaxWidthInSubsetOfMenuTable(sPlayerPCMenuActions, sTopMenuOptionOrder, sTopMenuNumOptions);
    tWindowId = AddWindow(&windowTemplate);
    SetStandardWindowBorderStyle(tWindowId, 0);
    PrintMenuActionTextsInUpperLeftCorner(tWindowId, sTopMenuNumOptions, sPlayerPCMenuActions, sTopMenuOptionOrder);
    InitMenuInUpperLeftCornerNormal(tWindowId, sTopMenuNumOptions, 0);
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].func = PlayerPCProcessMenuInput;
}

static void PlayerPCProcessMenuInput(u8 taskId)
{
    u16 *data;
    s8 inputOptionId;

    data = gTasks[taskId].data;
    if (sTopMenuNumOptions > 3)
        inputOptionId = Menu_ProcessInput();
    else
        inputOptionId = Menu_ProcessInputNoWrap();

    switch (inputOptionId)
    {
    case MENU_NOTHING_CHOSEN:
        break;
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
        ClearStdWindowAndFrameToTransparent(tWindowId, FALSE);
        ClearWindowTilemap(tWindowId);
        RemoveWindow(tWindowId);
        ScheduleBgCopyTilemapToVram(0);
        gTasks[taskId].func = PlayerPC_TurnOff;
        break;
    default:
        ClearStdWindowAndFrameToTransparent(tWindowId, FALSE);
        ClearWindowTilemap(tWindowId);
        RemoveWindow(tWindowId);
        ScheduleBgCopyTilemapToVram(0);
        gTasks[taskId].func = sPlayerPCMenuActions[sTopMenuOptionOrder[inputOptionId]].func.void_u8;
        break;
    }
}

void ReshowPlayerPC(u8 var)
{
    DisplayItemMessageOnField(var, gText_WhatWouldYouLike, InitPlayerPCMenu);
}

static void PlayerPC_ItemStorage(u8 taskId)
{
    InitItemStorageMenu(taskId, MENU_WITHDRAW);
    gTasks[taskId].func = ItemStorageMenuProcessInput;
}

static void PlayerPC_Mailbox(u8 taskId)
{
    gPlayerPCItemPageInfo.count = GetMailboxMailCount();

    if (gPlayerPCItemPageInfo.count == 0)
    {
        // Mailbox cannot be opened if no mail is in PC
        DisplayItemMessageOnField(taskId, gText_NoMailHere, ReshowPlayerPC);
    }
    else
    {
        gPlayerPCItemPageInfo.cursorPos = 0;
        gPlayerPCItemPageInfo.itemsAbove = 0;
        gPlayerPCItemPageInfo.scrollIndicatorTaskId = TASK_NONE;
        Mailbox_CompactMailList();
        SetPlayerPCListCount(taskId);
        if (MailboxMenu_Alloc(gPlayerPCItemPageInfo.count) == TRUE)
        {
            ClearDialogWindowAndFrame(0, 0);
            Mailbox_DrawMailboxMenu(taskId);
            gTasks[taskId].func = Mailbox_ProcessInput;
        }
        else
        {
            // Alloc failed, exit Mailbox
            DisplayItemMessageOnField(taskId, gText_NoMailHere, ReshowPlayerPC);
        }
    }
}

static void PlayerPC_Decoration(u8 taskId)
{
    DoPlayerRoomDecorationMenu(taskId);
}

static void PlayerPC_TurnOff(u8 taskId)
{
    if (sTopMenuNumOptions == NUM_BEDROOM_PC_OPTIONS) // Flimsy way to determine if Bedroom PC is in use
    {
        if (gSaveBlock2Ptr->playerGender == MALE)
            ScriptContext1_SetupScript(LittlerootTown_BrendansHouse_2F_EventScript_TurnOffPlayerPC);
        else
            ScriptContext1_SetupScript(LittlerootTown_MaysHouse_2F_EventScript_TurnOffPlayerPC);
    }
    else
    {
        EnableBothScriptContexts();
    }
    DestroyTask(taskId);
}

static void InitItemStorageMenu(u8 taskId, u8 var)
{
    u16 *data;
    struct WindowTemplate windowTemplate;

    data = gTasks[taskId].data;
    windowTemplate = sWindowTemplates_MainMenus[WIN_ITEM_STORAGE_MENU];
    windowTemplate.width = GetMaxWidthInMenuTable(sItemStorage_MenuActions, ARRAY_COUNT(sItemStorage_MenuActions));
    tWindowId = AddWindow(&windowTemplate);
    SetStandardWindowBorderStyle(tWindowId, 0);
    PrintMenuTable(tWindowId, ARRAY_COUNT(sItemStorage_MenuActions), sItemStorage_MenuActions);
    InitMenuInUpperLeftCornerNormal(tWindowId, 4, var);
    ScheduleBgCopyTilemapToVram(0);
    ItemStorageMenuPrint(sItemStorage_OptionDescriptions[var]);
}

static void ItemStorageMenuPrint(const u8 *textPtr)
{
    DrawDialogueFrame(0, 0);
    AddTextPrinterParameterized(0, FONT_NORMAL, textPtr, 0, 1, 0, 0);
}

static void ItemStorageMenuProcessInput(u8 taskId)
{
    s8 oldPos, newPos;
    s8 inputOptionId;

    oldPos = Menu_GetCursorPos();
    inputOptionId = Menu_ProcessInput();
    newPos = Menu_GetCursorPos();
    switch (inputOptionId)
    {
    case MENU_NOTHING_CHOSEN:
        if (oldPos != newPos)
            ItemStorageMenuPrint(sItemStorage_OptionDescriptions[newPos]);
        break;
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
        ItemStorage_Exit(taskId);
        break;
    default:
        PlaySE(SE_SELECT);
        sItemStorage_MenuActions[inputOptionId].func.void_u8(taskId);
        break;
    }
}

static void ItemStorage_Deposit(u8 taskId)
{
    gTasks[taskId].func = Task_ItemStorage_Deposit;
    FadeScreen(FADE_TO_BLACK, 0);
}

static void Task_ItemStorage_Deposit(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        CleanupOverworldWindowsAndTilemaps();
        CB2_GoToItemDepositMenu();
        DestroyTask(taskId);
    }
}

void CB2_PlayerPCExitBagMenu(void)
{
    gFieldCallback = ItemStorage_ReshowAfterBagMenu;
    SetMainCallback2(CB2_ReturnToField);
}

static void ItemStorage_ReshowAfterBagMenu(void)
{
    LoadMessageBoxAndBorderGfx();
    DrawDialogueFrame(0, 1);
    InitItemStorageMenu(CreateTask(ItemStorage_HandleReturnToProcessInput, 0), 1);
    FadeInFromBlack();
}

static void ItemStorage_HandleReturnToProcessInput(u8 taskId)
{
    if (IsWeatherNotFadingIn() == TRUE)
        gTasks[taskId].func = ItemStorageMenuProcessInput;
}

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

    tUsedSlots = CountUsedPCItemSlots();
    if (tUsedSlots != 0)
    {
        ItemStorage_Enter(taskId, FALSE);
    }
    else
    {
        // Can't withdraw, no items in PC
        ItemStorage_EraseMainMenu(taskId);
        DisplayItemMessageOnField(taskId, gText_NoItems, PlayerPC_ItemStorage);
    }

}

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

    tUsedSlots = CountUsedPCItemSlots();
    if (tUsedSlots != 0)
    {
        ItemStorage_Enter(taskId, TRUE);
    }
    else
    {
        // Can't toss, no items in PC
        ItemStorage_EraseMainMenu(taskId);
        DisplayItemMessageOnField(taskId, gText_NoItems, PlayerPC_ItemStorage);
    }
}

static void ItemStorage_Enter(u8 taskId, bool8 toss)
{
    u16 *data = gTasks[taskId].data;

    tInTossMenu = toss;
    ItemStorage_EraseMainMenu(taskId);
    gPlayerPCItemPageInfo.cursorPos = 0;
    gPlayerPCItemPageInfo.itemsAbove = 0;
    gPlayerPCItemPageInfo.scrollIndicatorTaskId = TASK_NONE;
    SetPlayerPCListCount(taskId);
    ItemStorage_Init();
    FreeAndReserveObjectSpritePalettes();
    LoadListMenuSwapLineGfx();
    CreateSwapLineSprites(sItemStorageMenu->swapLineSpriteIds, SWAP_LINE_LENGTH);
    ClearDialogWindowAndFrame(0,0);
    gTasks[taskId].func = ItemStorage_CreateListMenu;
}

static void ItemStorage_Exit(u8 taskId)
{
    ItemStorage_EraseMainMenu(taskId);
    ReshowPlayerPC(taskId);
}

// Used by Item Storage and the Mailbox
static void SetPlayerPCListCount(u8 taskId)
{
    if (gPlayerPCItemPageInfo.count > 7)
        gPlayerPCItemPageInfo.pageItems = 8;
    else
        gPlayerPCItemPageInfo.pageItems = gPlayerPCItemPageInfo.count + 1;
}

static void ItemStorage_EraseMainMenu(u8 taskId)
{
    u16 *data = gTasks[taskId].data;
    ClearStdWindowAndFrameToTransparent(tWindowId, FALSE);
    ClearWindowTilemap(tWindowId);
    RemoveWindow(tWindowId);
    ScheduleBgCopyTilemapToVram(0);
}

static u8 GetMailboxMailCount(void)
{
    u8 mailInPC, i;

    // Count mail in PC (by first skipping over mail in party)
    for (mailInPC = 0, i = PARTY_SIZE; i < MAIL_COUNT; i++)
        if (gSaveBlock1Ptr->mail[i].itemId != ITEM_NONE)
            mailInPC++;

    return mailInPC;
}

static void Mailbox_CompactMailList(void)
{
    struct Mail temp;
    u8 i, j;

    for (i = PARTY_SIZE; i < MAIL_COUNT - 1; i++)
    {
        for (j = i + 1; j < MAIL_COUNT; j++)
        {
            if (gSaveBlock1Ptr->mail[i].itemId == ITEM_NONE)
                SWAP(gSaveBlock1Ptr->mail[i], gSaveBlock1Ptr->mail[j], temp);
        }
    }
}

static void Mailbox_DrawMailboxMenu(u8 taskId)
{
    u8 windowId = MailboxMenu_AddWindow(MAILBOXWIN_TITLE);
    MailboxMenu_AddWindow(MAILBOXWIN_LIST);
    AddTextPrinterParameterized(windowId, FONT_NORMAL, gText_Mailbox, GetStringCenterAlignXOffset(FONT_NORMAL, gText_Mailbox, 0x40), 1, 0, NULL);
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].tListTaskId = MailboxMenu_CreateList(&gPlayerPCItemPageInfo);
    MailboxMenu_AddScrollArrows(&gPlayerPCItemPageInfo);
}

static void Mailbox_ProcessInput(u8 taskId)
{
    u16 *data = gTasks[taskId].data;

    if (!gPaletteFade.active)
    {
        s32 inputOptionId = ListMenu_ProcessInput(tListTaskId);
        ListMenuGetScrollAndRow(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);

        switch (inputOptionId)
        {
        case LIST_NOTHING_CHOSEN:
            break;
        case LIST_CANCEL:
            PlaySE(SE_SELECT);
            RemoveScrollIndicatorArrowPair(gPlayerPCItemPageInfo.scrollIndicatorTaskId);
            Mailbox_ReturnToPlayerPC(taskId);
            break;
        default:
            // Selected mail, ask what to do with it
            PlaySE(SE_SELECT);
            MailboxMenu_RemoveWindow(MAILBOXWIN_TITLE);
            MailboxMenu_RemoveWindow(MAILBOXWIN_LIST);
            DestroyListMenuTask(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
            ScheduleBgCopyTilemapToVram(0);
            RemoveScrollIndicatorArrowPair(gPlayerPCItemPageInfo.scrollIndicatorTaskId);
            gTasks[taskId].func = Mailbox_PrintWhatToDoWithPlayerMailText;
            break;
        }
    }
}

static void Mailbox_PrintWhatToDoWithPlayerMailText(u8 taskId)
{
    StringCopy(gStringVar1, gSaveBlock1Ptr->mail[gPlayerPCItemPageInfo.itemsAbove + PARTY_SIZE + gPlayerPCItemPageInfo.cursorPos].playerName);
    ConvertInternationalPlayerNameStripChar(gStringVar1, CHAR_SPACE);
    StringExpandPlaceholders(gStringVar4, gText_WhatToDoWithVar1sMail);
    DisplayItemMessageOnField(taskId, gStringVar4, Mailbox_PrintMailOptions);
}

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

    MailboxMenu_RemoveWindow(MAILBOXWIN_TITLE);
    MailboxMenu_RemoveWindow(MAILBOXWIN_LIST);
    DestroyListMenuTask(tListTaskId, NULL, NULL);
    ScheduleBgCopyTilemapToVram(0);
    MailboxMenu_Free();
    ReshowPlayerPC(taskId);
}

static void Mailbox_PrintMailOptions(u8 taskId)
{
    u8 windowId = MailboxMenu_AddWindow(MAILBOXWIN_OPTIONS);
    PrintMenuTable(windowId, ARRAY_COUNT(gMailboxMailOptions), gMailboxMailOptions);
    InitMenuInUpperLeftCornerNormal(windowId, 4, 0);
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].func = Mailbox_MailOptionsProcessInput;
}

static void Mailbox_MailOptionsProcessInput(u8 taskId)
{
    s8 inputOptionId = ProcessMenuInput_other();

    switch (inputOptionId)
    {
    case MENU_NOTHING_CHOSEN:
        break;
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
        Mailbox_Cancel(taskId);
        break;
    default:
        PlaySE(SE_SELECT);
        gMailboxMailOptions[inputOptionId].func.void_u8(taskId);
        break;
    }
}

static void Mailbox_DoMailRead(u8 taskId)
{
    FadeScreen(FADE_TO_BLACK, 0);
    gTasks[taskId].func = Mailbox_FadeAndReadMail;
}

static void Mailbox_FadeAndReadMail(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        MailboxMenu_Free();
        CleanupOverworldWindowsAndTilemaps();
        ReadMail(&gSaveBlock1Ptr->mail[gPlayerPCItemPageInfo.itemsAbove + PARTY_SIZE + gPlayerPCItemPageInfo.cursorPos], Mailbox_ReturnToFieldFromReadMail, TRUE);
        DestroyTask(taskId);
    }
}

static void Mailbox_ReturnToFieldFromReadMail(void)
{
    gFieldCallback = Mailbox_ReshowAfterMail;
    SetMainCallback2(CB2_ReturnToField);
}

static void Mailbox_ReshowAfterMail(void)
{
    u8 taskId;

    LoadMessageBoxAndBorderGfx();
    taskId = CreateTask(Mailbox_HandleReturnToProcessInput, 0);
    if (MailboxMenu_Alloc(gPlayerPCItemPageInfo.count) == TRUE)
        Mailbox_DrawMailboxMenu(taskId);
    else
        DestroyTask(taskId);
    FadeInFromBlack();
}

static void Mailbox_HandleReturnToProcessInput(u8 taskId)
{
    if (IsWeatherNotFadingIn() == TRUE)
        gTasks[taskId].func = Mailbox_ProcessInput;
}

static void Mailbox_MoveToBag(u8 taskId)
{
    DisplayItemMessageOnField(taskId, gText_MessageWillBeLost, Mailbox_AskConfirmMoveToBag);
}

static void Mailbox_AskConfirmMoveToBag(u8 taskId)
{
    DisplayYesNoMenuDefaultYes();
    gTasks[taskId].func = Mailbox_HandleConfirmMoveToBag;
}

static void Mailbox_HandleConfirmMoveToBag(u8 taskId)
{
    switch (Menu_ProcessInputNoWrapClearOnChoose())
    {
    case 0: // Yes
        Mailbox_DoMailMoveToBag(taskId);
        break;
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
    case 1: // No
        Mailbox_CancelMoveToBag(taskId);
        break;
    case MENU_NOTHING_CHOSEN:
    default:
        break;
    }
}

static void Mailbox_DoMailMoveToBag(u8 taskId)
{
    struct Mail *mail = &gSaveBlock1Ptr->mail[gPlayerPCItemPageInfo.itemsAbove + PARTY_SIZE + gPlayerPCItemPageInfo.cursorPos];
    if (!AddBagItem(mail->itemId, 1))
    {
        DisplayItemMessageOnField(taskId, gText_BagIsFull, Mailbox_Cancel);
    }
    else
    {
        DisplayItemMessageOnField(taskId, gText_MailToBagMessageErased, Mailbox_Cancel);
        ClearMail(mail);
        Mailbox_CompactMailList();
        gPlayerPCItemPageInfo.count--;
        if (gPlayerPCItemPageInfo.count < (gPlayerPCItemPageInfo.pageItems + gPlayerPCItemPageInfo.itemsAbove) && gPlayerPCItemPageInfo.itemsAbove != 0)
            gPlayerPCItemPageInfo.itemsAbove--;
        SetPlayerPCListCount(taskId);
    }
}

static void Mailbox_CancelMoveToBag(u8 taskId)
{
    Mailbox_Cancel(taskId);
}

static void Mailbox_Give(u8 taskId)
{
    if (CalculatePlayerPartyCount() == 0)
        Mailbox_NoPokemonForMail(taskId);
    else
    {
        FadeScreen(FADE_TO_BLACK, 0);
        gTasks[taskId].func = Mailbox_DoGiveMailPokeMenu;
    }
}

static void Mailbox_DoGiveMailPokeMenu(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        MailboxMenu_Free();
        CleanupOverworldWindowsAndTilemaps();
        ChooseMonToGiveMailFromMailbox();
        DestroyTask(taskId);
    }
}

void Mailbox_ReturnToMailListAfterDeposit(void)
{
    gFieldCallback = Mailbox_UpdateMailListAfterDeposit;
    SetMainCallback2(CB2_ReturnToField);
}

static void Mailbox_UpdateMailListAfterDeposit(void)
{
    u8 taskId;
    u8 prevCount;
    taskId = CreateTask(Mailbox_HandleReturnToProcessInput, 0);
    prevCount = gPlayerPCItemPageInfo.count;
    gPlayerPCItemPageInfo.count = GetMailboxMailCount();
    Mailbox_CompactMailList();
    if (prevCount != gPlayerPCItemPageInfo.count && (gPlayerPCItemPageInfo.count < (gPlayerPCItemPageInfo.pageItems + gPlayerPCItemPageInfo.itemsAbove))
       && gPlayerPCItemPageInfo.itemsAbove != 0)
        gPlayerPCItemPageInfo.itemsAbove--;
    SetPlayerPCListCount(taskId);
    LoadMessageBoxAndBorderGfx();
    if (MailboxMenu_Alloc(gPlayerPCItemPageInfo.count) == TRUE)
        Mailbox_DrawMailboxMenu(taskId);
    else
        DestroyTask(taskId);
    FadeInFromBlack();
}

static void Mailbox_NoPokemonForMail(u8 taskId)
{
    DisplayItemMessageOnField(taskId, gText_NoPokemon, Mailbox_Cancel);
}

static void Mailbox_Cancel(u8 taskId)
{
    MailboxMenu_RemoveWindow(MAILBOXWIN_OPTIONS);
    ClearDialogWindowAndFrame(0, 0);
    Mailbox_DrawMailboxMenu(taskId);
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].func = Mailbox_ProcessInput;
}

static void ItemStorage_Init(void)
{
    sItemStorageMenu = AllocZeroed(sizeof(*sItemStorageMenu));
    memset(sItemStorageMenu->windowIds, WINDOW_NONE, ITEMPC_WIN_COUNT);
    sItemStorageMenu->toSwapPos = NOT_SWAPPING;
    sItemStorageMenu->spriteId = SPRITE_NONE;
}

static void ItemStorage_Free(void)
{
    u32 i;
    for (i = 0; i < ITEMPC_WIN_COUNT; i++)
        ItemStorage_RemoveWindow(i);
    Free(sItemStorageMenu);
}

static u8 ItemStorage_AddWindow(u8 i)
{
    u8 *windowIdLoc = &sItemStorageMenu->windowIds[i];
    if (*windowIdLoc == WINDOW_NONE)
    {
        *windowIdLoc = AddWindow(&sWindowTemplates_ItemStorage[i]);
        DrawStdFrameWithCustomTileAndPalette(*windowIdLoc, FALSE, 0x214, 0xE);
        ScheduleBgCopyTilemapToVram(0);
    }
    return *windowIdLoc;
}

static void ItemStorage_RemoveWindow(u8 i)
{
    u8 *windowIdLoc = &sItemStorageMenu->windowIds[i];
    if (*windowIdLoc != WINDOW_NONE)
    {
        ClearStdWindowAndFrameToTransparent(*windowIdLoc, FALSE);
        ClearWindowTilemap(*windowIdLoc);
        ScheduleBgCopyTilemapToVram(0);
        RemoveWindow(*windowIdLoc);
        *windowIdLoc = WINDOW_NONE;
    }
}

void ItemStorage_RefreshListMenu(void)
{
    u16 i;

    // Copy item names for all entries but the last (which is Cancel)
    for(i = 0; i < gPlayerPCItemPageInfo.count - 1; i++)
    {
        CopyItemName_PlayerPC(&sItemStorageMenu->itemNames[i][0], gSaveBlock1Ptr->pcItems[i].itemId);
        sItemStorageMenu->listItems[i].name = &sItemStorageMenu->itemNames[i][0];
        sItemStorageMenu->listItems[i].id = i;
    }

    // Set up Cancel entry
    StringCopy(&sItemStorageMenu->itemNames[i][0], gText_Cancel2);
    sItemStorageMenu->listItems[i].name = &sItemStorageMenu->itemNames[i][0];
    sItemStorageMenu->listItems[i].id = LIST_CANCEL;

    // Set list menu data
    gMultiuseListMenuTemplate = sListMenuTemplate_ItemStorage;
    gMultiuseListMenuTemplate.windowId = ItemStorage_AddWindow(ITEMPC_WIN_LIST);
    gMultiuseListMenuTemplate.totalItems = gPlayerPCItemPageInfo.count;
    gMultiuseListMenuTemplate.items = sItemStorageMenu->listItems;
    gMultiuseListMenuTemplate.maxShowed = gPlayerPCItemPageInfo.pageItems;
}

void CopyItemName_PlayerPC(u8 *string, u16 itemId)
{
    CopyItemName(itemId, string);
}

static void ItemStorage_MoveCursor(s32 id, bool8 onInit, struct ListMenu *list)
{
    if (onInit != TRUE)
        PlaySE(SE_SELECT);
    if (sItemStorageMenu->toSwapPos == NOT_SWAPPING)
    {
        ItemStorage_EraseItemIcon();
        if (id != LIST_CANCEL)
            ItemStorage_DrawItemIcon(gSaveBlock1Ptr->pcItems[id].itemId);
        else
            ItemStorage_DrawItemIcon(MSG_GO_BACK_TO_PREV);
        ItemStorage_PrintDescription(id);
    }
}

static void ItemStorage_PrintMenuItem(u8 windowId, u32 id, u8 yOffset)
{
    if (id != LIST_CANCEL)
    {
        if (sItemStorageMenu->toSwapPos != NOT_SWAPPING)
        {
            if (sItemStorageMenu->toSwapPos == (u8)id)
                ItemStorage_DrawSwapArrow(yOffset, 0, TEXT_SKIP_DRAW);
            else
                ItemStorage_DrawSwapArrow(yOffset, 0xFF, TEXT_SKIP_DRAW);
        }
        ConvertIntToDecimalStringN(gStringVar1, gSaveBlock1Ptr->pcItems[id].quantity, STR_CONV_MODE_RIGHT_ALIGN, 3);
        StringExpandPlaceholders(gStringVar4, gText_xVar1);
        AddTextPrinterParameterized(windowId, FONT_NARROW, gStringVar4, GetStringRightAlignXOffset(FONT_NARROW, gStringVar4, 104), yOffset, TEXT_SKIP_DRAW, NULL);
    }
}

static void ItemStorage_PrintDescription(s32 id)
{
    const u8* description;
    u8 windowId = sItemStorageMenu->windowIds[ITEMPC_WIN_MESSAGE];

    // Get item description (or Cancel text)
    if (id != LIST_CANCEL)
        description = (u8 *)ItemId_GetDescription(gSaveBlock1Ptr->pcItems[id].itemId);
    else
        description = ItemStorage_GetMessage(MSG_GO_BACK_TO_PREV);

    FillWindowPixelBuffer(windowId, PIXEL_FILL(1));
    AddTextPrinterParameterized(windowId, FONT_NORMAL, description, 0, 1, 0, NULL);
}

static void ItemStorage_AddScrollIndicator(void)
{
    if (gPlayerPCItemPageInfo.scrollIndicatorTaskId == TASK_NONE)
        gPlayerPCItemPageInfo.scrollIndicatorTaskId = AddScrollIndicatorArrowPairParameterized(SCROLL_ARROW_UP, 176, 12, 148,
                                                                                                gPlayerPCItemPageInfo.count - gPlayerPCItemPageInfo.pageItems,
                                                                                                TAG_SCROLL_ARROW,
                                                                                                TAG_SCROLL_ARROW,
                                                                                                &gPlayerPCItemPageInfo.itemsAbove);
}

static void ItemStorage_RemoveScrollIndicator(void)
{
    if (gPlayerPCItemPageInfo.scrollIndicatorTaskId != TASK_NONE)
    {
        RemoveScrollIndicatorArrowPair(gPlayerPCItemPageInfo.scrollIndicatorTaskId);
        gPlayerPCItemPageInfo.scrollIndicatorTaskId = TASK_NONE;
    }
}

static void ItemStorage_SetSwapArrow(u8 listTaskId, u8 b, u8 speed)
{
    ItemStorage_DrawSwapArrow(ListMenuGetYCoordForPrintingArrowCursor(listTaskId), b, speed);
}

static void ItemStorage_DrawSwapArrow(u8 y, u8 b, u8 speed)
{
    u8 windowId = sItemStorageMenu->windowIds[ITEMPC_WIN_LIST];
    if (b == 0xFF)
        FillWindowPixelRect(windowId, PIXEL_FILL(1), 0, y, GetMenuCursorDimensionByFont(FONT_NORMAL, 0), GetMenuCursorDimensionByFont(FONT_NORMAL, 1));
    else
        AddTextPrinterParameterized4(windowId, FONT_NORMAL, 0, y, 0, 0, sSwapArrowTextColors, speed, gText_SelectorArrow2);
}

static void ItemStorage_DrawItemIcon(u16 itemId)
{
    u8 spriteId;
    u8* spriteIdLoc = &sItemStorageMenu->spriteId;

    if (*spriteIdLoc == SPRITE_NONE)
    {
        FreeSpriteTilesByTag(TAG_ITEM_ICON);
        FreeSpritePaletteByTag(TAG_ITEM_ICON);
        spriteId = AddItemIconSprite(TAG_ITEM_ICON, TAG_ITEM_ICON, itemId);
        if (spriteId != MAX_SPRITES)
        {
            *spriteIdLoc = spriteId;
            gSprites[spriteId].oam.priority = 0;
            gSprites[spriteId].x2 = 24;
            gSprites[spriteId].y2 = 80;
        }
    }
}

static void ItemStorage_EraseItemIcon(void)
{
    u8* spriteIdLoc = &sItemStorageMenu->spriteId;
    if (*spriteIdLoc != SPRITE_NONE)
    {
        FreeSpriteTilesByTag(TAG_ITEM_ICON);
        FreeSpritePaletteByTag(TAG_ITEM_ICON);
        DestroySprite(&gSprites[*spriteIdLoc]);
        *spriteIdLoc = SPRITE_NONE;
    }
}

static void ItemStorage_CompactList(void)
{
    CompactPCItems();
    SetItemListPerPageCount(gSaveBlock1Ptr->pcItems, PC_ITEMS_COUNT, &gPlayerPCItemPageInfo.pageItems, &gPlayerPCItemPageInfo.count, 8);
}

static void ItemStorage_CompactCursor(void)
{
    SetCursorWithinListBounds(&gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos, gPlayerPCItemPageInfo.pageItems, gPlayerPCItemPageInfo.count);
}

static void ItemStorage_CreateListMenu(u8 taskId)
{
    s16 *data;
    bool32 toss;
    u32 i, x;
    const u8* text;

    data = gTasks[taskId].data;
    for (i = 0; i <= ITEMPC_WIN_LIST_END; i++)
        ItemStorage_AddWindow(i);
    toss = tInTossMenu;
    text = gText_TossItem;
    if (!toss)
        text = gText_WithdrawItem;
    x = GetStringCenterAlignXOffset(FONT_NORMAL, text, 104);
    AddTextPrinterParameterized(sItemStorageMenu->windowIds[ITEMPC_WIN_TITLE], FONT_NORMAL, text, x, 1, 0, NULL);
    CopyWindowToVram(sItemStorageMenu->windowIds[ITEMPC_WIN_ICON], COPYWIN_GFX);
    ItemStorage_CompactList();
    ItemStorage_CompactCursor();
    ItemStorage_RefreshListMenu();
    tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, gPlayerPCItemPageInfo.itemsAbove, gPlayerPCItemPageInfo.cursorPos);
    ItemStorage_AddScrollIndicator();
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].func = ItemStorage_ProcessInput;
}

static const u8* ItemStorage_GetMessage(u16 itemId)
{
    const u8 *string;

    switch(itemId)
    {
    case MSG_GO_BACK_TO_PREV:
        string = gText_GoBackPrevMenu;
        break;
    case MSG_HOW_MANY_TO_WITHDRAW:
        string = gText_WithdrawHowManyItems;
        break;
    case MSG_WITHDREW_ITEM:
        string = gText_WithdrawXItems;
        break;
    case MSG_HOW_MANY_TO_TOSS:
        string = gText_TossHowManyVar1s;
        break;
    case MSG_THREW_AWAY_ITEM:
        string = gText_ThrewAwayVar2Var1s;
        break;
    case MSG_NO_MORE_ROOM:
        string = gText_NoRoomInBag;
        break;
    case MSG_TOO_IMPORTANT:
        string = gText_TooImportantToToss;
        break;
    case MSG_OKAY_TO_THROW_AWAY:
        string = gText_ConfirmTossItems;
        break;
    case MSG_SWITCH_WHICH_ITEM:
        string = gText_MoveVar1Where;
        break;
    default:
        string = ItemId_GetDescription(itemId);
        break;
    }
    return string;
}

static void ItemStorage_PrintMessage(const u8 *string)
{
    u8 windowId = sItemStorageMenu->windowIds[ITEMPC_WIN_MESSAGE];
    FillWindowPixelBuffer(windowId, PIXEL_FILL(1));
    StringExpandPlaceholders(gStringVar4, string);
    AddTextPrinterParameterized(windowId, FONT_NORMAL, gStringVar4, 0, 1, 0, NULL);
}

// Process input while on the item storage's item list
static void ItemStorage_ProcessInput(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (JOY_NEW(SELECT_BUTTON))
    {
        // 'Select' starts input for swapping items if not on Cancel
        ListMenuGetScrollAndRow(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
        if (gPlayerPCItemPageInfo.itemsAbove + gPlayerPCItemPageInfo.cursorPos != gPlayerPCItemPageInfo.count - 1)
        {
            PlaySE(SE_SELECT);
            ItemStorage_StartItemSwap(taskId);
        }
    }
    else
    {
        s32 id = ListMenu_ProcessInput(tListTaskId);
        ListMenuGetScrollAndRow(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
        switch (id)
        {
        case LIST_NOTHING_CHOSEN:
            break;
        case LIST_CANCEL:
            PlaySE(SE_SELECT);
            ItemStorage_ExitItemList(taskId);
            break;
        default:
            PlaySE(SE_SELECT);
            ItemStorage_DoItemAction(taskId);
            break;
        }
    }
}

static void ItemStorage_ReturnToMenuSelect(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (!IsDma3ManagerBusyWithBgCopy())
    {
        DrawDialogueFrame(0, 0);

        // Select Withdraw/Toss by default depending on which was just exited
        if (!tInTossMenu)
            InitItemStorageMenu(taskId, MENU_WITHDRAW);
        else
            InitItemStorageMenu(taskId, MENU_TOSS);
        gTasks[taskId].func = ItemStorageMenuProcessInput;
    }
}

static void ItemStorage_ExitItemList(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    ItemStorage_EraseItemIcon();
    ItemStorage_RemoveScrollIndicator();
    DestroyListMenuTask(tListTaskId, NULL, NULL);
    DestroySwapLineSprites(sItemStorageMenu->swapLineSpriteIds, SWAP_LINE_LENGTH);
    ItemStorage_Free();
    gTasks[taskId].func = ItemStorage_ReturnToMenuSelect;
}

static void ItemStorage_StartItemSwap(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    ListMenuSetUnkIndicatorsStructField(tListTaskId, 16, 1);
    sItemStorageMenu->toSwapPos = gPlayerPCItemPageInfo.itemsAbove + gPlayerPCItemPageInfo.cursorPos;
    ItemStorage_SetSwapArrow(tListTaskId, 0, 0);
    ItemStorage_UpdateSwapLinePos(sItemStorageMenu->toSwapPos);
    CopyItemName(gSaveBlock1Ptr->pcItems[sItemStorageMenu->toSwapPos].itemId, gStringVar1);
    ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_SWITCH_WHICH_ITEM));
    gTasks[taskId].func = ItemStorage_ProcessItemSwapInput;
}

static void ItemStorage_ProcessItemSwapInput(u8 taskId)
{
    s16 *data;
    s32 id;

    data = gTasks[taskId].data;
    if (JOY_NEW(SELECT_BUTTON))
    {
        ListMenuGetScrollAndRow(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
        ItemStorage_FinishItemSwap(taskId, FALSE);
        return;
    }
    id = ListMenu_ProcessInput(tListTaskId);
    ListMenuGetScrollAndRow(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
    SetSwapLineSpritesInvisibility(sItemStorageMenu->swapLineSpriteIds, SWAP_LINE_LENGTH, FALSE);
    ItemStorage_UpdateSwapLinePos(gPlayerPCItemPageInfo.cursorPos);
    switch (id)
    {
    case LIST_NOTHING_CHOSEN:
        break;
    case LIST_CANCEL:
        if (JOY_NEW(A_BUTTON))
            ItemStorage_FinishItemSwap(taskId, FALSE);
        else
            ItemStorage_FinishItemSwap(taskId, TRUE);
        break;
    default:
        ItemStorage_FinishItemSwap(taskId, FALSE);
        break;
    }
}

static void ItemStorage_FinishItemSwap(u8 taskId, bool8 canceled)
{
    s16 *data = gTasks[taskId].data;
    u16 newPos = gPlayerPCItemPageInfo.itemsAbove + gPlayerPCItemPageInfo.cursorPos;
    PlaySE(SE_SELECT);
    DestroyListMenuTask(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);

    if (!canceled && sItemStorageMenu->toSwapPos != newPos && sItemStorageMenu->toSwapPos != newPos - 1)
    {
        MoveItemSlotInList(gSaveBlock1Ptr->pcItems, sItemStorageMenu->toSwapPos, newPos);
        ItemStorage_RefreshListMenu();
    }
    if (sItemStorageMenu->toSwapPos < newPos)
        gPlayerPCItemPageInfo.cursorPos--;

    SetSwapLineSpritesInvisibility(sItemStorageMenu->swapLineSpriteIds, SWAP_LINE_LENGTH, TRUE);
    sItemStorageMenu->toSwapPos = NOT_SWAPPING;
    tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, gPlayerPCItemPageInfo.itemsAbove, gPlayerPCItemPageInfo.cursorPos);
    ScheduleBgCopyTilemapToVram(0);
    gTasks[taskId].func = ItemStorage_ProcessInput;
}

static void ItemStorage_UpdateSwapLinePos(u8 y)
{
    UpdateSwapLineSpritesPos(sItemStorageMenu->swapLineSpriteIds, SWAP_LINE_LENGTH, 128, (y+1) * 16);
}

static void ItemStorage_PrintItemQuantity(u8 windowId, u16 value, u32 mode, u8 x, u8 y, u8 n)
{
    ConvertIntToDecimalStringN(gStringVar1, value, mode, n);
    StringExpandPlaceholders(gStringVar4, gText_xVar1);
    AddTextPrinterParameterized(windowId, FONT_NORMAL, gStringVar4, GetStringCenterAlignXOffset(FONT_NORMAL, gStringVar4, 48), y, 0, NULL);
}

// Start an item Withdraw/Toss
static void ItemStorage_DoItemAction(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 pos = gPlayerPCItemPageInfo.cursorPos + gPlayerPCItemPageInfo.itemsAbove;
    ItemStorage_RemoveScrollIndicator();
    tQuantity = 1;

    if (!tInTossMenu)
    {
        if (gSaveBlock1Ptr->pcItems[pos].quantity == 1)
        {
            // Withdrawing 1 item, do it automatically
            ItemStorage_DoItemWithdraw(taskId);
            return;
        }

        // Withdrawing multiple items, show "how many" message
        CopyItemName(gSaveBlock1Ptr->pcItems[pos].itemId, gStringVar1);
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_HOW_MANY_TO_WITHDRAW));
    }
    else
    {
        if (gSaveBlock1Ptr->pcItems[pos].quantity == 1)
        {
            // Tossing 1 item, do it automatically
            ItemStorage_DoItemToss(taskId);
            return;
        }

        // Tossing multiple items, show "how many" message
        CopyItemName(gSaveBlock1Ptr->pcItems[pos].itemId, gStringVar1);
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_HOW_MANY_TO_TOSS));
    }

    // Set up "how many" prompt
    ItemStorage_PrintItemQuantity(ItemStorage_AddWindow(ITEMPC_WIN_QUANTITY), tQuantity, STR_CONV_MODE_LEADING_ZEROS, 8, 1, 3);
    gTasks[taskId].func = ItemStorage_HandleQuantityRolling;
}

static void ItemStorage_HandleQuantityRolling(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 pos = gPlayerPCItemPageInfo.cursorPos + gPlayerPCItemPageInfo.itemsAbove;

    if (AdjustQuantityAccordingToDPadInput(&tQuantity, gSaveBlock1Ptr->pcItems[pos].quantity) == TRUE)
    {
        ItemStorage_PrintItemQuantity(ItemStorage_AddWindow(ITEMPC_WIN_QUANTITY), tQuantity, STR_CONV_MODE_LEADING_ZEROS, 8, 1, 3);
    }
    else
    {
        if (JOY_NEW(A_BUTTON))
        {
            // Quantity confirmed, perform action
            PlaySE(SE_SELECT);
            ItemStorage_RemoveWindow(ITEMPC_WIN_QUANTITY);
            if (!tInTossMenu)
                ItemStorage_DoItemWithdraw(taskId);
            else
                ItemStorage_DoItemToss(taskId);
        }
        else if (JOY_NEW(B_BUTTON))
        {
            // Canceled action
            PlaySE(SE_SELECT);
            ItemStorage_RemoveWindow(ITEMPC_WIN_QUANTITY);
            ItemStorage_PrintMessage(ItemStorage_GetMessage(gSaveBlock1Ptr->pcItems[pos].itemId));
            ItemStorage_ReturnToListInput(taskId);
        }
    }
}

static void ItemStorage_DoItemWithdraw(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 pos = gPlayerPCItemPageInfo.cursorPos + gPlayerPCItemPageInfo.itemsAbove;

    if (AddBagItem(gSaveBlock1Ptr->pcItems[pos].itemId, tQuantity) == TRUE)
    {
        // Item withdrawn
        CopyItemName(gSaveBlock1Ptr->pcItems[pos].itemId, gStringVar1);
        ConvertIntToDecimalStringN(gStringVar2, tQuantity, STR_CONV_MODE_LEFT_ALIGN, 3);
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_WITHDREW_ITEM));
        gTasks[taskId].func = ItemStorage_HandleRemoveItem;
    }
    else
    {
        // No room to withdraw items
        tQuantity = 0;
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_NO_MORE_ROOM));
        gTasks[taskId].func = ItemStorage_HandleErrorMessageInput;
    }
}

static void ItemStorage_DoItemToss(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u16 pos = gPlayerPCItemPageInfo.cursorPos + gPlayerPCItemPageInfo.itemsAbove;

    if (!ItemId_GetImportance(gSaveBlock1Ptr->pcItems[pos].itemId))
    {
        // Show toss confirmation prompt
        CopyItemName(gSaveBlock1Ptr->pcItems[pos].itemId, gStringVar1);
        ConvertIntToDecimalStringN(gStringVar2, tQuantity, STR_CONV_MODE_LEFT_ALIGN, 3);
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_OKAY_TO_THROW_AWAY));
        CreateYesNoMenuWithCallbacks(taskId, &sWindowTemplates_ItemStorage[ITEMPC_WIN_YESNO], 1, 0, 1, 0x214, 0xE, &ItemTossYesNoFuncs);
    }
    else
    {
        // Can't toss important items
        tQuantity = 0;
        ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_TOO_IMPORTANT));
        gTasks[taskId].func = ItemStorage_HandleErrorMessageInput;
    }
}

static void ItemStorage_TossItemYes(u8 taskId)
{
    ItemStorage_PrintMessage(ItemStorage_GetMessage(MSG_THREW_AWAY_ITEM));
    gTasks[taskId].func = ItemStorage_HandleRemoveItem;
}

static void ItemStorage_TossItemNo(u8 taskId)
{
    ItemStorage_PrintMessage(ItemStorage_GetMessage(gSaveBlock1Ptr->pcItems[gPlayerPCItemPageInfo.itemsAbove + gPlayerPCItemPageInfo.cursorPos].itemId));
    ItemStorage_ReturnToListInput(taskId);
}

// Remove item from PC (was either Tossed or Withdrawn)
static void ItemStorage_HandleRemoveItem(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (JOY_NEW(A_BUTTON | B_BUTTON))
    {
        RemovePCItem(gPlayerPCItemPageInfo.cursorPos + gPlayerPCItemPageInfo.itemsAbove, tQuantity);
        DestroyListMenuTask(tListTaskId, &gPlayerPCItemPageInfo.itemsAbove, &gPlayerPCItemPageInfo.cursorPos);
        ItemStorage_CompactList();
        ItemStorage_CompactCursor();
        ItemStorage_RefreshListMenu();
        tListTaskId = ListMenuInit(&gMultiuseListMenuTemplate, gPlayerPCItemPageInfo.itemsAbove, gPlayerPCItemPageInfo.cursorPos);
        ItemStorage_ReturnToListInput(taskId);
    }
}

static void ItemStorage_HandleErrorMessageInput(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    if (JOY_NEW(A_BUTTON | B_BUTTON))
    {
        ItemStorage_PrintMessage(ItemStorage_GetMessage(gSaveBlock1Ptr->pcItems[gPlayerPCItemPageInfo.itemsAbove + gPlayerPCItemPageInfo.cursorPos].itemId));
        ItemStorage_ReturnToListInput(taskId);
    }
}

static void ItemStorage_ReturnToListInput(u8 taskId)
{
    ItemStorage_AddScrollIndicator();
    gTasks[taskId].func = ItemStorage_ProcessInput;
}
