#include "global.h"
#include "shop.h"
#include "decompress.h"
#include "field_fadetransition.h"
#include "field_weather.h"
#include "item_menu.h"
#include "main.h"
#include "menu.h"
#include "menu_helpers.h"
#include "money.h"
#include "palette.h"
#include "script.h"
#include "sound.h"
#include "sprite.h"
#include "strings.h"
#include "task.h"
#include "tv.h"
#include "scanline_effect.h"
#include "event_object_movement.h"
#include "field_player_avatar.h"
#include "fieldmap.h"
#include "item.h"
#include "decoration.h"
#include "constants/items.h"
#include "constants/songs.h"
#include "overworld.h"
#include "decoration_inventory.h"
#include "field_camera.h"
#include "ewram.h"

extern bool8 SellMenu_QuantityRoller(u8, u8);

extern u8 gBuyMenuFrame_Gfx[];
extern u16 gBuyMenuFrame_Tilemap[];
extern u16 gMenuMoneyPal[16];

static void Shop_DisplayPriceInList(int firstItemId, int lastItemId, bool32 hasControlCode);
static void Shop_PrintItemDescText(void);
static void Task_ReturnToBuyMenu(u8);
static void Task_ExitBuyMenu(u8);
static void Task_ExitBuyMenuDoFade(u8);
static void Task_UpdatePurchaseHistory(u8);
static void Task_HandleShopMenuBuy(u8 taskId);
static void Task_HandleShopMenuSell(u8 taskId);
static void Task_HandleShopMenuQuit(u8 taskId);
static void Task_DoItemPurchase(u8 taskId);
static void Task_CancelItemPurchase(u8 taskId);
static void Task_DoBuySellMenu(u8);
static void Shop_FadeAndRunBuySellCallback(u8);
static void BuyMenuDrawGraphics(void);
static void sub_80B3240(void);
static void DrawFirstMartScrollIndicators(void);
static void Shop_DrawViewport(void);
static void Shop_InitMenus(int, int);
static void Shop_PrintItemDesc(void);
static void Shop_DoCursorAction(u8);
static void Shop_LoadViewportObjects(void);
static void Shop_AnimViewportObjects(void);

// iwram
static struct MartInfo gMartInfo;

// ewram
EWRAM_DATA u32 gMartTotalCost = 0;
EWRAM_DATA s16 gMartViewportObjects[16][4] = {0};
EWRAM_DATA struct ItemSlot gMartPurchaseHistory[3] = {0};
EWRAM_DATA u8 gMartPurchaseHistoryId = 0;

EWRAM_DATA u8 gUnknown_02038731 = 0; // This really should be in fldeff_escalator, but being in a new file aligns the ewram, which doesnt match the ROM.

// rodata
static const struct MenuAction2 sBuySellQuitMenuActions[] =
{
    { MartText_Buy, Task_HandleShopMenuBuy },
    { MartText_Sell, Task_HandleShopMenuSell },
    { MartText_Quit2, Task_HandleShopMenuQuit },
};

static const u8 gMartBuySellOptionList[] = {SHOP_BUY, SHOP_SELL, SHOP_EXIT};
static const u8 gMartBuyNoSellOptionList[] = {SHOP_BUY, SHOP_EXIT};

static const u16 gUnusedMartArray[] = {0x2, 0x3, 0x4, 0xD, 0x121, 0xE, 0xE, 0xE, 0xE, 0xE, 0xE, 0x0, 0x0};

static const struct YesNoFuncTable sShopPurchaseYesNoFuncs[] =
{
    Task_DoItemPurchase,
    Task_CancelItemPurchase
};

static u8 CreateShopMenu(u8 martType)
{
    ScriptContext2_Enable();
    gMartInfo.martType = martType;
    gMartInfo.cursor = 0;

    if (martType == MART_TYPE_0)
    {
        gMartInfo.numChoices = 2;
        Menu_DrawStdWindowFrame(0, 0, 10, 7);
        Menu_PrintItemsReordered(1, 1, 3, sBuySellQuitMenuActions, gMartBuySellOptionList);
    }
    else
    {
        gMartInfo.numChoices = 1;
        Menu_DrawStdWindowFrame(0, 0, 10, 5);
        Menu_PrintItemsReordered(1, 1, 2, sBuySellQuitMenuActions, gMartBuyNoSellOptionList);
    }
    InitMenu(0, 1, 1, gMartInfo.numChoices + 1, 0, 9); // add 1 for cancel

    return CreateTask(Task_DoBuySellMenu, 8);
}

static void SetShopMenuCallback(void *callbackPtr)
{
    gMartInfo.callback = callbackPtr;
}

static void SetShopItemsForSale(const u16 *items)
{
    u16 i = 0;

    gMartInfo.itemList = items;
    gMartInfo.itemCount = 0;

    while (gMartInfo.itemList[i])
    {
        gMartInfo.itemCount++;
        i++;
    }
}

static void Task_DoBuySellMenu(u8 taskId)
{
    const u8 taskIdConst = taskId; // why is a local const needed to match?

    if (gMain.newAndRepeatedKeys & DPAD_UP)
    {
        if (gMartInfo.cursor) // can move cursor up?
        {
            PlaySE(SE_SELECT);
            gMartInfo.cursor = Menu_MoveCursor(-1);
        }
    }
    else if (gMain.newAndRepeatedKeys & DPAD_DOWN)
    {
        if (gMartInfo.cursor != gMartInfo.numChoices) // can move cursor down?
        {
            PlaySE(SE_SELECT);
            gMartInfo.cursor = Menu_MoveCursor(1);
        }
    }
    else if (gMain.newKeys & A_BUTTON)
    {
        PlaySE(SE_SELECT);
        if (gMartInfo.martType == MART_TYPE_0)
            sBuySellQuitMenuActions[gMartBuySellOptionList[gMartInfo.cursor]].func(taskIdConst);
        else
            sBuySellQuitMenuActions[gMartBuyNoSellOptionList[gMartInfo.cursor]].func(taskIdConst);
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        PlaySE(SE_SELECT);
        Task_HandleShopMenuQuit(taskIdConst);
    }
}

