#include "global.h"
#include "item_use.h"
#include "battle.h"
#include "berry.h"
#include "bike.h"
#include "coins.h"
#include "data2.h"
#include "event_data.h"
#include "field_effect.h"
#include "field_fadetransition.h"
#include "field_player_avatar.h"
#include "field_weather.h"
#include "fieldmap.h"
#include "item.h"
#include "item_menu.h"
#include "constants/flags.h"
#include "constants/items.h"
#include "mail.h"
#include "main.h"
#include "event_object_lock.h"
#include "menu.h"
#include "menu_helpers.h"
#include "metatile_behavior.h"
#include "palette.h"
#include "party_menu.h"
#include "pokeblock.h"
#include "pokemon_item_effect.h"
#include "pokemon_menu.h"
#include "overworld.h"
#include "rom_8094928.h"
#include "script.h"
#include "constants/songs.h"
#include "sound.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "constants/event_bg.h"
#include "constants/map_types.h"
#include "constants/species.h"
#include "constants/vars.h"

extern void (*gFieldItemUseCallback)(u8);
extern void (*gFieldCallback)(void);
extern void (*gPokemonItemUseCallback)(u8, u16, TaskFunc);

extern u8 gPokemonItemUseType;
extern u8 gLastFieldPokeMenuOpened;
extern u8 gBankInMenu;

extern u8 S_PlantBerryTreeFromBag[];
extern u8 S_WaterBerryTreeFromBag[];

extern u16 gBattlerPartyIndexes[];

extern u16 gBattleTypeFlags;

static void ItemUseOnFieldCB_Berry(u8);
static void ItemUseOnFieldCB_WailmerPail(u8);
static void StartTeachMonTMHMMove(u8);
static void DisplayTeachMonTMHMYesNoChoice(u8);
static void BootTMHM(u8);
static void WaitButtonPressAndDisplayTMHMInfo(u8);

static const u8 gSSTidalBetaString[] = _("この　チケットで　ふねに　のりほうだい\nはやく　のってみたいな");
static const u8 gSSTidalBetaString2[] = _("この　チケットで　ふねに　のりほうだい\nはやく　のってみたいな");

static const u8 *const sSSTidalBetaStrings[] =
{
    gSSTidalBetaString,
    gSSTidalBetaString2,
};

static const MainCallback gExitToOverworldFuncList[] =
{
    sub_808B020,
    CB2_ReturnToField,
    sub_810B96C,
};

static const u8 gItemFinderDirections[] = { DIR_NORTH, DIR_EAST, DIR_SOUTH, DIR_WEST };

static const struct YesNoFuncTable sTeachMonTMHMYesNoActions =
{
    .yesFunc = StartTeachMonTMHMMove,
    .noFunc = CleanUpItemMenuMessage,
};

void ExecuteSwitchToOverworldFromItemUse(u8 taskId)
{
    u8 taskData;

    if (gSpecialVar_ItemId == ITEM_ENIGMA_BERRY)
        taskData = gTasks[taskId].data[15] - 1;
    else
        taskData = ItemId_GetType(gSpecialVar_ItemId) - 1;

    gTasks[taskId].data[8] = (u32)gExitToOverworldFuncList[taskData] >> 16;
    gTasks[taskId].data[9] = (u32)gExitToOverworldFuncList[taskData];
    gTasks[taskId].func = HandleItemMenuPaletteFade;
}

void ItemMenu_ConfirmNormalFade(u8 var)
{
    ExecuteSwitchToOverworldFromItemUse(var);
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
}

void ItemMenu_ConfirmComplexFade(u8 var)
{
    ExecuteSwitchToOverworldFromItemUse(var);
    FadeScreen(1, 0);
}

void SetUpItemUseOnFieldCallback(u8 taskId)
{
    if (gTasks[taskId].data[2] != 1)
    {
        gFieldCallback = ExecuteItemUseFromBlackPalette;
        ItemMenu_ConfirmNormalFade(taskId);
    }
    else
    {
        gFieldItemUseCallback(taskId);
    }
}

static void HandleDeniedItemUseMessage(u8 taskId, u8 playerMenuStatus, const u8 *text)
{
    StringExpandPlaceholders(gStringVar4, text);

    switch (playerMenuStatus)
    {
    case 0: // Item Menu
        Menu_EraseWindowRect(0, 13, 13, 20);
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpItemMenuMessage, 1);
        break;
    default: // Field
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpOverworldMessage, 0);
        break;
    }
}