static void Task_HandleShopMenuBuy(u8 taskId)
{
    gTasks[taskId].data[8] = (u32)BuyMenuDrawGraphics >> 16;
    gTasks[taskId].data[9] = (u32)BuyMenuDrawGraphics;
    gTasks[taskId].func = Shop_FadeAndRunBuySellCallback;
    FadeScreen(1, 0);
}

static void Task_HandleShopMenuSell(u8 taskId)
{
    gTasks[taskId].data[8] = (u32)ItemMenu_LoadSellMenu >> 16;
    gTasks[taskId].data[9] = (u32)ItemMenu_LoadSellMenu;
    gTasks[taskId].func = Shop_FadeAndRunBuySellCallback;
    FadeScreen(1, 0);
}

static void Task_HandleShopMenuQuit(u8 taskId)
{
    Menu_DestroyCursor();
    Menu_EraseWindowRect(0, 0, 11, 8);
    sub_80BE3BC();
    ScriptContext2_Disable();
    DestroyTask(taskId);

    if (gMartInfo.callback)
        gMartInfo.callback(); // run the callback if it exists.
}

static void Shop_FadeAndRunBuySellCallback(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        SetMainCallback2((void *)((u16)gTasks[taskId].data[8] << 16 | (u16)gTasks[taskId].data[9]));
        DestroyTask(taskId);
    }
}

static void ReturnToShopMenuAfterExitingSellMenu(u8 taskId)
{
    CreateShopMenu(gMartInfo.martType);
    DestroyTask(taskId);
}

static void Task_ReturnToMartMenu(u8 taskId)
{
    if (IsWeatherNotFadingIn() == 1)
    {
        if (gMartInfo.martType == MART_TYPE_2)
            DisplayItemMessageOnField(taskId, gOtherText_CanIHelpYou, ReturnToShopMenuAfterExitingSellMenu, 0);
        else
            DisplayItemMessageOnField(taskId, gOtherText_AnythingElse, ReturnToShopMenuAfterExitingSellMenu, 0);
    }
}

void Shop_FadeReturnToMartMenu(void)
{
    pal_fill_black();
    CreateTask(Task_ReturnToMartMenu, 0x8);
}

void Shop_RunExitSellMenuTask(u8 taskId)
{
    Task_ReturnToMartMenu(taskId);
}

// unused
void Shop_LoadExitSellMenuTask(u8 taskId)
{
    gTasks[taskId].func = Task_ReturnToMartMenu;
}

static void MainCB2(void)
{
    AnimateSprites();
    BuildOamBuffer();
    RunTasks();
    UpdatePaletteFade();
}

static void VBlankCB(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
    DmaCopy16Defvars(3, gBGTilemapBuffers[1], (void *)(VRAM + 0xE800), 0x800);
    DmaCopy16Defvars(3, gBGTilemapBuffers[2], (void *)(VRAM + 0xE000), 0x800);
    DmaCopy16Defvars(3, gBGTilemapBuffers[3], (void *)(VRAM + 0xF000), 0x800);
}

static void BuyMenuDrawGraphics(void)
{
    ClearVideoCallbacks();
    ScanlineEffect_Stop();
    REG_BG1HOFS = 0;
    REG_BG1VOFS = 0;
    REG_BG2HOFS = 0;
    REG_BG2VOFS = 0;
    REG_BG3HOFS = 0;
    REG_BG3VOFS = 0;
    gPaletteFade.bufferTransferDisabled = 1;

    Dma3FillLarge32_(0, (void*)OAM, OAM_SIZE);
    LZDecompressVram(gBuyMenuFrame_Gfx, (void*)(VRAM + 0x7C00));
    LZDecompressWram(gBuyMenuFrame_Tilemap, ewram18000);
    LoadCompressedPalette(gMenuMoneyPal, 0xC0, sizeof(gMenuMoneyPal));
    FreeAllSpritePalettes();
    ResetPaletteFade();
    ResetSpriteData();
    ResetTasks();
    Text_LoadWindowTemplate(&gWindowTemplate_81E6DFC);
    InitMenuWindow(&gWindowTemplate_81E6DFC);
    Shop_DrawViewport();
    gMartInfo.cursor = 0;
    gMartInfo.choicesAbove = 0;
    Menu_EraseWindowRect(0, 0, 0x20, 0x20);
    OpenMoneyWindow(gSaveBlock1.money, 0, 0);
    Shop_InitMenus(0, 7);
    Shop_PrintItemDesc();
    DrawFirstMartScrollIndicators();
    CreateTask(Shop_DoCursorAction, 0x8);
    sub_80B3240();
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, RGB(0, 0, 0));
    gPaletteFade.bufferTransferDisabled = 0;
    SetVBlankCallback(VBlankCB);
    SetMainCallback2(MainCB2);
}

static void sub_80B3240(void)
{
    u16 colors[2] = {RGB(14, 15, 16), RGB_WHITE};

    LoadPalette(&colors[1], 0xD1, sizeof colors[1]);
    LoadPalette(&colors[0], 0xD8, sizeof colors[0]);
}

static void DrawFirstMartScrollIndicators(void)
{
    ClearVerticalScrollIndicatorPalettes();

    if (gMartInfo.itemCount >= 8)
    {
        CreateVerticalScrollIndicators(TOP_ARROW, 172, 12);
        CreateVerticalScrollIndicators(BOTTOM_ARROW, 172, 148);
        SetVerticalScrollIndicators(TOP_ARROW, INVISIBLE);
    }
}

static void Shop_TryDrawVerticalScrollIndicators(void)
{
    if (gMartInfo.choicesAbove == 0)
        SetVerticalScrollIndicators(TOP_ARROW, INVISIBLE);
    else
        SetVerticalScrollIndicators(TOP_ARROW, VISIBLE);

    if (gMartInfo.choicesAbove + 7 >= gMartInfo.itemCount)
        SetVerticalScrollIndicators(BOTTOM_ARROW, INVISIBLE);
    else
        SetVerticalScrollIndicators(BOTTOM_ARROW, VISIBLE);
}

// what is the point of this function? the tiles always get overwritten by BuyMenuDrawTextboxBG.
static void BuyMenuDrawTextboxBG_Old(u16 *array, s16 offset1, s16 offset2)
{
    array[offset1 + offset2] = 0xC3E1;
    array[offset1 + offset2 + 1] = 0xC3E1;
}

static void BuyMenuDrawMapMetatileLayer(u16 *array, s16 offset1, s16 offset2, u16 *array2)
{
    // This function draws a whole 2x2 metatile.
    array[offset1 + offset2] = array2[0]; // top left
    array[offset1 + offset2 + 1] = array2[1]; // top right
    array[offset1 + offset2 + 32] = array2[2]; // bottom left
    array[offset1 + offset2 + 33] = array2[3]; // bottom right
}

static void BuyMenuDrawMapMetatile(s16 var1, s16 var2, u16 *var3, u8 var4)
{
    var1 <<= 1;
    var2 = (var2 <<6) + 0x40;

    switch (var4)
    {
    case 0:
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], var1, var2, var3);
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[1], var1, var2, var3 + 4);
        break;
    case 1:
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], var1, var2, var3);
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], var1, var2, var3 + 4);
        break;
    case 2:
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], var1, var2, var3);
        BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[1], var1, var2, var3 + 4);
        break;
    }
}

// used to draw the border tiles around the viewport.
static void BuyMenuDrawMapPartialMetatile(s16 var1, s16 var2, u16 *var3)
{
    var1 <<=1;
    var2 = (var2 << 6) + 0x40;

    BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[3], var1, var2, var3);
    BuyMenuDrawMapMetatileLayer(gBGTilemapBuffers[2], var1, var2, var3 + 4);
}

static void Shop_DrawViewportTiles(void)
{
    s16 facingX, facingY;
    s16 x, y;

    GetXYCoordsOneStepInFrontOfPlayer(&facingX, &facingY);
    facingX -= 3;
    facingY -= 3;

    for (y = 0; y < 6; y++)
    {
        for (x = 0; x < 7; x++)
        {
            u16 metatileId = MapGridGetMetatileIdAt(facingX + x, facingY + y);

            if (y != 5 && x != 6)
            {
                u8 r3 = MapGridGetMetatileLayerTypeAt(facingX + x, facingY + y);

                if (metatileId < 512)
                    BuyMenuDrawMapMetatile(x, y, (u16 *)gMapHeader.mapLayout->primaryTileset->metatiles + metatileId * 8, r3);
                else
                    BuyMenuDrawMapMetatile(x, y, (u16 *)gMapHeader.mapLayout->secondaryTileset->metatiles + (metatileId - 512) * 8, r3);
            }
            else
            {
                if (metatileId < 512)
                    BuyMenuDrawMapPartialMetatile(x, y, (u16 *)gMapHeader.mapLayout->primaryTileset->metatiles + metatileId * 8);
                else
                    BuyMenuDrawMapPartialMetatile(x, y, (u16 *)gMapHeader.mapLayout->secondaryTileset->metatiles + (metatileId - 512) * 8);
            }

            if (y == 0 && x != 0 && x != 6)
                BuyMenuDrawTextboxBG_Old(gBGTilemapBuffers[1], x * 2, 64);
        }
    }
}

static void Shop_DrawViewport(void)
{
    ClearBGTilemapBuffers();
    Shop_LoadViewportObjects();
    Shop_AnimViewportObjects();
    Shop_DrawViewportTiles();
}

static void Shop_LoadViewportObjects(void)
{
    s16 facingX, facingY;
    u8 playerHeight;
    u8 y, x;
    u8 r8 = 0;

    GetXYCoordsOneStepInFrontOfPlayer(&facingX, &facingY);
    playerHeight = PlayerGetZCoord();
    for (y = 0; y < 16; y++)
        gMartViewportObjects[y][OBJ_EVENT_ID] = 16;
    for (y = 0; y < 5; y++)
    {
        for (x = 0; x < 7; x++)
        {
            u8 objEventId = GetObjectEventIdByXYZ(facingX - 3 + x, facingY - 2 + y, playerHeight);

            if (objEventId != 16)
            {
                gMartViewportObjects[r8][OBJ_EVENT_ID] = objEventId;
                gMartViewportObjects[r8][X_COORD] = x;
                gMartViewportObjects[r8][Y_COORD] = y;
                if (gObjectEvents[objEventId].facingDirection == DIR_SOUTH)
                    gMartViewportObjects[r8][ANIM_NUM] = 0;
                if (gObjectEvents[objEventId].facingDirection == DIR_NORTH)
                    gMartViewportObjects[r8][ANIM_NUM] = 1;
                if (gObjectEvents[objEventId].facingDirection == DIR_WEST)
                    gMartViewportObjects[r8][ANIM_NUM] = 2;
                if (gObjectEvents[objEventId].facingDirection == DIR_EAST)
                    gMartViewportObjects[r8][ANIM_NUM] = 3;
                r8++;
            }
        }
    }
}