void DisplayDadsAdviceCannotUseItemMessage(u8 taskId, u8 playerMenuStatus)
{
    HandleDeniedItemUseMessage(taskId, playerMenuStatus, gOtherText_DadsAdvice);
}

void DisplayCantGetOffBikeItemMessage(u8 taskId, u8 playerMenuStatus)
{
    HandleDeniedItemUseMessage(taskId, playerMenuStatus, gOtherText_CantGetOffBike);
}

u8 CheckIfItemIsTMHMOrEvolutionStone(u16 itemId)
{
    if (ItemId_GetFieldFunc(itemId) == ItemUseOutOfBattle_TMHM)
        return 1;
    else if (ItemId_GetFieldFunc(itemId) == ItemUseOutOfBattle_EvolutionStone)
        return 2;
    else
        return 0;
}

void ItemMenu_ReadMail(u8 taskId)
{
    struct MailStruct mailStruct;

    if (!gPaletteFade.active)
    {
        mailStruct.itemId = gSpecialVar_ItemId;
        HandleReadMail(&mailStruct, sub_80A5D04, 0);
        DestroyTask(taskId);
    }
}

void ItemUseOutOfBattle_Mail(u8 taskId)
{
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
    gTasks[taskId].func = ItemMenu_ReadMail;
}

void ItemUseOutOfBattle_Bike(u8 taskId)
{
    s16 x, y;
    u8 tileBehavior;

    PlayerGetDestCoords(&x, &y);
    tileBehavior = MapGridGetMetatileBehaviorAt(x, y);

    if (FlagGet(FLAG_SYS_CYCLING_ROAD) == TRUE
        || MetatileBehavior_IsVerticalRail(tileBehavior) == TRUE
        || MetatileBehavior_IsHorizontalRail(tileBehavior) == TRUE
        || MetatileBehavior_IsIsolatedVerticalRail(tileBehavior) == TRUE
        || MetatileBehavior_IsIsolatedHorizontalRail(tileBehavior) == TRUE)
    {
        DisplayCantGetOffBikeItemMessage(taskId, gTasks[taskId].data[2]);
    }
    else
    {
        if (Overworld_IsBikingAllowed() == TRUE && IsBikingDisallowedByPlayer() == FALSE)
        {
            gFieldItemUseCallback = (void *)ItemUseOnFieldCB_Bike;
            SetUpItemUseOnFieldCallback(taskId);
        }
        else
            DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
    }
}

void ItemUseOnFieldCB_Bike(u8 taskId)
{
    if (ItemId_GetSecondaryId(gSpecialVar_ItemId) == 0)
        GetOnOffBike(PLAYER_AVATAR_FLAG_MACH_BIKE);
    if (ItemId_GetSecondaryId(gSpecialVar_ItemId) == 1)
        GetOnOffBike(PLAYER_AVATAR_FLAG_ACRO_BIKE);

    ScriptUnfreezeObjectEvents();
    ScriptContext2_Disable();
    DestroyTask(taskId);
}

bool32 CanFish(void)
{
    s16 x, y;
    u16 tileBehavior;

    GetXYCoordsOneStepInFrontOfPlayer(&x, &y);
    tileBehavior = MapGridGetMetatileBehaviorAt(x, y);

    if (MetatileBehavior_IsWaterfall(tileBehavior))
        return FALSE;

    if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_UNDERWATER))
        return FALSE;

    if (!TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
    {
        if (IsPlayerFacingSurfableFishableWater())
            return TRUE;
    }
    else
    {
        if (MetatileBehavior_IsSurfableWaterOrUnderwater(tileBehavior) && MapGridGetCollisionAt(x, y) == 0)
            return TRUE;
        if (MetatileBehavior_IsBridge(tileBehavior) == TRUE)
            return TRUE;
    }

    return FALSE;
}

void ItemUseOutOfBattle_Rod(u8 taskId)
{
    if (CanFish() == TRUE)
    {
        gFieldItemUseCallback = (void *)ItemUseOnFieldCB_Rod;
        SetUpItemUseOnFieldCallback(taskId);
    }
    else
        DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
}

void ItemUseOnFieldCB_Rod(u8 taskId)
{
    StartFishing(ItemId_GetSecondaryId(gSpecialVar_ItemId));
    DestroyTask(taskId);
}

void ItemUseOutOfBattle_Itemfinder(u8 taskId)
{
    IncrementGameStat(GAME_STAT_USED_ITEMFINDER);
    gFieldItemUseCallback = (void *)ItemUseOnFieldCB_Itemfinder;
    SetUpItemUseOnFieldCallback(taskId);
}

void ItemUseOnFieldCB_Itemfinder(u8 taskId)
{
    if (ItemfinderCheckForHiddenItems(gMapHeader.events, taskId) == TRUE)
        gTasks[taskId].func = RunItemfinderResults;
    else
        DisplayItemMessageOnField(taskId, gOtherText_NoResponse, ExitItemfinder, 0);
}

void RunItemfinderResults(u8 taskId)
{
    u8 playerDir;
    u8 playerDirToItem;
    u8 i;
    s16 *data = gTasks[taskId].data;

    if (!data[3])
    {
        if (data[4] == 4)
        {
            playerDirToItem = GetPlayerDirectionTowardsHiddenItem(data[0], data[1]);
            if (playerDirToItem)
            {
                SetPlayerDirectionTowardsItem(gItemFinderDirections[playerDirToItem - 1]);
                gTasks[taskId].func = DisplayItemRespondingMessageAndExitItemfinder;
            }
            else // player is above hidden item.
            {
                playerDir = GetPlayerFacingDirection();

                // rotate player clockwise depending on current direction.
                for (i = 0; i < 4; i++)
                    if (playerDir == gItemFinderDirections[i])
                        data[5] = (i + 1) & 3;

                gTasks[taskId].func = RotatePlayerAndExitItemfinder;
                data[3] = 0;
                data[2] = 0;
            }
            return;
        }
        PlaySE(SE_ITEMFINDER); // play the itemfinder jingle 4 times before executing the itemfinder.
        data[4]++;
    }
    data[3] = (data[3] + 1) & 0x1F;
}

void ExitItemfinder(u8 taskId)
{
    Menu_EraseWindowRect(0, 14, 29, 19);
    ScriptUnfreezeObjectEvents();
    ScriptContext2_Disable();
    DestroyTask(taskId);
}

bool8 ItemfinderCheckForHiddenItems(const struct MapEvents *events, u8 taskId)
{
    int distanceX, distanceY;
    u16 x, y;
    s16 newDistanceX, newDistanceY, i;

    PlayerGetDestCoords(&x, &y);
    gTasks[taskId].data[2] = FALSE;

    for (i = 0; i < events->bgEventCount; i++)
    {
        if ((events->bgEvents[i].kind == BG_EVENT_HIDDEN_ITEM) && !FlagGet(events->bgEvents[i].bgUnion.hiddenItem.hiddenItemId + FLAG_HIDDEN_ITEMS_START))
        {
            // do a distance lookup of each item so long as the index remains less than the objects on the current map.
            distanceX = (u16)events->bgEvents[i].x + 7;
            newDistanceX = distanceX - x;
            distanceY = (u16)events->bgEvents[i].y + 7;
            newDistanceY = distanceY - y;

            // is item in range?
            if ((u16)(newDistanceX + 7) < 15 && (newDistanceY >= -5) && (newDistanceY < 6))
                sub_80C9838(taskId, newDistanceX, newDistanceY); // send coordinates of the item relative to the player
        }
    }
    sub_80C9720(taskId);

    // hidden item detected?
    if (gTasks[taskId].data[2] == TRUE)
        return TRUE;
    else
        return FALSE;
}

bool8 HiddenItemAtPos(const struct MapEvents *events, s16 x, s16 y)
{
    u8 bgEventCount = events->bgEventCount;
    const struct BgEvent *bgEvent = events->bgEvents;
    int i;

    for (i = 0; i < bgEventCount; i++)
    {
        if (bgEvent[i].kind == BG_EVENT_HIDDEN_ITEM && x == (u16)bgEvent[i].x && y == (u16)bgEvent[i].y) // hidden item and coordinates matches x and y passed?
        {
            if (!FlagGet(bgEvent[i].bgUnion.hiddenItem.hiddenItemId + FLAG_HIDDEN_ITEMS_START))
                return TRUE;
            else
                return FALSE;
        }
    }
    return FALSE;
}