static void Shop_AnimViewportObjects(void)
{
    u8 i;

    for (i = 0; i < 16; i++) // max objects?
    {
        if (gMartViewportObjects[i][OBJ_EVENT_ID] == 16)
            continue;

        StartSpriteAnim(&gSprites[AddPseudoObjectEvent(
            gObjectEvents[gMartViewportObjects[i][OBJ_EVENT_ID]].graphicsId,
            SpriteCallbackDummy,
            (u16)gMartViewportObjects[i][X_COORD] * 16 + 8,
            (u16)gMartViewportObjects[i][Y_COORD] * 16 + 32,
            2)],
            gMartViewportObjects[i][ANIM_NUM]);
    }
}

static void BuyMenuDrawTextboxBG(void)
{
    s16 i;
    
    for (i = 0; i < 0x400; i++)
    {
        if (ewram18000[i] != 0)
            gBGTilemapBuffers[1][i] = ewram18000[i] + 0xC3E0;
    }
}

static void Shop_InitMenus(int firstItemId, int lastItemId)
{
    BuyMenuDrawTextboxBG();
    Shop_DisplayPriceInList(firstItemId, lastItemId, 0);
    InitMenu(0, 0xE, 0x2, 0x8, gMartInfo.cursor, 0xF);
}

// after printing the item quantity and price, restore the textbox tiles before the Yes/No prompt.
static void BuyMenuDrawTextboxBG_Restore(void)
{
    u16 i, j;

    for (i = 0; i < 8; i++)
        for (j = 0; j < 14; j++)
            gBGTilemapBuffers[1][32 * (i + 12) + j] = ewram18000[0x180 + 32 * i + j] + 0xC3E0;
}

static void Shop_PrintItemDesc(void)
{
    Shop_PrintItemDescText();
}

#define tItemCount data[1]

static void Shop_DisplayPriceInCheckoutWindow(u8 taskId)
{
    u16 itemListIndex = gMartInfo.choicesAbove + gMartInfo.cursor;
    u16 itemId = gMartInfo.itemList[itemListIndex];
    u32 price = (ItemId_GetPrice(itemId) >> GetPriceReduction(1));

    PrintMoneyAmount(gTasks[taskId].tItemCount * price, 6, 6, 11);
    gStringVar1[0] = EXT_CTRL_CODE_BEGIN;
    gStringVar1[1] = 0x14;
    gStringVar1[2] = 0x6;
    ConvertIntToDecimalStringN(&gStringVar1[3], gTasks[taskId].tItemCount, 1, 2);
    Menu_PrintText(gOtherText_xString1, 1, 11);
    sub_80A3FA0(gBGTilemapBuffers[1], 1, 11, 12, 2, 0xC3E1);
}

static void Shop_DisplayNormalPriceInList(u16 itemId, u8 var2, bool32 hasControlCode)
{
    u8 *stringPtr = gStringVar1;

    if (hasControlCode)
    {
        stringPtr[0] = EXT_CTRL_CODE_BEGIN;
        stringPtr[1] = 0x1;
        stringPtr[2] = 0x2;
        stringPtr += 3;
    }

    CopyItemName(itemId, stringPtr);

    sub_8072A18(&gStringVar1[0], 0x70, var2 << 3, 0x58, 0x1);
    stringPtr = gStringVar1;

    if (hasControlCode)
        stringPtr = &gStringVar1[3];

    GetMoneyAmountText(stringPtr, (ItemId_GetPrice(itemId) >> GetPriceReduction(1)), 0x4);
    Menu_PrintTextPixelCoords(&gStringVar1[0], 0xCA, var2 << 3, 1);
}

static void Shop_DisplayDecorationPriceInList(u16 itemId, u8 var2, bool32 hasControlCode)
{
    u8 *stringPtr = gStringVar1;

    if (hasControlCode)
    {
        stringPtr[0] = EXT_CTRL_CODE_BEGIN;
        stringPtr[1] = 0x1;
        stringPtr[2] = 0x2;
        stringPtr += 3;
    }

    StringCopy(stringPtr, gDecorations[itemId].name);
    sub_8072A18(&gStringVar1[0], 0x70, var2 << 3, 0x58, 0x1);
    stringPtr = gStringVar1;

    if (hasControlCode)
        stringPtr = &gStringVar1[3];

    // some names are the maximum string length for a shop item. Because there is no room for
    // a 6 character price (including the currency), a sprite is instead used for anything that
    // is the maximum decoration price in order to fit it on screen.
    if (gDecorations[itemId].price == 10000)
    {
        Draw10000Sprite(0x19, var2, hasControlCode);
    }
    else
    {
        GetMoneyAmountText(stringPtr, gDecorations[itemId].price, 0x4);
        Menu_PrintTextPixelCoords(&gStringVar1[0], 0xCA, var2 << 3, 0x1);
    }
}

static void Shop_DisplayPriceInList(int firstItemId, int lastItemId, bool32 hasControlCode)
{
    u8 i;

    for (i = firstItemId; i <= lastItemId && gMartInfo.choicesAbove + i < gMartInfo.itemCount; i++)
    {
        if (gMartInfo.martType == MART_TYPE_0)
            Shop_DisplayNormalPriceInList(gMartInfo.itemList[gMartInfo.choicesAbove + i], (i << 1) + 2, hasControlCode);
        else
            Shop_DisplayDecorationPriceInList(gMartInfo.itemList[gMartInfo.choicesAbove + i], (i << 1) + 2, hasControlCode);
    }

    if (i != 8 && gMartInfo.choicesAbove + i == gMartInfo.itemCount)
    {
        Menu_BlankWindowRect(0xE, (i << 1) + 2, 0x1C, (i << 1) + 3);
        Menu_PrintText(gOtherText_CancelNoTerminator, 0xE, (i << 1) + 2);
    }
}