bool8 sub_80C9688(const struct MapConnection *connection, int x, int y)
{
    const struct MapHeader *mapHeader;
    u16 localX, localY;
    u32 localOffset;
    s32 localLength;

    mapHeader = GetMapHeaderFromConnection(connection);

    switch (connection->direction)
    {
    // same weird temp variable behavior seen in HiddenItemAtPos
    case 2:
        localOffset = connection->offset + 7;
        localX = x - localOffset;
        localLength = mapHeader->mapLayout->height - 7;
        localY = localLength + y; // additions are reversed for some reason
        break;
    case 1:
        localOffset = connection->offset + 7;
        localX = x - localOffset;
        localLength = gMapHeader.mapLayout->height + 7;
        localY = y - localLength;
        break;
    case 3:
        localLength = mapHeader->mapLayout->width - 7;
        localX = localLength + x; // additions are reversed for some reason
        localOffset = connection->offset + 7;
        localY = y - localOffset;
        break;
    case 4:
        localLength = gMapHeader.mapLayout->width + 7;
        localX = x - localLength;
        localOffset = connection->offset + 7;
        localY = y - localOffset;
        break;
    default:
        return FALSE;
    }
    return HiddenItemAtPos(mapHeader->events, localX, localY);
}

void sub_80C9720(u8 taskId)
{
    s16 x, y;
    s16 curX, curY;
    s16 width = gMapHeader.mapLayout->width + 7;
    s16 height = gMapHeader.mapLayout->height + 7;

    s16 var1 = 7;
    s16 var2 = 7;

    PlayerGetDestCoords(&x, &y);

    for (curX = x - 7; curX <= x + 7; curX++)
    {
        for (curY = y - 5; curY <= y + 5; curY++)
        {
            if (var1 > curX
             || curX >= width
             || var2 > curY
             || curY >= height)
            {
                const struct MapConnection *conn = GetMapConnectionAtPos(curX, curY);
                if (conn && sub_80C9688(conn, curX, curY) == TRUE)
                    sub_80C9838(taskId, curX - x, curY - y);
            }
        }
    }
}

void sub_80C9838(u8 taskId, s16 x, s16 y)
{
    s16 *data = gTasks[taskId].data;
    s16 var1, var2, var3, var4;

    if (data[2] == FALSE)
    {
        data[0] = x;
        data[1] = y;
        data[2] = TRUE;
    }
    else
    {
        // data[0] and data[1] contain the player's coordinates.
        // x and y contain the item's coordinates.
        if (data[0] < 0)
            var1 = data[0] * -1; // item is to the left
        else
            var1 = data[0]; // item is to the right

        if (data[1] < 0)
            var2 = data[1] * -1; // item is to the north
        else
            var2 = data[1]; // item is to the south

        if (x < 0)
            var3 = x * -1;
        else
            var3 = x;

        if (y < 0)
            var4 = y * -1;
        else
            var4 = y;

        if (var1 + var2 > var3 + var4)
        {
            data[0] = x;
            data[1] = y;
        }
        else
        {
            if (var1 + var2 == var3 + var4 && (var2 > var4 || (var2 == var4 && data[1] < y)))
            {
                data[0] = x;
                data[1] = y;
            }
        }
    }
}

u8 GetPlayerDirectionTowardsHiddenItem(s16 itemX, s16 itemY)
{
    s16 abX, abY;

    if (itemX == 0 && itemY == 0)
        return DIR_NONE; // player is standing on the item.

    // get absolute X distance.
    if (itemX < 0)
        abX = itemX * -1;
    else
        abX = itemX;

    // get absolute Y distance.
    if (itemY < 0)
        abY = itemY * -1;
    else
        abY = itemY;

    if (abX > abY)
    {
        if (itemX < 0)
            return DIR_EAST;
        else
            return DIR_NORTH;
    }
    else
    {
        if (abX < abY)
        {
            if (itemY < 0)
                return DIR_SOUTH;
            else
                return DIR_WEST;
        }
        if (abX == abY)
        {
            if (itemY < 0)
                return DIR_SOUTH;
            else
                return DIR_WEST;
        }
        return DIR_NONE; // should never get here. return something so it doesnt crash.
    }
}

void SetPlayerDirectionTowardsItem(u8 direction)
{
    ObjectEventClearHeldMovementIfFinished(&gObjectEvents[GetObjectEventIdByLocalIdAndMap(0xFF, 0, 0)]);
    ObjectEventClearHeldMovement(&gObjectEvents[GetObjectEventIdByLocalIdAndMap(0xFF, 0, 0)]);
    UnfreezeObjectEvent(&gObjectEvents[GetObjectEventIdByLocalIdAndMap(0xFF, 0, 0)]);
    PlayerTurnInPlace(direction);
}

void DisplayItemRespondingMessageAndExitItemfinder(u8 taskId)
{
    if (ObjectEventCheckHeldMovementStatus(&gObjectEvents[GetObjectEventIdByLocalIdAndMap(0xFF, 0, 0)]) == TRUE)
        DisplayItemMessageOnField(taskId, gOtherText_ItemfinderResponding, ExitItemfinder, 0);
}

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

    if (ObjectEventCheckHeldMovementStatus(&gObjectEvents[GetObjectEventIdByLocalIdAndMap(0xFF, 0, 0)]) == TRUE
    || data[2] == FALSE)
    {
        SetPlayerDirectionTowardsItem(gItemFinderDirections[data[5]]);
        data[2] = 1;
        data[5] = (data[5] + 1) & 3;
        data[3]++;

        if (data[3] == 4)
            DisplayItemMessageOnField(taskId, gOtherText_ItemfinderItemUnderfoot, ExitItemfinder, 0);
    }
}

void ItemUseOutOfBattle_PokeblockCase(u8 taskId)
{
    if (sub_80F9344() == TRUE)
    {
        DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
    }
    else if (gTasks[taskId].data[2] != TRUE)
    {
        sub_810BA7C(0);
        ItemMenu_ConfirmNormalFade(taskId);
    }
    else
    {
        gFieldCallback = (void *)sub_8080E28;
        sub_810BA7C(1);
        ItemMenu_ConfirmComplexFade(taskId);
    }
}

void ItemUseOutOfBattle_CoinCase(u8 taskId)
{
    ConvertIntToDecimalStringN(gStringVar1, GetCoins(), 0, 4);
    StringExpandPlaceholders(gStringVar4, gOtherText_Coins3);

    if (!gTasks[taskId].data[2])
    {
        Menu_EraseWindowRect(0, 13, 13, 20);
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpItemMenuMessage, 1);
    }
    else
    {
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpOverworldMessage, 0);
    }
}

static void SSTicketWaitForAButtonPress(u8 taskId)
{
    if (gMain.newKeys & A_BUTTON)
        CleanUpItemMenuMessage(taskId);
}

static void SSTicketWaitForAButtonPress2(u8 taskId)
{
    if (gMain.newKeys & A_BUTTON)
        CleanUpOverworldMessage(taskId);
}

// unused
void ItemUseOutOfBattle_SSTicket(u8 taskId)
{
    if (gTasks[taskId].data[2] == 0)
    {
        Menu_EraseWindowRect(0, 13, 13, 20);
        DisplayItemMessageOnField(taskId, sSSTidalBetaStrings[ItemId_GetSecondaryId(gSpecialVar_ItemId)], SSTicketWaitForAButtonPress, 1);
    }
    else
    {
        DisplayItemMessageOnField(taskId, sSSTidalBetaStrings[ItemId_GetSecondaryId(gSpecialVar_ItemId)], SSTicketWaitForAButtonPress2, 0);
    }
}

void ItemUseOutOfBattle_Berry(u8 taskId)
{
    if (IsPlayerFacingUnplantedSoil() == TRUE)
    {
        gFieldItemUseCallback = ItemUseOnFieldCB_Berry;
        gFieldCallback = ExecuteItemUseFromBlackPalette;
        gTasks[taskId].data[8] = (u32)CB2_ReturnToField >> 16;
        gTasks[taskId].data[9] = (u32)CB2_ReturnToField;
        gTasks[taskId].func = HandleItemMenuPaletteFade;
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, RGB(0, 0, 0));
    }
    else
    {
        ItemId_GetFieldFunc(gSpecialVar_ItemId)(taskId);
    }
}