static void Shop_PrintItemDescText(void)
{
    if (gMartInfo.choicesAbove + gMartInfo.cursor != gMartInfo.itemCount)
    {
        if (gMartInfo.martType == MART_TYPE_0)
        {
            sub_8072AB0(ItemId_GetDescription(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]),
                0x4, 0x68, 0x68, 0x30, 0);
        }
        else
        {
            sub_8072AB0(gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].description,
                0x4, 0x68, 0x68, 0x30, 0);
        }
    }
    else
    {
        sub_8072AB0(gOtherText_QuitShopping, 0x4, 0x68, 0x68, 0x30, 0);
    }
}

static void Shop_DoPremierBallCheck(u8 taskId)
{
    if (gMain.newKeys & A_BUTTON || gMain.newKeys & B_BUTTON)
    {
        Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0);
        PlaySE(SE_SELECT);

        if (gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor] == ITEM_POKE_BALL && gTasks[taskId].tItemCount >= 10 && AddBagItem(ITEM_PREMIER_BALL, 1) == TRUE)
            DisplayItemMessageOnField(taskId, gOtherText_FreePremierBall, Task_ReturnToBuyMenu, 0xC3E1);
        else
            Task_ReturnToBuyMenu(taskId);
    }
}

static void Shop_DoItemTransaction(u8 taskId)
{
    IncrementGameStat(GAME_STAT_SHOPPED);
    RemoveMoney(&gSaveBlock1.money, gMartTotalCost);
    PlaySE(SE_SHOP);
    UpdateMoneyWindow(gSaveBlock1.money, 0, 0);
    gTasks[taskId].func = Shop_DoPremierBallCheck;
}

static void Shop_DoPricePrintAndReturnToBuyMenu(u8 taskId)
{
    Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0);
    Task_ReturnToBuyMenu(taskId);
}

static void Task_DoItemPurchase(u8 taskId)
{
    Menu_EraseWindowRect(0x7, 0x8, 0xD, 0xD);
    sub_80A3FA0(gBGTilemapBuffers[1], 8, 9, 4, 4, 0);
    BuyMenuDrawTextboxBG_Restore();
    Shop_DrawViewportTiles();

    if (IsEnoughMoney(gSaveBlock1.money, gMartTotalCost))
    {
        if (gMartInfo.martType == MART_TYPE_0)
        {
            if (AddBagItem(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gTasks[taskId].tItemCount))
            {
                DisplayItemMessageOnField(taskId, gOtherText_HereYouGo, Shop_DoItemTransaction, 0xC3E1);
                Task_UpdatePurchaseHistory(taskId);
            }
            else
            {
                DisplayItemMessageOnField(taskId, gOtherText_NoRoomFor, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1);
            }
        }
        else // a normal mart is only type 0, so types 1 and 2 are decoration marts.
        {
            if (AddDecoration(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]))
            {
                if (gMartInfo.martType == MART_TYPE_1)
                    DisplayItemMessageOnField(taskId, gOtherText_HereYouGo2, Shop_DoItemTransaction, 0xC3E1);
                else
                    DisplayItemMessageOnField(taskId, gOtherText_HereYouGo3, Shop_DoItemTransaction, 0xC3E1);
            }
            else
            {
                StringExpandPlaceholders(gStringVar4, gOtherText_SpaceForIsFull);
                DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1);
            }
        }
    }
    else
    {
        DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1);
    }
}

static void Shop_DoYesNoPurchase(u8 taskId)
{
    DisplayYesNoMenu(7, 8, 1);
    sub_80A3FA0(gBGTilemapBuffers[1], 8, 9, 4, 4, 0xC3E1);
    DoYesNoFuncWithChoice(taskId, sShopPurchaseYesNoFuncs);
}

static void Task_CancelItemPurchase(u8 taskId)
{
    Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0);
    Menu_EraseWindowRect(0x7, 0x8, 0xD, 0xD);
    sub_80A3FA0(gBGTilemapBuffers[1], 0x8, 0x9, 0x4, 0x4, 0);
    Task_ReturnToBuyMenu(taskId);
}

static void Shop_PrintPrice(u8 taskId)
{
    if (SellMenu_QuantityRoller(taskId, gMartInfo.curItemCount) == TRUE)
        Shop_DisplayPriceInCheckoutWindow(taskId);

    if (gMain.newKeys & A_BUTTON)
    {
        gMartTotalCost = (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1)) * gTasks[taskId].tItemCount; // set total cost of your purchase.
        Menu_EraseWindowRect(0, 0xA, 0xD, 0xD);
        sub_80A3FA0(gBGTilemapBuffers[1], 0x1, 0xB, 0xC, 0x2, 0);
        BuyMenuDrawTextboxBG_Restore();
        Shop_DrawViewportTiles();
        CopyItemName(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gStringVar1);
        ConvertIntToDecimalStringN(gStringVar2, gTasks[taskId].tItemCount, 0, 0x2);
        ConvertIntToDecimalStringN(gStringVar3, gMartTotalCost, 0, 0x8);
        StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe);
        DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoYesNoPurchase, 0xC3E1);
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 0);
        Task_ReturnToBuyMenu(taskId);
    }
}