static void ItemUseOnFieldCB_Berry(u8 taskId)
{
    RemoveBagItem(gSpecialVar_ItemId, 1);
    ScriptContext2_Enable();
    ScriptContext1_SetupScript(S_PlantBerryTreeFromBag);
    DestroyTask(taskId);
}

void ItemUseOutOfBattle_WailmerPail(u8 taskId)
{
    if (TryToWaterBerryTree() == TRUE)
    {
        gFieldItemUseCallback = ItemUseOnFieldCB_WailmerPail;
        SetUpItemUseOnFieldCallback(taskId);
    }
    else
    {
        DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
    }
}

static void ItemUseOnFieldCB_WailmerPail(u8 taskId)
{
    ScriptContext2_Enable();
    ScriptContext1_SetupScript(S_WaterBerryTreeFromBag);
    DestroyTask(taskId);
}

static void SetPokemonItemUseAndFadeOut(u8 taskId)
{
    gPokemonItemUseType = ITEM_USE_SINGLE_MON;
    ItemMenu_ConfirmNormalFade(taskId);
}

void ItemUseOutOfBattle_Medicine(u8 taskId)
{
    gPokemonItemUseCallback = UseMedicine;
    SetPokemonItemUseAndFadeOut(taskId);
}

void ItemUseOutOfBattle_SacredAsh(u8 taskId)
{
    u8 i;

    gLastFieldPokeMenuOpened = 0;

    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) != SPECIES_NONE && GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0)
        {
            gLastFieldPokeMenuOpened = i;
            break;
        }
    }
    gPokemonItemUseCallback = DoSacredAshItemEffect;
    gPokemonItemUseType = ITEM_USE_ALL_MONS;
    ItemMenu_ConfirmNormalFade(taskId);
}

void ItemUseOutOfBattle_PPRecovery(u8 taskId)
{
    gPokemonItemUseCallback = DoPPRecoveryItemEffect;
    SetPokemonItemUseAndFadeOut(taskId);
}

void ItemUseOutOfBattle_PPUp(u8 taskId)
{
    gPokemonItemUseCallback = DoPPUpItemEffect;
    SetPokemonItemUseAndFadeOut(taskId);
}

void ItemUseOutOfBattle_RareCandy(u8 taskId)
{
    gPokemonItemUseCallback = DoRareCandyItemEffect;
    SetPokemonItemUseAndFadeOut(taskId);
}

void ItemUseOutOfBattle_TMHM(u8 taskId)
{
    Menu_EraseWindowRect(0, 13, 13, 20);

    if (gSpecialVar_ItemId >= ITEM_HM01_CUT)
        DisplayItemMessageOnField(taskId, gOtherText_BootedHM, BootTMHM, 1); // HM
    else
        DisplayItemMessageOnField(taskId, gOtherText_BootedTM, BootTMHM, 1); // TM
}

static void BootTMHM(u8 taskId)
{
    PlaySE(SE_PC_LOGIN);
    gTasks[taskId].func = WaitButtonPressAndDisplayTMHMInfo;
}

static void WaitButtonPressAndDisplayTMHMInfo(u8 taskId)
{
    if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON)
    {
        StringCopy(gStringVar1, gMoveNames[ItemIdToBattleMoveId(gSpecialVar_ItemId)]);
        StringExpandPlaceholders(gStringVar4, gOtherText_ContainsMove);
        DisplayItemMessageOnField(taskId, gStringVar4, DisplayTeachMonTMHMYesNoChoice, 1);
    }
}

static void DisplayTeachMonTMHMYesNoChoice(u8 taskId)
{
    DisplayYesNoMenu(7, 7, 1);
    sub_80A3FA0(gBGTilemapBuffers[1], 8, 8, 5, 4, 1);
    DoYesNoFuncWithChoice(taskId, &sTeachMonTMHMYesNoActions);
}

static void StartTeachMonTMHMMove(u8 taskId)
{
    gPokemonItemUseCallback = TeachMonTMMove;
    SetPokemonItemUseAndFadeOut(taskId);
}

static void PrepareItemUseMessage(void)
{
    RemoveBagItem(gSpecialVar_ItemId, 1);
    sub_80A3E0C();
    CopyItemName(gSpecialVar_ItemId, gStringVar2);
    StringExpandPlaceholders(gStringVar4, gOtherText_UsedItem);
}