// set the item count in the mart info to the maximum allowed by the player's budget.
static void Shop_UpdateCurItemCountToMax(u8 taskId)
{
    u16 var;

    gTasks[taskId].tItemCount = 1;
    Menu_DrawStdWindowFrame(0, 0xA, 0xD, 0xD);
    Shop_DisplayPriceInCheckoutWindow(taskId);

    var = gSaveBlock1.money / (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1));
    if (var > 99)
        gMartInfo.curItemCount = 99;
    else
        gMartInfo.curItemCount = var;

    gTasks[taskId].func = Shop_PrintPrice;
}

#ifdef NONMATCHING
static void Shop_MoveItemListUp(void)
{
    u16 *r1;
    u16 *r2;
    register u8 *r10 asm("r10");
    s32 i;
    s32 j;
    struct Window *r8 = &gMenuWindow;
    
    r1 = r8->tilemap;
    r1 += 0x1EF;
    r2 = r1;
    r2 += 64;
    r10 = r8->tileData;
    
    for (i = 0; i < 14; i++)
    {
        for (j = 0; j < 15; j++)
        {
            if ((r1[j] & 0x3FF) <= r8->tileDataStartOffset + 1)
                r2[j] = r8->tileDataStartOffset + 1;
            else
                r2[j] = r1[j] + 0x3C;
        }
        
        r1 -= 32;
        r2 -= 32;
    }
    
    {
        u8 *r1 = r10 + 0x3A20;
        u8 *r2 = r1 + 0x780;
        for (i = 0; i < 14; i++)
        {
            DmaCopy16(3, r1, r2, 0x1E0);
            r2 -= 0x3C0;
            r1 -= 0x3C0;
        }
    }
}
#else
NAKED
static void Shop_MoveItemListUp(void)
{
    asm(".syntax unified\n\
    push {r4-r7,lr}\n\
    mov r7, r10\n\
    mov r6, r9\n\
    mov r5, r8\n\
    push {r5-r7}\n\
    sub sp, 0x4\n\
    ldr r0, _080B4020 @ =gMenuWindow\n\
    mov r8, r0\n\
    ldr r1, [r0, 0x28]\n\
    ldr r3, _080B4024 @ =0x000003de\n\
    adds r1, r3\n\
    adds r2, r1, 0\n\
    adds r2, 0x80\n\
    ldr r7, [r0, 0x24]\n\
    mov r10, r7\n\
    ldr r0, _080B4028 @ =0x000003ff\n\
    mov r9, r0\n\
    movs r6, 0xD\n\
_080B3FAC:\n\
    adds r3, r2, 0\n\
    subs r3, 0x40\n\
    str r3, [sp]\n\
    movs r7, 0x40\n\
    negs r7, r7\n\
    adds r7, r1\n\
    mov r12, r7\n\
    adds r3, r2, 0\n\
    adds r4, r1, 0\n\
    movs r5, 0xE\n\
_080B3FC0:\n\
    ldrh r2, [r4]\n\
    mov r1, r9\n\
    ands r1, r2\n\
    mov r7, r8\n\
    ldrh r0, [r7, 0x1A]\n\
    adds r0, 0x1\n\
    cmp r1, r0\n\
    ble _080B3FD4\n\
    adds r0, r2, 0\n\
    adds r0, 0x3C\n\
_080B3FD4:\n\
    strh r0, [r3]\n\
    adds r3, 0x2\n\
    adds r4, 0x2\n\
    subs r5, 0x1\n\
    cmp r5, 0\n\
    bge _080B3FC0\n\
    ldr r2, [sp]\n\
    mov r1, r12\n\
    subs r6, 0x1\n\
    cmp r6, 0\n\
    bge _080B3FAC\n\
    ldr r1, _080B402C @ =0x00003a20\n\
    add r1, r10\n\
    movs r0, 0xF0\n\
    lsls r0, 3\n\
    adds r2, r1, r0\n\
    ldr r3, _080B4030 @ =0x040000d4\n\
    ldr r5, _080B4034 @ =0x800000f0\n\
    ldr r4, _080B4038 @ =0xfffffc40\n\
    movs r6, 0xD\n\
_080B3FFC:\n\
    str r1, [r3]\n\
    str r2, [r3, 0x4]\n\
    str r5, [r3, 0x8]\n\
    ldr r0, [r3, 0x8]\n\
    adds r2, r4\n\
    adds r1, r4\n\
    subs r6, 0x1\n\
    cmp r6, 0\n\
    bge _080B3FFC\n\
    add sp, 0x4\n\
    pop {r3-r5}\n\
    mov r8, r3\n\
    mov r9, r4\n\
    mov r10, r5\n\
    pop {r4-r7}\n\
    pop {r0}\n\
    bx r0\n\
    .align 2, 0\n\
_080B4020: .4byte gMenuWindow\n\
_080B4024: .4byte 0x000003de\n\
_080B4028: .4byte 0x000003ff\n\
_080B402C: .4byte 0x00003a20\n\
_080B4030: .4byte 0x040000d4\n\
_080B4034: .4byte 0x800000f0\n\
_080B4038: .4byte 0xfffffc40\n\
    .syntax divided");
}
#endif