void ItemUseOutOfBattle_Repel(u8 taskId)
{
    if (VarGet(VAR_REPEL_STEP_COUNT) == 0)
    {
        VarSet(VAR_REPEL_STEP_COUNT, ItemId_GetHoldEffectParam(gSpecialVar_ItemId));
        PrepareItemUseMessage();
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpItemMenuMessage, 1);
    }
    else
    {
        DisplayItemMessageOnField(taskId, gOtherText_RepelLingers, CleanUpItemMenuMessage, 1);
    }
}

static void sub_80CA07C(void)
{
    sub_80A3E0C();
    CopyItemName(gSpecialVar_ItemId, gStringVar2);
}

static void PlayBlackWhiteFluteSound(u8 taskId)
{
    if(++gTasks[taskId].data[15] > 7)
    {
        PlaySE(SE_GLASS_FLUTE);
        DisplayItemMessageOnField(taskId, gStringVar4, CleanUpItemMenuMessage, 1);
    }
}

void ItemUseOutOfBattle_BlackWhiteFlute(u8 taskId)
{
    if (gSpecialVar_ItemId == ITEM_WHITE_FLUTE)
    {
        FlagSet(FLAG_SYS_ENC_UP_ITEM);
        FlagClear(FLAG_SYS_ENC_DOWN_ITEM);
        sub_80CA07C();
        StringExpandPlaceholders(gStringVar4, gOtherText_UsedFlute);
        gTasks[taskId].func = PlayBlackWhiteFluteSound;
        gTasks[taskId].data[15] = 0;
    }
    else if (gSpecialVar_ItemId == ITEM_BLACK_FLUTE)
    {
        FlagSet(FLAG_SYS_ENC_DOWN_ITEM);
        FlagClear(FLAG_SYS_ENC_UP_ITEM);
        sub_80CA07C();
        StringExpandPlaceholders(gStringVar4, gOtherText_UsedRepel);
        gTasks[taskId].func = PlayBlackWhiteFluteSound;
        gTasks[taskId].data[15] = 0;
    }
}

void task08_080A1C44(u8 taskId)
{
    ResetInitialPlayerAvatarState();
    StartEscapeRopeFieldEffect();
    DestroyTask(taskId);
}

static void ItemUseOnFieldCB_EscapeRope(u8 taskId)
{
    Overworld_ResetStateAfterDigEscRope();
    PrepareItemUseMessage();
    gTasks[taskId].data[0] = 0;
    DisplayItemMessageOnField(taskId, gStringVar4, task08_080A1C44, 0);
}

bool8 CanUseEscapeRopeOnCurrMap(void)
{
    if (gMapHeader.mapType == MAP_TYPE_UNDERGROUND)
        return TRUE;
    else
        return FALSE;
}

void ItemUseOutOfBattle_EscapeRope(u8 taskId)
{
    if (CanUseEscapeRopeOnCurrMap() == TRUE)
    {
        gFieldItemUseCallback = ItemUseOnFieldCB_EscapeRope;
        SetUpItemUseOnFieldCallback(taskId);
    }
    else
    {
        DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
    }
}

void ItemUseOutOfBattle_EvolutionStone(u8 taskId)
{
    gPokemonItemUseCallback = DoEvolutionStoneItemEffect;
    SetPokemonItemUseAndFadeOut(taskId);
}

void ItemUseInBattle_PokeBall(u8 taskId)
{
    if (PlayerPartyAndPokemonStorageFull() == FALSE) // have room for mon?
    {
        RemoveBagItem(gSpecialVar_ItemId, 1);
        sub_80A7094(taskId);
    }
    else
    {
        Menu_EraseWindowRect(0, 13, 13, 20);
        DisplayItemMessageOnField(taskId, gOtherText_BoxIsFull, CleanUpItemMenuMessage, 1);
    }
}

void sub_80CA294(u8 taskId)
{
    if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON)
        sub_80A7094(taskId);
}

void sub_80CA2BC(u8 taskId)
{
    if(++gTasks[taskId].data[15] > 7)
    {
        PlaySE(SE_USE_ITEM);
        RemoveBagItem(gSpecialVar_ItemId, 1);
        DisplayItemMessageOnField(taskId, sub_803F378(gSpecialVar_ItemId), sub_80CA294, 1);
    }
}