#ifdef NONMATCHING
static void Shop_MoveItemListDown(void)
{
    u16 *r1;
    u16 *r2;
    u8 *r10;
    s32 i;
    s32 j;
    struct Window *r8 = &gMenuWindow;
    
    r1 = r8->tilemap;
    r1 += 0x4F;
    r2 = r1;
    r2 += 64;
    r10 = r8->tileData;
    
    for (i = 0; i < 14; i++)
    {
        for (j = 0; j < 15; j++)
        {
            if ((r1[j] & 0x3FF) <= r8->tileDataStartOffset + 1)
                r2[j] = r8->tileDataStartOffset + 1;
            else
                r2[j] = r1[j] + 0x3C;
        }
        
        r1 += 32;
        r2 += 32;
    }

    {
        register u8 *r1 asm("r1") = r10 + 0x960;
        register u8 *r2 asm("r2") = r1;
       
        r1 += 0x780;
        for (i = 0; i < 14; i++)
        {
            DmaCopy16(3, r1, r2, 0x1E0);
            r1 += 0x3C0;
            r2 += 0x3C0;
        }
    }
}
#else
NAKED
static void Shop_MoveItemListDown(void)
{
    asm(".syntax unified\n\
    push {r4-r7,lr}\n\
    mov r7, r10\n\
    mov r6, r9\n\
    mov r5, r8\n\
    push {r5-r7}\n\
    sub sp, 0x4\n\
    ldr r0, _080B40D8 @ =gMenuWindow\n\
    mov r8, r0\n\
    ldr r2, [r0, 0x28]\n\
    adds r1, r2, 0\n\
    adds r1, 0x9E\n\
    adds r2, r1, 0\n\
    adds r1, 0x80\n\
    ldr r3, [r0, 0x24]\n\
    mov r10, r3\n\
    ldr r7, _080B40DC @ =0x000003ff\n\
    mov r9, r7\n\
    movs r6, 0xD\n\
_080B4060:\n\
    adds r0, r2, 0\n\
    adds r0, 0x40\n\
    str r0, [sp]\n\
    movs r3, 0x40\n\
    adds r3, r1\n\
    mov r12, r3\n\
    adds r3, r2, 0\n\
    adds r4, r1, 0\n\
    movs r5, 0xE\n\
_080B4072:\n\
    ldrh r2, [r4]\n\
    mov r1, r9\n\
    ands r1, r2\n\
    mov r7, r8\n\
    ldrh r0, [r7, 0x1A]\n\
    adds r0, 0x1\n\
    cmp r1, r0\n\
    ble _080B4086\n\
    adds r0, r2, 0\n\
    subs r0, 0x3C\n\
_080B4086:\n\
    strh r0, [r3]\n\
    adds r3, 0x2\n\
    adds r4, 0x2\n\
    subs r5, 0x1\n\
    cmp r5, 0\n\
    bge _080B4072\n\
    ldr r2, [sp]\n\
    mov r1, r12\n\
    subs r6, 0x1\n\
    cmp r6, 0\n\
    bge _080B4060\n\
    movs r1, 0x96\n\
    lsls r1, 4\n\
    add r1, r10\n\
    adds r2, r1, 0\n\
    movs r0, 0xF0\n\
    lsls r0, 3\n\
    adds r1, r0\n\
    ldr r3, _080B40E0 @ =0x040000d4\n\
    ldr r5, _080B40E4 @ =0x800000f0\n\
    movs r4, 0xF0\n\
    lsls r4, 2\n\
    movs r6, 0xD\n\
_080B40B4:\n\
    str r1, [r3]\n\
    str r2, [r3, 0x4]\n\
    str r5, [r3, 0x8]\n\
    ldr r0, [r3, 0x8]\n\
    adds r2, r4\n\
    adds r1, r4\n\
    subs r6, 0x1\n\
    cmp r6, 0\n\
    bge _080B40B4\n\
    add sp, 0x4\n\
    pop {r3-r5}\n\
    mov r8, r3\n\
    mov r9, r4\n\
    mov r10, r5\n\
    pop {r4-r7}\n\
    pop {r0}\n\
    bx r0\n\
    .align 2, 0\n\
_080B40D8: .4byte gMenuWindow\n\
_080B40DC: .4byte 0x000003ff\n\
_080B40E0: .4byte 0x040000d4\n\
_080B40E4: .4byte 0x800000f0\n\
    .syntax divided");
}
#endif