void ItemUseInBattle_StatIncrease(u8 taskId)
{
    u16 partyId = gBattlerPartyIndexes[gBankInMenu];

    Menu_EraseWindowRect(0, 13, 13, 20);

    if (ExecuteTableBasedItemEffect_(&gPlayerParty[partyId], gSpecialVar_ItemId, partyId, 0) != FALSE)
    {
        DisplayItemMessageOnField(taskId, gOtherText_WontHaveAnyEffect, CleanUpItemMenuMessage, 1);
    }
    else
    {
        gTasks[taskId].func = sub_80CA2BC;
        gTasks[taskId].data[15] = 0;
    }
}

void sub_80CA394(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        sub_8094E4C();
        FreeAndReserveObjectSpritePalettes();
        DestroyTask(taskId);
    }
}

void sub_80CA3C0(u8 taskId)
{
    gTasks[taskId].func = sub_80CA394;
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
}

void ItemUseInBattle_Medicine(u8 var)
{
    gPokemonItemUseCallback = UseMedicine;
    sub_80CA3C0(var);
}

void unref_sub_80CA410(u8 var)
{
    gPokemonItemUseCallback = DoSacredAshItemEffect;
    sub_80CA3C0(var);
}

void ItemUseInBattle_PPRecovery(u8 var)
{
    gPokemonItemUseCallback = DoPPRecoveryItemEffect;
    sub_80CA3C0(var);
}

void unref_sub_80CA448(u8 var)
{
    Menu_EraseWindowRect(0, 13, 13, 20);

    if (ExecuteTableBasedItemEffect__(0, gSpecialVar_ItemId, 0) == FALSE)
    {
        RemoveBagItem(gSpecialVar_ItemId, 1);
        GetMonNickname(&gPlayerParty[0], gStringVar1);
        StringExpandPlaceholders(gStringVar4, gOtherText_SnapConfusion);
        DisplayItemMessageOnField(var, gStringVar4, sub_80A7094, 1);
    }
    else
    {
        DisplayItemMessageOnField(var, gOtherText_WontHaveAnyEffect, CleanUpItemMenuMessage, 1);
    }
}

void ItemUseInBattle_Escape(u8 taskId)
{
    Menu_EraseWindowRect(0, 13, 13, 20);

    if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) == FALSE)
    {
        PrepareItemUseMessage();
        DisplayItemMessageOnField(taskId, gStringVar4, sub_80A7094, 1);
    }
    else
    {
        DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
    }
}

void ItemUseOutOfBattle_EnigmaBerry(u8 taskId)
{
    switch (GetItemEffectType(gSpecialVar_ItemId) - 1)
    {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 10:
    case 11:
    case 12:
    case 13:
    case 14:
    case 15:
    case 16:
        gTasks[taskId].data[15] = 1;
        ItemUseOutOfBattle_Medicine(taskId);
        break;
    case 9:
        gTasks[taskId].data[15] = 1;
        ItemUseOutOfBattle_SacredAsh(taskId);
        break;
    case 0:
        gTasks[taskId].data[15] = 1;
        ItemUseOutOfBattle_RareCandy(taskId);
        break;
    case 18:
    case 19:
        gTasks[taskId].data[15] = 1;
        ItemUseOutOfBattle_PPUp(taskId);
        break;
    case 20:
        gTasks[taskId].data[15] = 1;
        ItemUseOutOfBattle_PPRecovery(taskId);
        break;
    default:
        gTasks[taskId].data[15] = 4;
        ItemUseOutOfBattle_CannotUse(taskId);
    }
}

void ItemUseInBattle_EnigmaBerry(u8 taskId)
{
    switch (GetItemEffectType(gSpecialVar_ItemId))
    {
    case 0:
        ItemUseInBattle_StatIncrease(taskId);
        break;
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7:
    case 8:
    case 9:
    case 11:
        ItemUseInBattle_Medicine(taskId);
        break;
    case 21:
        ItemUseInBattle_PPRecovery(taskId);
        break;
    default:
        ItemUseOutOfBattle_CannotUse(taskId);
    }
}

void ItemUseOutOfBattle_CannotUse(u8 taskId)
{
    DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].data[2]);
}