static void Shop_DoCursorAction(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        if ((gMain.newAndRepeatedKeys & DPAD_ANY) == DPAD_UP) // only up can be pressed
        {
            if (gMartInfo.cursor == 0)
            {
                if (gMartInfo.choicesAbove == 0) // if there are no choices above, dont bother
                    return;

                PlaySE(SE_SELECT);
                gMartInfo.choicesAbove--; // since cursor is at the top and there are choices above the top, scroll the menu up by updating choicesAbove.
                Shop_MoveItemListUp();
                Shop_DisplayPriceInList(0, 0, 0);
                Shop_PrintItemDescText();
                Shop_TryDrawVerticalScrollIndicators();
            }
            else // if the cursor is not 0, choicesAbove cannot be updated yet since the cursor is at the top of the menu, so update cursor.
            {
                PlaySE(SE_SELECT);
                gMartInfo.cursor = Menu_MoveCursor(-1); // move cursor up
                Shop_PrintItemDescText();
            }
        }
        else if ((gMain.newAndRepeatedKeys & DPAD_ANY) == DPAD_DOWN) // only down can be pressed
        {
            if (gMartInfo.cursor == 7) // are you at the bottom of the menu?
            {
                if (gMartInfo.choicesAbove + gMartInfo.cursor == gMartInfo.itemCount) // are you at cancel?
                    return;

                PlaySE(SE_SELECT);
                gMartInfo.choicesAbove++;
                Shop_MoveItemListDown();
                Shop_DisplayPriceInList(7, 7, 0);
                Shop_PrintItemDescText();
                Shop_TryDrawVerticalScrollIndicators();
            }
            else if (gMartInfo.cursor != gMartInfo.itemCount)
            {
                PlaySE(SE_SELECT);
                gMartInfo.cursor = Menu_MoveCursor(1);
                Shop_PrintItemDescText();
            }
        }
        else if (gMain.newKeys & A_BUTTON)
        {
            PlaySE(SE_SELECT);

            if (gMartInfo.choicesAbove + gMartInfo.cursor != gMartInfo.itemCount) // did you not hit CANCEL?
            {
                PauseVerticalScrollIndicator(TOP_ARROW);
                PauseVerticalScrollIndicator(BOTTOM_ARROW);
                SetVerticalScrollIndicators(BOTTOM_ARROW, INVISIBLE);
                Shop_DisplayPriceInList(gMartInfo.cursor, gMartInfo.cursor, 1);
                Menu_DestroyCursor();
                Menu_EraseWindowRect(0, 0xC, 0xD, 0x13);

                if (gMartInfo.martType == MART_TYPE_0)
                {
                    gMartTotalCost = (ItemId_GetPrice(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]) >> GetPriceReduction(1)); // set 1x price
                    if (!IsEnoughMoney(gSaveBlock1.money, gMartTotalCost))
                    {
                        DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); // tail merge
                    }
                    else // _080B42BA
                    {
                        CopyItemName(gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor], gStringVar1);
                        StringExpandPlaceholders(gStringVar4, gOtherText_HowManyYouWant);
                        DisplayItemMessageOnField(taskId, gStringVar4, Shop_UpdateCurItemCountToMax, 0xC3E1);                    
                    }
                }
                else // _080B428C
                {
                    gMartTotalCost = gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].price;

                    if (!IsEnoughMoney(gSaveBlock1.money, gMartTotalCost))
                    {
                        DisplayItemMessageOnField(taskId, gOtherText_NotEnoughMoney, Shop_DoPricePrintAndReturnToBuyMenu, 0xC3E1); // tail merge
                    }
                    else
                    {    
                        StringCopy(gStringVar1, gDecorations[gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]].name);
                        ConvertIntToDecimalStringN(gStringVar2, gMartTotalCost, 0, 0x8);

                        if (gMartInfo.martType == MART_TYPE_1)
                            StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe2);
                        else
                            StringExpandPlaceholders(gStringVar4, gOtherText_ThatWillBe3);
                        DisplayItemMessageOnField(taskId, gStringVar4, Shop_DoYesNoPurchase, 0xC3E1);
                    }
                }
            }
            else
            {
                Task_ExitBuyMenu(taskId);
            }
        }
        else if (gMain.newKeys & B_BUTTON) // go back to buy/sell/exit menu
        {
            PlaySE(SE_SELECT);
            Task_ExitBuyMenu(taskId);
        }
    }
}

static void Task_ReturnToBuyMenu(u8 taskId)
{
    Menu_EraseWindowRect(0, 0xE, 0x1D, 0x13);
    Menu_EraseWindowRect(0, 0xA, 0xD, 0xD);
    sub_80A3FA0(gBGTilemapBuffers[1], 0x1, 0xB, 0xC, 0x2, 0);
    Shop_DrawViewportTiles();
    Shop_InitMenus(6, 7);
    Shop_PrintItemDesc();
    StartVerticalScrollIndicators(TOP_ARROW);
    StartVerticalScrollIndicators(BOTTOM_ARROW);
    Shop_TryDrawVerticalScrollIndicators();
    gTasks[taskId].func = Shop_DoCursorAction;
}

static void Task_ExitBuyMenu(u8 taskId)
{
    gFieldCallback = Shop_FadeReturnToMartMenu;
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
    gTasks[taskId].func = Task_ExitBuyMenuDoFade;
}

static void Task_ExitBuyMenuDoFade(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        CloseMoneyWindow(0, 0);
        BuyMenuFreeMemory();
        SetMainCallback2(CB2_ReturnToField);
        DestroyTask(taskId);
    }
}

// Task_UpdatePurchaseHistory
static void Task_UpdatePurchaseHistory(u8 taskId)
{
    u16 i;

    for (i = 0; i < 3; i++)
    {
        if (gMartPurchaseHistory[i].itemId == gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor]
         && gMartPurchaseHistory[i].quantity != 0)
        {
            if (gMartPurchaseHistory[i].quantity + gTasks[taskId].tItemCount > 255)
                gMartPurchaseHistory[i].quantity = 255;
            else
                gMartPurchaseHistory[i].quantity += gTasks[taskId].tItemCount;
            return;
        }
    }

    if (gMartPurchaseHistoryId < 3)
    {
        gMartPurchaseHistory[gMartPurchaseHistoryId].itemId = gMartInfo.itemList[gMartInfo.choicesAbove + gMartInfo.cursor];
        gMartPurchaseHistory[gMartPurchaseHistoryId].quantity = gTasks[taskId].tItemCount;
        gMartPurchaseHistoryId++;
    }
}

#undef tItemCount

static void ClearItemPurchases(void)
{
    gMartPurchaseHistoryId = 0;
    ClearItemSlots(gMartPurchaseHistory, 3);
}

void Shop_CreatePokemartMenu(u16 *itemList)
{
    CreateShopMenu(MART_TYPE_0);
    SetShopItemsForSale(itemList);
    ClearItemPurchases();
    SetShopMenuCallback(EnableBothScriptContexts);
}

void Shop_CreateDecorationShop1Menu(u16 *itemList)
{
    CreateShopMenu(MART_TYPE_1);
    SetShopItemsForSale(itemList);
    SetShopMenuCallback(EnableBothScriptContexts);
}

void Shop_CreateDecorationShop2Menu(u16 *itemList)
{
    CreateShopMenu(MART_TYPE_2);
    SetShopItemsForSale(itemList);
    SetShopMenuCallback(EnableBothScriptContexts);
}

#if DEBUG

void debug_sub_80C2818(void)
{
    CreateShopMenu(MART_TYPE_0);
    SetShopItemsForSale(gUnusedMartArray);
    SetShopMenuCallback(NULL);
}

#endif
