#include "global.h"
#include "gflib.h"
#include "decompress.h"
#include "task.h"
#include "coins.h"
#include "quest_log.h"
#include "overworld.h"
#include "menu.h"
#include "new_menu_helpers.h"
#include "text_window.h"
#include "random.h"
#include "trig.h"
#include "strings.h"
#include "constants/songs.h"

#define SLOT_IMAGE_7         0
#define SLOT_IMAGE_ROCKET    1
#define SLOT_IMAGE_PIKACHU   2
#define SLOT_IMAGE_PSYDUCK   3
#define SLOT_IMAGE_CHERRIES  4
#define SLOT_IMAGE_MAGNEMITE 5
#define SLOT_IMAGE_SHELLDER  6

#define SLOT_PAYOUT_NONE      0
#define SLOT_PAYOUT_CHERRIES2 1
#define SLOT_PAYOUT_CHERRIES3 2
#define SLOT_PAYOUT_MAGSHELL  3
#define SLOT_PAYOUT_PIKAPSY   4
#define SLOT_PAYOUT_ROCKET    5
#define SLOT_PAYOUT_7         6

#define ROWATTR_COL1POS 0
#define ROWATTR_COL2POS 1
#define ROWATTR_COL3POS 2
#define ROWATTR_MINBET  3

#define SLOTTASK_GFX_INIT            0
#define SLOTTASK_FADEOUT_EXIT        1
#define SLOTTASK_UPDATE_LINE_LIGHTS  2
#define SLOTTASK_CLEFAIRY_BOUNCE     3
#define SLOTTASK_ANIM_WIN            4
#define SLOTTASK_END_ANIM_WIN        5
#define SLOTTASK_ANIM_LOSE           6
#define SLOTTASK_ANIM_BETTING        7
#define SLOTTASK_SHOW_AMOUNTS        8
#define SLOTTASK_MSG_NO_COINS        9
#define SLOTTASK_ASK_QUIT           10
#define SLOTTASK_DESTROY_YESNO      11
#define SLOTTASK_PRESS_BUTTON       12
#define SLOTTASK_RELEASE_BUTTONS    13
#define SLOTTASK_SHOWHELP           14
#define SLOTTASK_HIDEHELP           15

struct SlotMachineState
{
    MainCallback savedCallback;
    u16 machineidx;
    u16 currentReel;
    u16 machineBias;
    u16 slotRewardClass;
    u16 biasCooldown;
    u16 bet;
    u8 taskId;
    u8 spinReelsTaskId;
    bool32 reelIsSpinning[3];
    s16 reelPositions[3];
    s16 reelSubpixel[3];
    s16 destReelPos[3];
    s16 reelStopOrder[3];
    u32 reel2BiasInPlay;
    bool32 winFlags[5];
    u16 payout;
};

struct SlotMachineGfxManager
{
    u32 field_00[3];
    struct Sprite * reelIconSprites[3][5];
    struct Sprite * creditDigitSprites[4];
    struct Sprite * payoutDigitSprites[4];
    struct Sprite * clefairySprites[2];
    vu16 * reelIconAffineParamPtr;
};

struct SlotMachineSetupTaskDataSub_0000
{
    u16 funcno;
    u8 state;
    bool8 active;
};

struct SlotMachineSetupTaskData
{
    struct SlotMachineSetupTaskDataSub_0000 tasks[8];
    u8 reelButtonToPress;
    // align 2
    s32 bg1X;
    bool32 yesNoMenuActive;
    u16 buttonPressedTiles[3][4];
    u16 buttonReleasedTiles[3][4];
    u8 field_005C[0x800];
    u8 bg0TilemapBuffer[0x800];
    u8 bg1TilemapBuffer[0x800];
    u8 bg2TilemapBuffer[0x800];
    u8 bg3TilemapBuffer[0x800];
}; // size: 285C

struct LineStateTileIdxList
{
    const u16 * tiles;
    u32 count;
};

static EWRAM_DATA struct SlotMachineState * sSlotMachineState = NULL;
static EWRAM_DATA struct SlotMachineGfxManager * sSlotMachineGfxManager = NULL;

static void InitSlotMachineState(struct SlotMachineState * ptr);
static void CB2_InitSlotMachine(void);
static void CleanSupSlotMachineState(void);
static void CB2_RunSlotMachine(void);
static void MainTask_SlotsGameLoop(u8 taskId);
static void MainTask_NoCoinsGameOver(u8 taskId);
static void MainTask_ShowHelp(u8 taskId);
static void MainTask_ConfirmExitGame(u8 taskId);
static void MainTask_DarnNoPayout(u8 taskId);
static void MainTask_WinHandlePayout(u8 taskId);
static void MainTask_ExitSlots(u8 taskId);
static void SetMainTask(TaskFunc taskFunc);
static void Task_SpinReels(u8 taskId);
static void StartReels(void);
static void StopCurrentReel(u16 whichReel, u16 whichReel2);
static bool32 IsReelSpinning(u16);
static void StopReel1(u16 whichReel);
static void StopReel2(u16 whichReel);
static void StopReel3(u16 whichReel);
static bool32 TwoReelBiasCheck(s32 reel0id, s32 reel0pos, s32 reel1id, s32 reel1pos, s32 icon);
static bool32 OneReelBiasCheck(s32, s32, s32);
static bool32 TestReelIconAttribute(s32 attr, s32 icon);
static void CalcSlotBias(void);
static void ResetMachineBias(void);
static u16 CalcPayout(void);
static void InitGfxManager(struct SlotMachineGfxManager * manager);
static void UpdateReelIconSprites(const s16 *, const s16 *);
static bool32 CreateSlotMachine(void);
static void DestroySlotMachine(void);
static struct SlotMachineSetupTaskData * GetSlotMachineSetupTaskDataPtr(void);
static void Task_SlotMachine(u8 taskId);
static void SetSlotMachineSetupTask(u16 funcno, u8 taskId);
static bool32 IsSlotMachineSetupTaskActive(u8 taskId);
static bool8 SlotsTask_GraphicsInit(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_FadeOut(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_UpdateLineStates(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_ClefairyUpdateOnReelsStart(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_StartClefairyDanceAndWinningLineFlash(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_StopWinningLineFlashTask(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_ClefairyFainted(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_ClefairyNeutral(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_UpdateCoinsDisplay(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_MessageOutOfCoins(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_AskQuitPlaying(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_DestroyYesNoMenu(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_PressReelButton(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_ReleaseReelButtons(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_ShowHelp(u8 *state, struct SlotMachineSetupTaskData * ptr);
static bool8 SlotsTask_HideHelp(u8 *state, struct SlotMachineSetupTaskData * ptr);
static void Slot_PrintOnWindow0(const u8 * str);
static void Slot_ClearWindow0(void);
static void SetLineStatesByBet(u16 * bgTilemapBuffer);
static void SetLineState(u16 * bgTilemapBuffer, u16 lineId, u16 paletteNo);
static void Task_FlashWinningLine(u8 taskId);
static void SignalStopWinningLineFlashTask(void);
static void Slot_CreateYesNoMenu(u8 cursorPos);
static void Slot_DestroyYesNoMenu(void);
static void InitReelButtonTileMem(void);
static void SetReelButtonPressed(u8 reel);
static void ReleaseReelButtons(void);
static void PressReelButton(u8 reel, u8 taskId);

static const u8 sSecondReelBiasCheckIndices[][2] = {
    {0x00, 0x03},
    {0x00, 0x06},
    {0x03, 0x06},

    {0x01, 0x04},
    {0x01, 0x07},
    {0x04, 0x07},

    {0x02, 0x05},
    {0x02, 0x08},
    {0x05, 0x08},

    {0x00, 0x04},
    {0x00, 0x08},
    {0x04, 0x08},

    {0x02, 0x04},
    {0x02, 0x06},
    {0x04, 0x06}
};

static const u8 sThirdReelBiasCheckIndices[][3] = {
    {0x00, 0x03, 0x06}, // top row
    {0x01, 0x04, 0x07}, // middle row
    {0x02, 0x05, 0x08}, // bottom row
    {0x00, 0x04, 0x08}, // tl-br
    {0x02, 0x04, 0x06}  // bl-tr
};

static const u8 sRowAttributes[][4] = {
    {0x00, 0x04, 0x08, 0x03}, // tl-br
    {0x00, 0x03, 0x06, 0x02}, // top row
    {0x01, 0x04, 0x07, 0x01}, // middle row
    {0x02, 0x05, 0x08, 0x02}, // bottom row
    {0x02, 0x04, 0x06, 0x03}  // bl-tr
};

static const u16 sReelBiasChances[][7] = {
    {0x1fa1, 0x2eab, 0x3630, 0x39f3, 0x3bd4, 0x3bfc, 0x0049},
    {0x1f97, 0x2ea2, 0x3627, 0x39e9, 0x3bca, 0x3bf8, 0x0049},
    {0x1f91, 0x2e9b, 0x3620, 0x39e3, 0x3bc4, 0x3bf4, 0x0049},
    {0x1f87, 0x2e92, 0x3617, 0x39d9, 0x3bba, 0x3bef, 0x0050},
    {0x1f7f, 0x2e89, 0x360e, 0x39d1, 0x3bb2, 0x3bea, 0x0050},
    {0x1fc9, 0x2efc, 0x3696, 0x3a63, 0x3c49, 0x3c8b, 0x0073},
};

static const u8 sReelIconAnimByReelAndPos[][21] = {
    {
        SLOT_IMAGE_7,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_ROCKET,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_7,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_ROCKET,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_7,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_ROCKET,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_PIKACHU
    }, {
        SLOT_IMAGE_7,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_ROCKET,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_7,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_CHERRIES,
        SLOT_IMAGE_ROCKET,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_CHERRIES
    }, {
        SLOT_IMAGE_7,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_PIKACHU,
        SLOT_IMAGE_PSYDUCK,
        SLOT_IMAGE_MAGNEMITE,
        SLOT_IMAGE_SHELLDER,
        SLOT_IMAGE_ROCKET
    },
};

static const u16 sPayoutTable[] = {
    [SLOT_PAYOUT_NONE]      =   0,
    [SLOT_PAYOUT_CHERRIES2] =   2,
    [SLOT_PAYOUT_CHERRIES3] =   6,
    [SLOT_PAYOUT_MAGSHELL]  =   8,
    [SLOT_PAYOUT_PIKAPSY]   =  15,
    [SLOT_PAYOUT_ROCKET]    = 100,
    [SLOT_PAYOUT_7]         = 300
};

static const u16 sSpritePal_ReelIcons_0[] = INCBIN_U16("graphics/slot_machine/unk_8464974.gbapal");
static const u16 sSpritePal_ReelIcons_1[] = INCBIN_U16("graphics/slot_machine/unk_8464994.gbapal");
static const u16 sSpritePal_ReelIcons_2[] = INCBIN_U16("graphics/slot_machine/unk_84649b4.gbapal");
static const u16 sSpritePal_ReelIcons_3[] = INCBIN_U16("graphics/slot_machine/unk_84649d4.gbapal");
static const u16 sSpritePal_ReelIcons_4[] = INCBIN_U16("graphics/slot_machine/unk_84649f4.gbapal");
#if defined(FIRERED)
static const u32 sSpriteTiles_ReelIcons[] = INCBIN_U32("graphics/slot_machine/unk_8464a14.4bpp.lz");
static const u16 sSpritePal_Clefairy[] = INCBIN_U16("graphics/slot_machine/unk_846506c.gbapal");
static const u32 sSpriteTiles_Clefairy[] = INCBIN_U32("graphics/slot_machine/unk_846506c.4bpp.lz");
#elif defined(LEAFGREEN)
static const u32 sSpriteTiles_ReelIcons[] = INCBIN_U32("graphics/slot_machine/unk_lg_8464434.4bpp.lz");
static const u16 sSpritePal_Clefairy[] = INCBIN_U16("graphics/slot_machine/unk_lg_8464a3c.gbapal");
static const u32 sSpriteTiles_Clefairy[] = INCBIN_U32("graphics/slot_machine/unk_lg_8464a3c.4bpp.lz");
#endif
static const u16 sSpritePal_Digits[] = INCBIN_U16("graphics/slot_machine/unk_8465524.gbapal");
static const u32 sSpriteTiles_Digits[] = INCBIN_U32("graphics/slot_machine/unk_8465544.4bpp.lz");

static const struct CompressedSpriteSheet sSpriteSheets[] = {
    {(const void *)sSpriteTiles_ReelIcons, 0xe00, 0},
    {(const void *)sSpriteTiles_Clefairy,  0xc00, 1},
    {(const void *)sSpriteTiles_Digits,    0x280, 2},
};

static const struct SpritePalette sSpritePalettes[] = {
    {sSpritePal_ReelIcons_0, 0},
    {sSpritePal_ReelIcons_1, 1},
    {sSpritePal_ReelIcons_2, 2},
    {sSpritePal_ReelIcons_3, 3},
    {sSpritePal_ReelIcons_4, 4},
    {sSpritePal_Clefairy,    5},
    {sSpritePal_Digits,      6},
    {NULL}
};

#if defined(FIRERED)
static const u16 sReelIconPaletteTags[] = {
    [SLOT_IMAGE_7]         = 2,
    [SLOT_IMAGE_ROCKET]    = 2,
    [SLOT_IMAGE_PIKACHU]   = 0,
    [SLOT_IMAGE_PSYDUCK]   = 0,
    [SLOT_IMAGE_CHERRIES]  = 2,
    [SLOT_IMAGE_MAGNEMITE] = 4,
    [SLOT_IMAGE_SHELLDER]  = 3
};
#elif defined(LEAFGREEN)
static const u16 sReelIconPaletteTags[] = {
    [SLOT_IMAGE_7]         = 2,
    [SLOT_IMAGE_ROCKET]    = 2,
    [SLOT_IMAGE_PIKACHU]   = 0,
    [SLOT_IMAGE_PSYDUCK]   = 3,
    [SLOT_IMAGE_CHERRIES]  = 2,
    [SLOT_IMAGE_MAGNEMITE] = 1,
    [SLOT_IMAGE_SHELLDER]  = 1
};
#endif

static const u16 sReelIconAffineParams[] = {
    0x0120, 0x011f, 0x011e, 0x011d, 0x011c, 0x011b, 0x011a, 0x0119, 0x0118, 0x0117, 0x0116, 0x0115, 0x0114, 0x0113, 0x0112, 0x0111,
    0x0110, 0x010f, 0x010e, 0x010d, 0x010c, 0x010b, 0x010a, 0x0109, 0x0108, 0x0107, 0x0106, 0x0105, 0x0104, 0x0103, 0x0102, 0x0101,
    0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
    0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
    0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108, 0x0109, 0x010a, 0x010b, 0x010c, 0x010d, 0x010e, 0x010f, 0x0110,
    0x0111, 0x0112, 0x0113, 0x0114, 0x0115, 0x0116, 0x0117, 0x0118, 0x0119, 0x011a, 0x011b, 0x011c, 0x011d, 0x011e, 0x011f, 0x0120
};

static const u8 sReelIconBldY[] = {
    0x10, 0x10, 0x10, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0c, 0x0b, 0x0a, 0x0a, 0x09, 0x08, 0x07, 0x07, 0x06, 0x05, 0x04, 0x04, 0x03, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x06, 0x07, 0x08, 0x09, 0x09, 0x0a, 0x0b, 0x0c, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f
};

static const struct OamData sOamData_ReelIcons = {
    .y = 0,
    .affineMode = ST_OAM_AFFINE_NORMAL,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = FALSE,
    .bpp = ST_OAM_4BPP,
    .shape = ST_OAM_SQUARE,
    .x = 0,
    .matrixNum = 0,
    .size = ST_OAM_SIZE_2,
    .tileNum = 0,
    .priority = 3,
    .paletteNum = 0,
    .affineParam = 0
};

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

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

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

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

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

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

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

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

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

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

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

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

static const union AnimCmd *const sAnimTable_ReelIcons[] = {
    sAnimCmd_ReelIcon_7,
    sAnimCmd_ReelIcon_Rocket,
    sAnimCmd_ReelIcon_Pikachu,
    sAnimCmd_ReelIcon_Psyduck,
    sAnimCmd_ReelIcon_Cherries,
    sAnimCmd_ReelIcon_Magnemite,
    sAnimCmd_ReelIcon_Shellder,
    sAnimCmd_ReelIcon_Pikachu_2,
    sAnimCmd_ReelIcon_Psyduck_2,
    sAnimCmd_ReelIcon_Cherries_2,
    sAnimCmd_ReelIcon_Magnemite_2,
    sAnimCmd_ReelIcon_Shellder_2
};

static const union AffineAnimCmd sAffineAnimCmd_ReelIcons_Unused[] = {
    AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 1),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd *const sAffineAnimTable_ReelIcons_Unused[] = {
    sAffineAnimCmd_ReelIcons_Unused
};

static const struct SpriteTemplate sSpriteTemplate_ReelIcons = {
    .tileTag = 0,
    .paletteTag = 0,
    .oam = &sOamData_ReelIcons,
    .anims = sAnimTable_ReelIcons,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

static const struct OamData sOamData_Digits = {
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = FALSE,
    .bpp = ST_OAM_4BPP,
    .shape = ST_OAM_V_RECTANGLE,
    .x = 0,
    .matrixNum = 0,
    .size = ST_OAM_SIZE_0,
    .tileNum = 0,
    .priority = 1,
    .paletteNum = 0,
    .affineParam = 0
};

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

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

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

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

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

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

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

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

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

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

static const union AnimCmd *const sAnimTable_Digits[] = {
    sSpriteAnim_Digit0,
    sSpriteAnim_Digit1,
    sSpriteAnim_Digit2,
    sSpriteAnim_Digit3,
    sSpriteAnim_Digit4,
    sSpriteAnim_Digit5,
    sSpriteAnim_Digit6,
    sSpriteAnim_Digit7,
    sSpriteAnim_Digit8,
    sSpriteAnim_Digit9
};

static const struct SpriteTemplate sSpriteTemplate_Digits = {
    .tileTag = 2,
    .paletteTag = 6,
    .oam = &sOamData_Digits,
    .anims = sAnimTable_Digits,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

static const struct OamData sOamData_Clefairy = {
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = FALSE,
    .bpp = ST_OAM_4BPP,
    .shape = ST_OAM_SQUARE,
    .x = 0,
    .matrixNum = 0,
    .size = ST_OAM_SIZE_2,
    .tileNum = 0,
    .priority = 1,
    .paletteNum = 0,
    .affineParam = 0
};

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

static const union AnimCmd sAnimCmd_Clefairy_Spinning[] = {
    ANIMCMD_FRAME( 0, 24),
    ANIMCMD_FRAME(16, 24),
    ANIMCMD_JUMP(0)
};

static const union AnimCmd sAnimCmd_Clefairy_Payout[] = {
    ANIMCMD_FRAME(32, 28),
    ANIMCMD_FRAME(48, 28),
    ANIMCMD_JUMP(0)
};

static const union AnimCmd sAnimCmd_Clefairy_Lose[] = {
    ANIMCMD_FRAME(64, 12),
    ANIMCMD_FRAME(80, 12),
    ANIMCMD_JUMP(0)
};

static const union AnimCmd *const sAnimTable_Clefairy[] = {
    sAnimCmd_Clefairy_Neutral,
    sAnimCmd_Clefairy_Spinning,
    sAnimCmd_Clefairy_Payout,
    sAnimCmd_Clefairy_Lose
};

static const struct SpriteTemplate sSpriteTemplate_Clefairy = {
    .tileTag = 1,
    .paletteTag = 5,
    .oam = &sOamData_Clefairy,
    .anims = sAnimTable_Clefairy,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

bool8 (*const sSlotMachineSetupTasks[])(u8 *, struct SlotMachineSetupTaskData *) = {
    [SLOTTASK_GFX_INIT] = SlotsTask_GraphicsInit,
    [SLOTTASK_FADEOUT_EXIT] = SlotsTask_FadeOut,
    [SLOTTASK_UPDATE_LINE_LIGHTS] = SlotsTask_UpdateLineStates,
    [SLOTTASK_CLEFAIRY_BOUNCE] = SlotsTask_ClefairyUpdateOnReelsStart,
    [SLOTTASK_ANIM_WIN] = SlotsTask_StartClefairyDanceAndWinningLineFlash,
    [SLOTTASK_END_ANIM_WIN] = SlotsTask_StopWinningLineFlashTask,
    [SLOTTASK_ANIM_LOSE] = SlotsTask_ClefairyFainted,
    [SLOTTASK_ANIM_BETTING] = SlotsTask_ClefairyNeutral,
    [SLOTTASK_SHOW_AMOUNTS] = SlotsTask_UpdateCoinsDisplay,
    [SLOTTASK_MSG_NO_COINS] = SlotsTask_MessageOutOfCoins,
    [SLOTTASK_ASK_QUIT] = SlotsTask_AskQuitPlaying,
    [SLOTTASK_DESTROY_YESNO] = SlotsTask_DestroyYesNoMenu,
    [SLOTTASK_PRESS_BUTTON] = SlotsTask_PressReelButton,
    [SLOTTASK_RELEASE_BUTTONS] = SlotsTask_ReleaseReelButtons,
    [SLOTTASK_SHOWHELP] = SlotsTask_ShowHelp,
    [SLOTTASK_HIDEHELP] = SlotsTask_HideHelp
};

#if defined(FIRERED)
static const u16 sBgPal_00[] = INCBIN_U16("graphics/slot_machine/unk_8465930.gbapal");
static const u16 sBgPal_10[] = INCBIN_U16("graphics/slot_machine/unk_8465950.gbapal");
static const u16 sBgPal_20[] = INCBIN_U16("graphics/slot_machine/unk_8465970.gbapal");
static const u16 sBgPal_30[] = INCBIN_U16("graphics/slot_machine/unk_8465990.gbapal");
static const u16 sBgPal_40[] = INCBIN_U16("graphics/slot_machine/unk_84659b0.gbapal");
static const u32 sBg2Tiles_00[] = INCBIN_U32("graphics/slot_machine/unk_84659d0.4bpp.lz");
static const u32 sBg2Map[] = INCBIN_U32("graphics/slot_machine/unk_84661d4.bin.lz");
static const u16 sBgPal_50[] = INCBIN_U16("graphics/slot_machine/unk_84664bc.gbapal");
static const u16 sBgPal_VictoryFlash[] = INCBIN_U16("graphics/slot_machine/unk_84664dc.gbapal","graphics/slot_machine/unk_84664fc.gbapal", "graphics/slot_machine/unk_846651c.gbapal");
#elif defined(LEAFGREEN)
static const u16 sBgPal_00[] = INCBIN_U16("graphics/slot_machine/unk_lg_84652bc.gbapal");
static const u16 sBgPal_10[] = INCBIN_U16("graphics/slot_machine/unk_lg_84652dc.gbapal");
static const u16 sBgPal_20[] = INCBIN_U16("graphics/slot_machine/unk_lg_84652fc.gbapal");
static const u16 sBgPal_30[] = INCBIN_U16("graphics/slot_machine/unk_lg_846531c.gbapal");
static const u16 sBgPal_40[] = INCBIN_U16("graphics/slot_machine/unk_lg_846533c.gbapal");
static const u32 sBg2Tiles_00[] = INCBIN_U32("graphics/slot_machine/unk_lg_846535c.4bpp.lz");
static const u32 sBg2Map[] = INCBIN_U32("graphics/slot_machine/unk_lg_8465ab8.bin.lz");
static const u16 sBgPal_50[] = INCBIN_U16("graphics/slot_machine/unk_lg_8465d9c.gbapal");
static const u16 sBgPal_VictoryFlash[] = INCBIN_U16("graphics/slot_machine/unk_lg_8465dbc.gbapal","graphics/slot_machine/unk_lg_8465ddc.gbapal", "graphics/slot_machine/unk_lg_8465dfc.gbapal");
#endif
static const u32 sBg2Tiles_C0[] = INCBIN_U32("graphics/slot_machine/unk_846653c.4bpp.lz");
static const u16 sBgPal_70[] = INCBIN_U16("graphics/slot_machine/unk_84665c0.gbapal");
static const u16 sBgPal_80[] = INCBIN_U16("graphics/slot_machine/unk_84665e0.gbapal");
static const u16 sBgPal_90[] = INCBIN_U16("graphics/slot_machine/unk_8466600.gbapal");
#if defined(FIRERED)
static const u32 sBg1Tiles[] = INCBIN_U32("graphics/slot_machine/unk_8466620.4bpp.lz");
static const u32 sBg1Map[] = INCBIN_U32("graphics/slot_machine/unk_8466998.bin.lz");
#elif defined(LEAFGREEN)
static const u32 sBg1Tiles[] = INCBIN_U32("graphics/slot_machine/unk_lg_8465f00.4bpp.lz");
static const u32 sBg1Map[] = INCBIN_U32("graphics/slot_machine/unk_lg_8466278.bin.lz");
#endif

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

static const struct WindowTemplate sWindowTemplates[] = {
    {
        .bg = 0,
        .tilemapLeft = 5,
        .tilemapTop = 15,
        .width = 20,
        .height = 4,
        .paletteNum = 0x0f,
        .baseBlock = 0x04f
    }, {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 0,
        .width = 30,
        .height = 2,
        .paletteNum = 0x0e,
        .baseBlock = 0x013
    },
    DUMMY_WIN_TEMPLATE
};

static const u16 sLineTiles_TLBR[] = {
    0x00a4, 0x00a5, 0x00a6, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00e7, 0x012c, 0x014c, 0x0191, 0x01b1, 0x01f6, 0x0216, 0x0217, 0x0218, 0x0219, 0x0237, 0x0238, 0x0239
};

static const u16 sLineTiles_TopRow[] = {
    0x00e4, 0x00e5, 0x00e6, 0x00f7, 0x00f8, 0x00f9, 0x0104, 0x0105, 0x0106, 0x0107, 0x010c, 0x0111, 0x0116, 0x0117, 0x0118, 0x0119, 0x0124, 0x0125, 0x0126, 0x0137, 0x0138, 0x0139
};

static const u16 sLineTiles_MiddleRow[] = {
    0x0144, 0x0145, 0x0146, 0x0157, 0x0158, 0x0159, 0x0164, 0x0165, 0x0166, 0x0167, 0x016c, 0x0171, 0x0176, 0x0177, 0x0178, 0x0179, 0x0184, 0x0185, 0x0186, 0x0197, 0x0198, 0x0199
};

static const u16 sLineTiles_BottomRow[] = {
    0x01a4, 0x01a5, 0x01a6, 0x01b7, 0x01b8, 0x01b9, 0x01c4, 0x01c5, 0x01c6, 0x01c7, 0x01cc, 0x01d1, 0x01d6, 0x01d7, 0x01d8, 0x01d9, 0x01e4, 0x01e5, 0x01e6, 0x01f7, 0x01f8, 0x01f9
};

static const u16 sLineTiles_BLTR[] = {
    0x0204, 0x0205, 0x0206, 0x0224, 0x0225, 0x0226, 0x01e7, 0x0207, 0x018c, 0x01ac, 0x0131, 0x0151, 0x00d6, 0x00f6, 0x00b7, 0x00b8, 0x00b9, 0x00d7, 0x00d8, 0x00d9
};

static const struct LineStateTileIdxList sLineStateTileIdxs[] = {
    { sLineTiles_TLBR, NELEMS(sLineTiles_TLBR) },
    { sLineTiles_TopRow, NELEMS(sLineTiles_TopRow) },
    { sLineTiles_MiddleRow, NELEMS(sLineTiles_MiddleRow) },
    { sLineTiles_BottomRow, NELEMS(sLineTiles_BottomRow) },
    { sLineTiles_BLTR, NELEMS(sLineTiles_BLTR) }
};

static const u8 sWInningLineFlashPalIdxs[2] = {2, 4};

static const struct WindowTemplate sYesNoWindowTemplate = {
    .bg = 0,
    .tilemapLeft = 19,
    .tilemapTop = 9,
    .width = 6,
    .height = 4,
    .paletteNum = 15,
    .baseBlock = 0x9F
};

static const u16 sReelButtonMapTileIdxs[][4] = {
    {0x0229, 0x022a, 0x0249, 0x024a},
    {0x022e, 0x022f, 0x024e, 0x024f},
    {0x0233, 0x0234, 0x0253, 0x0254}
};

void PlaySlotMachine(u16 machineIdx, MainCallback savedCallback)
{
    ResetTasks();
    sSlotMachineState = Alloc(sizeof(*sSlotMachineState));
    if (sSlotMachineState == NULL)
        SetMainCallback2(savedCallback);
    else
    {
        if (machineIdx > 5)
            machineIdx = 0;
        sSlotMachineState->machineidx = machineIdx;
        sSlotMachineState->savedCallback = savedCallback;
        InitSlotMachineState(sSlotMachineState);
        SetMainCallback2(CB2_InitSlotMachine);
    }
}

static void InitSlotMachineState(struct SlotMachineState * ptr)
{
    s32 i;

    ptr->currentReel = 0;
    ptr->bet = 0;
    ptr->payout = 0;
    // for whatever reason, the loop does not use the ptr param
    for (i = 0; i < 3; i++)
    {
        sSlotMachineState->reelIsSpinning[i] = FALSE;
        sSlotMachineState->reelPositions[i]  =     0;
        sSlotMachineState->reelSubpixel[i]   =     0;
        sSlotMachineState->destReelPos[i]    =    21;
    }
}

static void CB2_InitSlotMachine(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();

    switch (gMain.state)
    {
    case 0:
        if (CreateSlotMachine())
        {
            SetMainCallback2(sSlotMachineState->savedCallback);
            CleanSupSlotMachineState();
        }
        else
        {
            SetSlotMachineSetupTask(SLOTTASK_GFX_INIT, 0);
            gMain.state++;
        }
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0))
        {
            sSlotMachineState->taskId = CreateTask(MainTask_SlotsGameLoop, 0);
            sSlotMachineState->spinReelsTaskId = CreateTask(Task_SpinReels, 1);
            SetMainCallback2(CB2_RunSlotMachine);
        }
        break;
    }
}

static void CleanSupSlotMachineState(void)
{
    DestroySlotMachine();
    if (sSlotMachineState != NULL)
    {
        Free(sSlotMachineState);
        sSlotMachineState = NULL;
    }
}

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

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

    switch (data[0])
    {
    case 0:
        // Betting Phase
        if (GetCoins() == 0)
        {
            SetMainTask(MainTask_NoCoinsGameOver);
        }
        else if (JOY_NEW(DPAD_DOWN))
        {
            sSlotMachineState->bet++;
            RemoveCoins(1);
            PlaySE(SE_RS_SHOP);
            SetSlotMachineSetupTask(SLOTTASK_SHOW_AMOUNTS, 0);
            SetSlotMachineSetupTask(SLOTTASK_UPDATE_LINE_LIGHTS, 1);
            data[0] = 1;
        }
        else if (JOY_NEW(R_BUTTON))
        {
            s32 toAdd = 3 - sSlotMachineState->bet;
            if (GetCoins() >= toAdd)
            {
                sSlotMachineState->bet = 3;
                RemoveCoins(toAdd);
            }
            else
            {
                sSlotMachineState->bet += GetCoins();
                SetCoins(0);
            }
            PlaySE(SE_RS_SHOP);
            SetSlotMachineSetupTask(SLOTTASK_SHOW_AMOUNTS, 0);
            SetSlotMachineSetupTask(SLOTTASK_UPDATE_LINE_LIGHTS, 1);
            data[0] = 1;
        }
        else if (JOY_NEW(A_BUTTON) && sSlotMachineState->bet != 0)
        {
            data[0] = 2;
        }
        else if (JOY_NEW(B_BUTTON))
        {
            SetMainTask(MainTask_ConfirmExitGame);
        }
        else if (JOY_NEW(DPAD_RIGHT))
        {
            SetMainTask(MainTask_ShowHelp);
        }
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0) && !IsSlotMachineSetupTaskActive(1))
        {
            if (sSlotMachineState->bet == 3 || GetCoins() == 0)
                data[0] = 2;
            else
                data[0] = 0;
        }
        break;
    case 2:
        SetQLPlayedTheSlots();
        CalcSlotBias();
        StartReels();
        sSlotMachineState->currentReel = 0;
        SetSlotMachineSetupTask(SLOTTASK_CLEFAIRY_BOUNCE, 0);
        data[0] = 3;
        break;
    case 3:
        if (!IsSlotMachineSetupTaskActive(0))
        {
            if (JOY_NEW(A_BUTTON))
            {
                PlaySE(SE_CONTEST_PLACE);
                StopCurrentReel(sSlotMachineState->currentReel, sSlotMachineState->currentReel);
                PressReelButton(sSlotMachineState->currentReel, 0);
                data[0] = 4;
            }
        }
        break;
    case 4:
        if (IsReelSpinning(sSlotMachineState->currentReel) == 0 && !IsSlotMachineSetupTaskActive(0))
        {
            sSlotMachineState->currentReel++;
            if (sSlotMachineState->currentReel >= 3)
            {
                sSlotMachineState->slotRewardClass = CalcPayout();
                sSlotMachineState->bet = 0;
                sSlotMachineState->currentReel = 0;
                if (sSlotMachineState->slotRewardClass == SLOT_PAYOUT_NONE)
                    SetMainTask(MainTask_DarnNoPayout);
                else
                {
                    if (sSlotMachineState->slotRewardClass == SLOT_PAYOUT_7)
                        IncrementGameStat(GAME_STAT_SLOT_JACKPOTS);
                    ResetMachineBias();
                    SetMainTask(MainTask_WinHandlePayout);
                }
            }
            else
                data[0] = 3;
        }
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        SetSlotMachineSetupTask(SLOTTASK_MSG_NO_COINS, 0);
        data[0]++;
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0))
            data[0]++;
        break;
    case 2:
        if (JOY_NEW(A_BUTTON | B_BUTTON | DPAD_ANY))
            SetMainTask(MainTask_ExitSlots);
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        SetSlotMachineSetupTask(SLOTTASK_SHOWHELP, 0);
        data[0]++;
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0))
            data[0]++;
        break;
    case 2:
        if (JOY_NEW(DPAD_LEFT))
        {
            SetSlotMachineSetupTask(SLOTTASK_HIDEHELP, 0);
            data[0]++;
        }
        break;
    case 3:
        if (!IsSlotMachineSetupTaskActive(0))
            SetMainTask(MainTask_SlotsGameLoop);
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        SetSlotMachineSetupTask(SLOTTASK_ASK_QUIT, 0);
        data[0]++;
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0))
            data[0]++;
        break;
    case 2:
        switch (Menu_ProcessInputNoWrapClearOnChoose())
        {
        case 0:
            AddCoins(sSlotMachineState->bet);
            SetSlotMachineSetupTask(SLOTTASK_SHOW_AMOUNTS, 0);
            data[0] = 3;
            break;
        case 1:
        case -1:
            SetSlotMachineSetupTask(SLOTTASK_DESTROY_YESNO, 0);
            data[0] = 4;
            break;
        }
        break;
    case 3:
        if (!IsSlotMachineSetupTaskActive(0))
            SetMainTask(MainTask_ExitSlots);
        break;
    case 4:
        if (!IsSlotMachineSetupTaskActive(0))
            SetMainTask(MainTask_SlotsGameLoop);
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        SetSlotMachineSetupTask(SLOTTASK_ANIM_LOSE, 0);
        data[1] = 0;
        data[0]++;
        break;
    case 1:
        data[1]++;
        if (data[1] > 60)
        {
            SetSlotMachineSetupTask(SLOTTASK_ANIM_BETTING, 0);
            SetSlotMachineSetupTask(SLOTTASK_UPDATE_LINE_LIGHTS, 1);
            SetSlotMachineSetupTask(SLOTTASK_RELEASE_BUTTONS, 2);
            data[0]++;
        }
        break;
    case 2:
        if (!IsSlotMachineSetupTaskActive(0) && !IsSlotMachineSetupTaskActive(1) && !IsSlotMachineSetupTaskActive(2))
            SetMainTask(MainTask_SlotsGameLoop);
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        if (sSlotMachineState->slotRewardClass == SLOT_PAYOUT_ROCKET || sSlotMachineState->slotRewardClass == SLOT_PAYOUT_7)
            PlayFanfare(MUS_SLOTS_JACKPOT);
        else
            PlayFanfare(MUS_SLOTS_WIN);
        SetSlotMachineSetupTask(SLOTTASK_SHOW_AMOUNTS, 0);
        SetSlotMachineSetupTask(SLOTTASK_ANIM_WIN, 1);
        data[1] = 8;
        data[0]++;
        break;
    case 1:
        data[1]++;
        if (data[1] > 120)
        {
            data[1] = 8;
            if (JOY_HELD(A_BUTTON))
                data[1] = 2;
            data[0]++;
        }
        break;
    case 2:
        if (!IsSlotMachineSetupTaskActive(0))
        {
            if (IsFanfareTaskInactive() && JOY_NEW(START_BUTTON))
            {
                AddCoins(sSlotMachineState->payout);
                sSlotMachineState->payout = 0;
            }
            else
            {
                data[1]--;
                if (data[1] == 0)
                {
                    if (IsFanfareTaskInactive())
                        PlaySE(SE_PIN);
                    if (sSlotMachineState->payout != 0)
                    {
                        AddCoins(1);
                        sSlotMachineState->payout--;
                    }
                    data[1] = 8;
                    if (JOY_HELD(A_BUTTON))
                        data[1] = 2;
                }
            }
            SetSlotMachineSetupTask(SLOTTASK_SHOW_AMOUNTS, 0);
            if (sSlotMachineState->payout == 0)
                data[0]++;
        }
        break;
    case 3:
        if (IsFanfareTaskInactive() && !IsSlotMachineSetupTaskActive(0))
        {
            SetSlotMachineSetupTask(SLOTTASK_END_ANIM_WIN, 0);
            data[0]++;
        }
        break;
    case 4:
        if (!IsSlotMachineSetupTaskActive(0))
        {
            SetSlotMachineSetupTask(SLOTTASK_UPDATE_LINE_LIGHTS, 0);
            SetSlotMachineSetupTask(SLOTTASK_RELEASE_BUTTONS, 1);
            data[0]++;
        }
        break;
    case 5:
        if (!IsSlotMachineSetupTaskActive(0) && !IsSlotMachineSetupTaskActive(1))
            SetMainTask(MainTask_SlotsGameLoop);
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        SetSlotMachineSetupTask(SLOTTASK_FADEOUT_EXIT, 0);
        data[0]++;
        break;
    case 1:
        if (!IsSlotMachineSetupTaskActive(0))
        {
            SetMainCallback2(sSlotMachineState->savedCallback);
            CleanSupSlotMachineState();
        }
        break;
    }
}

static void SetMainTask(TaskFunc taskFunc)
{
    gTasks[sSlotMachineState->taskId].func = taskFunc;
    gTasks[sSlotMachineState->taskId].data[0] = 0;
}

static void Task_SpinReels(u8 taskId)
{
    // taskId is never used

    s32 i;

    for (i = 0; i < 3; i++)
    {
        if (sSlotMachineState->reelIsSpinning[i] || sSlotMachineState->reelSubpixel[i] != 0)
        {
            if (sSlotMachineState->reelSubpixel[i] != 0 || sSlotMachineState->reelPositions[i] != sSlotMachineState->destReelPos[i])
            {
                sSlotMachineState->reelSubpixel[i]++;
                if (sSlotMachineState->reelSubpixel[i] > 2)
                {
                    sSlotMachineState->reelSubpixel[i] = 0;
                    sSlotMachineState->reelPositions[i]--;
                    if (sSlotMachineState->reelPositions[i] < 0)
                        sSlotMachineState->reelPositions[i] = 20;
                }
                if (sSlotMachineState->reelPositions[i] != sSlotMachineState->destReelPos[i])
                    continue;
            }
            sSlotMachineState->destReelPos[i] = 21;
            sSlotMachineState->reelIsSpinning[i] = FALSE;
        }
    }
    UpdateReelIconSprites(sSlotMachineState->reelPositions, sSlotMachineState->reelSubpixel);
}

static void StartReels(void)
{
    s32 i;

    for (i = 0; i < 3; i++)
    {
        sSlotMachineState->reelIsSpinning[i] = TRUE;
    }
}

static void StopCurrentReel(u16 whichReel, u16 whichReel2)
{
    switch (whichReel2)
    {
    case 0:
        StopReel1(whichReel);
        break;
    case 1:
        StopReel2(whichReel);
        break;
    case 2:
        StopReel3(whichReel);
        break;
    }
}

static bool32 IsReelSpinning(u16 whichReel)
{
    return sSlotMachineState->reelIsSpinning[whichReel];
}

static s16 GetNextReelPosition(u16 whichReel)
{
    s16 position = sSlotMachineState->reelPositions[whichReel];
    if (sSlotMachineState->reelSubpixel[whichReel] != 0)
    {
        position--;
        if (position < 0)
            position = 20;
    }
    return position;
}

static void StopReel1(u16 whichReel)
{
    s32 i, j;
    s16 destPos;
    u8 posToSample[5];
    s16 nextPos = GetNextReelPosition(whichReel);
    s32 numPosToSample = 0;

    if (sSlotMachineState->machineBias == 0 && whichReel == 0)
    {
        for (i = 0; i < 5; i++)
        {
            for (j = 0, destPos = nextPos - i + 1; j < 3; j++, destPos++)
            {
                if (destPos >= 21)
                    destPos = 0;
                if (TestReelIconAttribute(1, sReelIconAnimByReelAndPos[whichReel][destPos]))
                    break;
            }
            if (j == 3)
            {
                posToSample[numPosToSample] = i;
                numPosToSample++;
            }
        }
    }
    else if (sSlotMachineState->machineBias != 1 || whichReel == 0)
    {
        for (i = 0, destPos = nextPos + 1; i < 3; i++, destPos++)
        {
            if (destPos >= 21)
                destPos = 0;
            if (TestReelIconAttribute(sSlotMachineState->machineBias, sReelIconAnimByReelAndPos[whichReel][destPos]))
            {
                posToSample[0] = 0;
                numPosToSample = 1;
                break;
            }
        }
        for (i = 0, destPos = nextPos; i < 4; i++, destPos--)
        {
            if (destPos < 0)
                destPos = 20;
            if (TestReelIconAttribute(sSlotMachineState->machineBias, sReelIconAnimByReelAndPos[whichReel][destPos]))
            {
                posToSample[numPosToSample] = i + 1;
                numPosToSample++;
            }
        }
    }
    if (numPosToSample == 0)
    {
        destPos = Random() % 5;
    }
    else
    {
        destPos = posToSample[Random() % numPosToSample];
    }
    destPos = nextPos - destPos;
    if (destPos < 0)
        destPos += 21;
    sSlotMachineState->reelStopOrder[0] = whichReel;
    sSlotMachineState->destReelPos[whichReel] = destPos;
}

static void StopReel2(u16 whichReel)
{
    s16 pos, firstStoppedReelPos, firstStoppedReelId, nextPos;
    s32 i;
    s32 numPossiblePositions;
    u8 possiblePositions[5];

    firstStoppedReelId = sSlotMachineState->reelStopOrder[0];
    firstStoppedReelPos = sSlotMachineState->reelPositions[firstStoppedReelId] + 1;
    if (firstStoppedReelPos >= 21)
        firstStoppedReelPos = 0;
    nextPos = GetNextReelPosition(whichReel);
    pos = nextPos + 1;
    if (pos >= 21)
        pos = 0;
    numPossiblePositions = 0;
    for (i = 0; i < 5; i++)
    {
        if (TwoReelBiasCheck(firstStoppedReelId, firstStoppedReelPos, whichReel, pos, sSlotMachineState->machineBias))
        {
            possiblePositions[numPossiblePositions] = i;
            numPossiblePositions++;
        }
        pos--;
        if (pos < 0)
            pos = 20;
    }
    if (numPossiblePositions == 0)
    {
        sSlotMachineState->reel2BiasInPlay = 0;
        if (sSlotMachineState->machineBias == SLOT_PAYOUT_ROCKET || sSlotMachineState->machineBias == SLOT_PAYOUT_7)
            pos = 4;
        else
            pos = 0;
    }
    else
    {
        sSlotMachineState->reel2BiasInPlay = 1;
        pos = possiblePositions[0];
    }
    pos = nextPos - pos;
    if (pos < 0)
        pos += 21;
    sSlotMachineState->reelStopOrder[1] = whichReel;
    sSlotMachineState->destReelPos[whichReel] = pos;
}

static void StopReel3(u16 whichReel)
{
    s32 i;
    s32 numPossiblePositions;
    s32 nextPos;
    s32 testPos;
    s32 pos;
    u8 possiblePositions[5];

    nextPos = GetNextReelPosition(whichReel);
    testPos = nextPos;
    numPossiblePositions = 0;
    for (i = 0; i < 5; i++)
    {
        if (OneReelBiasCheck(whichReel, testPos, sSlotMachineState->machineBias))
        {
            possiblePositions[numPossiblePositions] = i;
            numPossiblePositions++;
        }
        testPos--;
        if (testPos < 0)
            testPos = 20;
    }
    if (numPossiblePositions == 0)
    {
        if (sSlotMachineState->machineBias == SLOT_PAYOUT_ROCKET || sSlotMachineState->machineBias == SLOT_PAYOUT_7)
            pos = 4;
        else
            pos = 0;
    }
    else
        pos = possiblePositions[0];
    pos = nextPos - pos;
    if (pos < 0)
        pos += 21;
    sSlotMachineState->destReelPos[whichReel] = pos;
}

static bool32 TwoReelBiasCheck(s32 reel0id, s32 reel0pos, s32 reel1id, s32 reel1pos, s32 icon)
{
    s32 i;
    u8 icons[9] = {};

    for (i = 0; i < 9; i++)
        icons[i] = 7;

    for (i = 0; i < 3; i++)
    {
        icons[3 * reel0id + i] = sReelIconAnimByReelAndPos[reel0id][reel0pos];
        icons[3 * reel1id + i] = sReelIconAnimByReelAndPos[reel1id][reel1pos];
        reel0pos++;
        if (reel0pos >= 21)
            reel0pos = 0;
        reel1pos++;
        if (reel1pos >= 21)
            reel1pos = 0;
    }

    switch (icon)
    {
    case 0:
        for (i = 0; i < 3; i++)
        {
            if (TestReelIconAttribute(1, icons[i]))
                return FALSE;
        }
        for (i = 0; i < 15; i++)
        {
            if (icons[sSecondReelBiasCheckIndices[i][0]] == icons[sSecondReelBiasCheckIndices[i][1]])
                return TRUE;
        }
        return FALSE;
    case 1:
        if (reel0id == 0 || reel1id == 0)
        {
            if (reel0id == 1 || reel1id == 1)
            {
                for (i = 0; i < 15; i += 3)
                {
                    if (icons[sSecondReelBiasCheckIndices[i][0]] == icons[sSecondReelBiasCheckIndices[i][1]])
                        return FALSE;
                }
            }
            for (i = 0; i < 3; i++)
            {
                if (TestReelIconAttribute(icon, icons[i]))
                    return TRUE;
            }
            return FALSE;
        }
        else
            return TRUE;
    case 2:
        if (reel0id == 2 || reel1id == 2)
        {
            for (i = 0; i < 9; i++)
            {
                if (TestReelIconAttribute(icon, icons[i]))
                    return TRUE;
            }
            return FALSE; // wrong level
        }
    }
    for (i = 0; i < 15; i++)
    {
        if (icons[sSecondReelBiasCheckIndices[i][0]] == icons[sSecondReelBiasCheckIndices[i][1]] && TestReelIconAttribute(icon, icons[sSecondReelBiasCheckIndices[i][0]]))
            return TRUE;
    }
    return FALSE;
}

static bool32 OneReelBiasCheck(s32 reelId, s32 reelPos, s32 biasIcon)
{
    u8 icons[9];
    s32 secondStoppedPos, firstStoppedPos;
    s32 i;

    firstStoppedPos++; firstStoppedPos--;
    firstStoppedPos = sSlotMachineState->reelPositions[sSlotMachineState->reelStopOrder[0]] + 1;
    secondStoppedPos = sSlotMachineState->reelPositions[sSlotMachineState->reelStopOrder[1]] + 1;
    reelPos++;
    if (firstStoppedPos >= 21)
        firstStoppedPos = 0;
    if (secondStoppedPos >= 21)
        secondStoppedPos = 0;
    if (reelPos >= 21)
        reelPos = 0;
    for (i = 0; i < 3; i++)
    {
        icons[sSlotMachineState->reelStopOrder[0] * 3 + i] = sReelIconAnimByReelAndPos[sSlotMachineState->reelStopOrder[0]][firstStoppedPos];
        icons[sSlotMachineState->reelStopOrder[1] * 3 + i] = sReelIconAnimByReelAndPos[sSlotMachineState->reelStopOrder[1]][secondStoppedPos];
        icons[reelId * 3 + i] = sReelIconAnimByReelAndPos[reelId][reelPos];
        if (++firstStoppedPos >= 21)
            firstStoppedPos = 0;
        if (++secondStoppedPos >= 21)
            secondStoppedPos = 0;
        if (++reelPos >= 21)
            reelPos = 0;
    }
    switch (biasIcon)
    {
    case SLOT_PAYOUT_NONE:
        for (i = 0; i < 3; i++)
        {
            if (TestReelIconAttribute(1, icons[i]))
                return FALSE;
        }
        for (i = 0; i < 5; i++)
        {
            if (icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][1]] && icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][2]])
                return FALSE;
        }
        return TRUE;
    case SLOT_PAYOUT_CHERRIES2:
        for (i = 0; i < 5; i++)
        {
            if (icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][1]] && TestReelIconAttribute(biasIcon, icons[sThirdReelBiasCheckIndices[i][0]]))
                return FALSE;
        }
        for (i = 0; i < 3; i++)
        {
            if (TestReelIconAttribute(biasIcon, icons[i]))
                return TRUE;
        }
        return FALSE;
    case SLOT_PAYOUT_CHERRIES3:
        for (i = 0; i < 5; i++)
        {
            if (icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][1]] && TestReelIconAttribute(biasIcon, icons[sThirdReelBiasCheckIndices[i][0]]))
                return TRUE;
        }
        return FALSE;
    }
    for (i = 0; i < 5; i++)
    {
        if (icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][1]] && icons[sThirdReelBiasCheckIndices[i][0]] == icons[sThirdReelBiasCheckIndices[i][2]] && TestReelIconAttribute(biasIcon, icons[sThirdReelBiasCheckIndices[i][0]]))
            return TRUE;
    }
    return FALSE;
}

static bool32 TestReelIconAttribute(s32 attr, s32 icon)
{
    switch (attr)
    {
    case SLOT_PAYOUT_NONE:
        return icon ^ 4 ? TRUE : FALSE;
    case SLOT_PAYOUT_CHERRIES2:
    case SLOT_PAYOUT_CHERRIES3:
        return icon == SLOT_IMAGE_CHERRIES ? TRUE : FALSE;
    case SLOT_PAYOUT_MAGSHELL:
        return icon == SLOT_IMAGE_MAGNEMITE || icon == SLOT_IMAGE_SHELLDER ? TRUE : FALSE;
    case SLOT_PAYOUT_PIKAPSY:
        return icon == SLOT_IMAGE_PIKACHU || icon == SLOT_IMAGE_PSYDUCK ? TRUE : FALSE;
    case SLOT_PAYOUT_ROCKET:
        return icon == SLOT_IMAGE_ROCKET ? TRUE : FALSE;
    case SLOT_PAYOUT_7:
        return icon == SLOT_IMAGE_7 ? TRUE : FALSE;
    default:
        return FALSE;
    }
}

static u8 ReelIconToPayoutRank(s32 iconId)
{
    switch (iconId)
    {
    default:
    case SLOT_IMAGE_CHERRIES:
        return SLOT_PAYOUT_CHERRIES2;
    case SLOT_IMAGE_MAGNEMITE:
    case SLOT_IMAGE_SHELLDER:
        return SLOT_PAYOUT_MAGSHELL;
    case SLOT_IMAGE_PIKACHU:
    case SLOT_IMAGE_PSYDUCK:
        return SLOT_PAYOUT_PIKAPSY;
    case SLOT_IMAGE_ROCKET:
        return SLOT_PAYOUT_ROCKET;
    case SLOT_IMAGE_7:
        return SLOT_PAYOUT_7;
    }
}

static void CalcSlotBias(void)
{
    u16 rval = Random() / 4;
    s32 i;
    const u16 * biasChances = sReelBiasChances[sSlotMachineState->machineidx];
    for (i = 0; i < 6; i++)
    {
        if (rval < biasChances[i])
            break;
    }
    if (sSlotMachineState->machineBias < SLOT_PAYOUT_ROCKET)
    {
        if (sSlotMachineState->biasCooldown == 0)
        {
            if ((Random() & 0x3FFF) < biasChances[SLOT_PAYOUT_7])
                sSlotMachineState->biasCooldown = (Random() & 1) ? 5 : 60;
        }
        if (sSlotMachineState->biasCooldown != 0)
        {
            if (i == 0 && (Random() & 0x3FFF) < 0x2CCC) // 70%
                sSlotMachineState->biasCooldown = (Random() & 1) ? 5 : 60;
            sSlotMachineState->biasCooldown--;
        }
        sSlotMachineState->machineBias = i;
    }
}

static void ResetMachineBias(void)
{
    sSlotMachineState->machineBias = 0;
}

static u16 CalcPayout(void)
{
    u8 visibleIcons[9] = {};
    s32 i;
    s32 reel1pos, reel2pos, reel3pos;
    s32 bestMatch;

    for (i = 0; i < 5; i++)
        sSlotMachineState->winFlags[i] = FALSE;

    bestMatch = 0;
    reel1pos = sSlotMachineState->reelPositions[0];
    reel2pos = sSlotMachineState->reelPositions[1];
    reel3pos = sSlotMachineState->reelPositions[2];

    for (i = 0; i < 3; i++)
    {
        reel1pos++;
        if (reel1pos >= 21)
            reel1pos = 0;
        reel2pos++;
        if (reel2pos >= 21)
            reel2pos = 0;
        reel3pos++;
        if (reel3pos >= 21)
            reel3pos = 0;
        visibleIcons[0 * 3 + i] = sReelIconAnimByReelAndPos[0][reel1pos];
        visibleIcons[1 * 3 + i] = sReelIconAnimByReelAndPos[1][reel2pos];
        visibleIcons[2 * 3 + i] = sReelIconAnimByReelAndPos[2][reel3pos];
    }
    sSlotMachineState->payout = 0;
    for (i = 0; i < 5; i++)
    {
        if (sSlotMachineState->bet >= sRowAttributes[i][ROWATTR_MINBET])
        {
            int curMatch;
            if (TestReelIconAttribute(1, visibleIcons[sRowAttributes[i][ROWATTR_COL1POS]]))
                curMatch = TestReelIconAttribute(2, visibleIcons[sRowAttributes[i][ROWATTR_COL2POS]]) ? 2 : 1;
            else if (visibleIcons[sRowAttributes[i][ROWATTR_COL1POS]] == visibleIcons[sRowAttributes[i][ROWATTR_COL2POS]] && visibleIcons[sRowAttributes[i][ROWATTR_COL1POS]] == visibleIcons[sRowAttributes[i][ROWATTR_COL3POS]])
                curMatch = ReelIconToPayoutRank(visibleIcons[sRowAttributes[i][ROWATTR_COL1POS]]);
            else
                curMatch = 0;
            if (curMatch != 0)
            {
                sSlotMachineState->winFlags[i] = TRUE;
                sSlotMachineState->payout += sPayoutTable[curMatch];
            }
            if (curMatch > bestMatch)
                bestMatch = curMatch;
        }
    }
    return bestMatch;
}

static u16 GetPayout(void)
{
    return sSlotMachineState->payout;
}

static u8 GetPlayerBet(void)
{
    return sSlotMachineState->bet;
}

static bool32 GetWinFlagByLine(int a0)
{
    return sSlotMachineState->winFlags[a0];
}

static bool32 LoadSpriteGraphicsAndAllocateManager(void)
{
    s32 i;

    for (i = 0; i < NELEMS(sSpriteSheets); i++)
        LoadCompressedSpriteSheet(&sSpriteSheets[i]);
    LoadSpritePalettes(sSpritePalettes);
    sSlotMachineGfxManager = Alloc(sizeof(*sSlotMachineGfxManager));
    if (sSlotMachineGfxManager == NULL)
        return FALSE;
    InitGfxManager(sSlotMachineGfxManager);
    return TRUE;
}

static void DestroyGfxManager(void)
{
    if (sSlotMachineGfxManager != NULL)
    {
        Free(sSlotMachineGfxManager);
        sSlotMachineGfxManager = NULL;
    }
}

static void InitGfxManager(struct SlotMachineGfxManager * manager)
{
    s32 i, j;

    for (i = 0; i < 3; i++)
    {
        manager->field_00[i] = 0;
        for (j = 0; j < 5; j++)
        {
            manager->reelIconSprites[i][j] = NULL;
        }
    }
}

static void CreateReelIconSprites(void)
{
    struct Sprite * sprite;
    s32 i, j;
    s32 spriteId;
    s32 animId;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 5; j++)
        {
            spriteId = CreateSprite(&sSpriteTemplate_ReelIcons, 80 + 40 * i, 44 + 24 * j, 2);
            animId =  sReelIconAnimByReelAndPos[i][j];
            sprite = &gSprites[spriteId];
            StartSpriteAnim(sprite, animId);
            sprite->oam.paletteNum = IndexOfSpritePaletteTag(sReelIconPaletteTags[animId]);
            sprite->data[0] = i;
            sprite->data[1] = j;
            sprite->data[2] = j;
            sprite->data[3] = 0;
            sprite->oam.matrixNum = 0;
            sSlotMachineGfxManager->reelIconSprites[i][j] = sprite;
            sSlotMachineGfxManager->reelIconAffineParamPtr = (vu16 *)(OAM + 0 * sizeof(struct OamData) + offsetof(struct OamData, affineParam));
        }
    }
}

static void UpdateReelIconSprites(const s16 * reelPosPtr, const s16 * yposPtr)
{
    s32 i, j;
    s32 reelPos, ypos;

    for (i = 0; i < 3; i++)
    {
        reelPos = *reelPosPtr;
        ypos = *yposPtr * 8;
        for (j = 0; j < 5; j++)
        {
            sSlotMachineGfxManager->reelIconSprites[i][j]->y2 = ypos;
            {
                s32 animId = sReelIconAnimByReelAndPos[i][reelPos];
                struct Sprite * sprite = sSlotMachineGfxManager->reelIconSprites[i][j];
                StartSpriteAnim(sprite, animId);
            }
            {
                s32 animId = sReelIconAnimByReelAndPos[i][reelPos];
                struct Sprite * sprite = sSlotMachineGfxManager->reelIconSprites[i][j];
                StartSpriteAnim(sprite, animId);
            }
            sSlotMachineGfxManager->reelIconSprites[i][j]->oam.paletteNum = IndexOfSpritePaletteTag(sReelIconPaletteTags[sReelIconAnimByReelAndPos[i][reelPos]]);
            reelPos++;
            if (reelPos >= 21)
                reelPos = 0;
        }
        reelPosPtr++;
        yposPtr++;
    }
}

static void HBlankCB_SlotMachine(void)
{
    s32 vcount = REG_VCOUNT - 0x2B;
    if (vcount < 0x54u)
    {
        *sSlotMachineGfxManager->reelIconAffineParamPtr = sReelIconAffineParams[vcount];
        REG_BLDY = sReelIconBldY[vcount];
    }
    else
    {
        *sSlotMachineGfxManager->reelIconAffineParamPtr = 0x100;
        REG_BLDY = 0;
    }
}

static void CreateScoreDigitSprites(void)
{
    s32 i;
    s32 spriteId;

    for (i = 0; i < 4; i++)
    {
        spriteId = CreateSprite(&sSpriteTemplate_Digits, 0x55 + 7 * i, 30, 0);
        sSlotMachineGfxManager->creditDigitSprites[i] = &gSprites[spriteId];
        spriteId = CreateSprite(&sSpriteTemplate_Digits, 0x85 + 7 * i, 30, 0);
        sSlotMachineGfxManager->payoutDigitSprites[i] = &gSprites[spriteId];
    }
}

static void UpdateCoinsDisplay(void)
{
    s32 coins = GetCoins();
    s32 payout = GetPayout();
    s32 i;
    s32 divisor = 1000;
    s32 quotient;

    for (i = 0; i < 4; i++)
    {
        quotient = coins / divisor;
        StartSpriteAnim(sSlotMachineGfxManager->creditDigitSprites[i], quotient);
        coins -= quotient * divisor;
        quotient = payout / divisor;
        StartSpriteAnim(sSlotMachineGfxManager->payoutDigitSprites[i], quotient);
        payout -= quotient * divisor;
        divisor /= 10;
    }
}

static void CreateClefairySprites(void)
{
    s32 spriteId;

    spriteId = CreateSprite(&sSpriteTemplate_Clefairy, 0x10, 0x88, 1);
    sSlotMachineGfxManager->clefairySprites[0] = &gSprites[spriteId];
    spriteId = CreateSprite(&sSpriteTemplate_Clefairy, 0xE0, 0x88, 1);
    sSlotMachineGfxManager->clefairySprites[1] = &gSprites[spriteId];
    sSlotMachineGfxManager->clefairySprites[1]->hFlip = TRUE;
}

static void SetClefairySpriteAnim(u8 animId)
{
    s32 i;
    for (i = 0; i < 2; i++)
    {
        StartSpriteAnim(sSlotMachineGfxManager->clefairySprites[i], animId);
    }
}

static bool32 CreateSlotMachine(void)
{
    s32 i;

    struct SlotMachineSetupTaskData * ptr = Alloc(sizeof(struct SlotMachineSetupTaskData));
    if (ptr == NULL)
        return FALSE;
    for (i = 0; i < 8; i++)
        ptr->tasks[i].active = 0;
    ptr->yesNoMenuActive = FALSE;
    SetWordTaskArg(CreateTask(Task_SlotMachine, 2), 0, (uintptr_t)ptr);
    return FALSE;
}

static void DestroySlotMachine(void)
{
    if (FuncIsActiveTask(Task_SlotMachine))
    {
        Free(GetSlotMachineSetupTaskDataPtr());
        DestroyTask(FindTaskIdByFunc(Task_SlotMachine));
    }
    DestroyGfxManager();
    FreeAllWindowBuffers();
}

static void Task_SlotMachine(u8 taskId)
{
    struct SlotMachineSetupTaskData * ptr = (void *)GetWordTaskArg(taskId, 0);
    s32 i;

    for (i = 0; i < 8; i++)
    {
        if (ptr->tasks[i].active)
            ptr->tasks[i].active = sSlotMachineSetupTasks[ptr->tasks[i].funcno](&ptr->tasks[i].state, ptr);
    }
}

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

static struct SlotMachineSetupTaskData * GetSlotMachineSetupTaskDataPtr(void)
{
    return (void *)GetWordTaskArg(FindTaskIdByFunc(Task_SlotMachine), 0);
}

static void SetSlotMachineSetupTask(u16 funcno, u8 taskId)
{
    struct SlotMachineSetupTaskData * ptr = GetSlotMachineSetupTaskDataPtr();
    ptr->tasks[taskId].funcno = funcno;
    ptr->tasks[taskId].state = 0;
    ptr->tasks[taskId].active = sSlotMachineSetupTasks[funcno](&ptr->tasks[taskId].state, ptr);
}

static bool32 IsSlotMachineSetupTaskActive(u8 taskId)
{
    return GetSlotMachineSetupTaskDataPtr()->tasks[taskId].active;
}

static inline void LoadColor(u16 color, u16 *pal)
{
    *pal = color;
    LoadPalette(pal, 0x00, 0x02);
}

static bool8 SlotsTask_GraphicsInit(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    u16 pal[2];
    u8 textColor[3];
    u32 x;

    switch (*state)
    {
    case 0:
        BlendPalettes(0xFFFFFFFF, 16, RGB_BLACK);
        (*state)++;
        break;
    case 1:
        SetVBlankCallback(NULL);
        ResetSpriteData();
        FreeAllSpritePalettes();
        RequestDma3Fill(0, (void *)OAM, OAM_SIZE, DMA3_32BIT);
        RequestDma3Fill(0, (void *)VRAM, 0x20, DMA3_32BIT);
        RequestDma3Fill(0, (void *)(VRAM + 0xC000), 0x20, DMA3_32BIT);
        SetGpuReg(REG_OFFSET_DISPCNT, 0);
        ResetBgPositions();
        ResetBgsAndClearDma3BusyFlags(FALSE);
        InitBgsFromTemplates(0, sBgTemplates, NELEMS(sBgTemplates));
        InitWindows(sWindowTemplates);

        SetBgTilemapBuffer(3, ptr->bg3TilemapBuffer);
        FillBgTilemapBufferRect_Palette0(3, 0, 0, 0, 32, 32);
        CopyBgTilemapBufferToVram(3);

        ResetTempTileDataBuffers();
        DecompressAndCopyTileDataToVram(2, sBg2Tiles_00, 0, 0x00, 0);
        DecompressAndCopyTileDataToVram(2, sBg2Tiles_C0, 0, 0xC0, 0);
        SetBgTilemapBuffer(2, ptr->bg2TilemapBuffer);
        CopyToBgTilemapBuffer(2, sBg2Map, 0, 0x00);
        CopyBgTilemapBufferToVram(2);
        LoadPalette(sBgPal_00, 0x00, 0xA0);
        LoadPalette(sBgPal_50, 0x50, 0x20);
        LoadPalette(sBgPal_70, 0x70, 0x60);
        LoadColor(RGB(30, 30, 31), pal);
        LoadUserWindowBorderGfx(0, 0x00A, 0xD0);
        DrawWindowBorderWithStdpal3(0, 0x001, 0xF0);

        SetBgTilemapBuffer(0, ptr->bg0TilemapBuffer);
        FillBgTilemapBufferRect_Palette0(0, 0, 0, 2, 32, 30);
        DecompressAndCopyTileDataToVram(1, sBg1Tiles, 0, 0, 0);
        DecompressAndCopyTileDataToVram(1, sBg1Map, 0, 0, 1);
        CopyBgTilemapBufferToVram(1);

        LoadPalette(stdpal_get(2), 0xE0, 0x20);
        FillWindowPixelBuffer(1, 0xFF);
        PutWindowTilemap(1);

        x = 236 - GetStringWidth(0, gString_SlotMachineControls, 0);
        textColor[0] = TEXT_DYNAMIC_COLOR_6;
        textColor[1] = TEXT_COLOR_WHITE;
        textColor[2] = TEXT_COLOR_DARK_GRAY;
        AddTextPrinterParameterized3(1, 0, x, 0, textColor, 0, gString_SlotMachineControls);
        CopyBgTilemapBufferToVram(0);

        SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_MODE_0 | 0x20 | DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON);
        SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG3 | BLDCNT_TGT1_OBJ | BLDCNT_TGT1_BD | BLDCNT_EFFECT_DARKEN);
        LoadSpriteGraphicsAndAllocateManager();
        CreateReelIconSprites();
        CreateScoreDigitSprites();
        CreateClefairySprites();
        UpdateCoinsDisplay();
        BlendPalettes(0xFFFFFFFF, 0x10, RGB_BLACK);
        SetVBlankCallback(VBlankCB_SlotMachine);
        SetHBlankCallback(HBlankCB_SlotMachine);
        (*state)++;
        break;
    case 2:
        if (!FreeTempTileDataBuffersIfPossible())
        {
            ShowBg(0);
            ShowBg(3);
            ShowBg(2);
            HideBg(1);
            InitReelButtonTileMem();
            BlendPalettes(0xFFFFFFFF, 0x10, RGB_BLACK);
            BeginNormalPaletteFade(0xFFFFFFFF, -1, 16, 0, RGB_BLACK);
            EnableInterrupts(INTR_FLAG_VBLANK | INTR_FLAG_HBLANK);
            (*state)++;
        }
        break;
    case 3:
        UpdatePaletteFade();
        if (!gPaletteFade.active)
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_FadeOut(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        BeginNormalPaletteFade(0xFFFFFFFF, -1, 0, 16, 0);
        (*state)++;
        break;
    case 1:
        if (!gPaletteFade.active)
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_UpdateLineStates(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        SetLineStatesByBet(GetBgTilemapBuffer(2));
        CopyBgTilemapBufferToVram(2);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_ClefairyUpdateOnReelsStart(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    SetClefairySpriteAnim(1);
    return FALSE;
}

static bool8 SlotsTask_StartClefairyDanceAndWinningLineFlash(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    SetClefairySpriteAnim(2);
    CreateTask(Task_FlashWinningLine, 3);
    return FALSE;
}

static bool8 SlotsTask_StopWinningLineFlashTask(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        SignalStopWinningLineFlashTask();
        (*state)++;
        break;
    case 1:
        if (!FuncIsActiveTask(Task_FlashWinningLine))
        {
            SetClefairySpriteAnim(0);
            return FALSE;
        }
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_ClefairyFainted(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    SetClefairySpriteAnim(3);
    return FALSE;
}

static bool8 SlotsTask_ClefairyNeutral(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    SetClefairySpriteAnim(0);
    return FALSE;
}

static bool8 SlotsTask_UpdateCoinsDisplay(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    UpdateCoinsDisplay();
    return FALSE;
}

static bool8 SlotsTask_MessageOutOfCoins(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        Slot_PrintOnWindow0(gString_OutOfCoins);
        CopyWindowToVram(0, COPYWIN_BOTH);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_AskQuitPlaying(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        Slot_PrintOnWindow0(gString_QuitPlaying);
        Slot_CreateYesNoMenu(0);
        CopyWindowToVram(0, COPYWIN_BOTH);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_DestroyYesNoMenu(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        Slot_ClearWindow0();
        Slot_DestroyYesNoMenu();
        CopyWindowToVram(0, COPYWIN_BOTH);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_PressReelButton(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        SetReelButtonPressed(ptr->reelButtonToPress);
        CopyBgTilemapBufferToVram(2);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_ReleaseReelButtons(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        ReleaseReelButtons();
        CopyBgTilemapBufferToVram(2);
        (*state)++;
        break;
    case 1:
        if (!IsDma3ManagerBusyWithBgCopy())
            return FALSE;
        break;
    }
    return TRUE;
}

static bool8 SlotsTask_ShowHelp(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        SetGpuReg(REG_OFFSET_WININ, WININ_WIN0_BG0 | WININ_WIN0_BG1 | WININ_WIN0_BG2 | WININ_WIN0_BG3 | WININ_WIN0_OBJ | WININ_WIN0_CLR);
        SetGpuReg(REG_OFFSET_WINOUT, WINOUT_WIN01_BG0 | WINOUT_WIN01_BG2 | WINOUT_WIN01_BG3 | WINOUT_WIN01_OBJ | WINOUT_WIN01_CLR);
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(0,   0));
        SetGpuReg(REG_OFFSET_WIN1H, WIN_RANGE(0, 160));
        ShowBg(1);
        PlaySE(SE_WIN_OPEN);
        ptr->bg1X = 0;
        (*state)++;
        break;
    case 1:
        ptr->bg1X += 16;
        if (ptr->bg1X >= 256)
        {
            ptr->bg1X = 256;
            (*state)++;
        }
        ChangeBgX(1, 256 * (256 - ptr->bg1X), 0);
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(0, ptr->bg1X));
        break;
    case 2:
        return FALSE;
    }
    return TRUE;
}

static bool8 SlotsTask_HideHelp(u8 * state, struct SlotMachineSetupTaskData * ptr)
{
    switch (*state)
    {
    case 0:
        PlaySE(SE_WIN_OPEN);
        (*state)++;
        // fallthrough
    case 1:
        ptr->bg1X -= 16;
        if (ptr->bg1X <= 0)
        {
            ptr->bg1X = 0;
            (*state)++;
        }
        ChangeBgX(1, 256 * (256 - ptr->bg1X), 0);
        SetGpuReg(REG_OFFSET_WIN0H, WIN_RANGE(0, ptr->bg1X));
        break;
    case 2:
        HideBg(1);
        ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        (*state)++;
        break;
    case 3:
        return FALSE;
    }
    return TRUE;
}

static void Slot_PrintOnWindow0(const u8 * str)
{
    FillWindowPixelBuffer(0, PIXEL_FILL(1));
    PutWindowTilemap(0);
    DrawTextBorderOuter(0, 0x001, 15);
    AddTextPrinterParameterized5(0, 2, str, 1, 2, TEXT_SPEED_FF, NULL, 1, 2);
}

static void Slot_ClearWindow0(void)
{
    rbox_fill_rectangle(0);
}

static void SetLineStatesByBet(u16 * bgTilemapBuffer)
{
    switch (GetPlayerBet())
    {
    case 0:
        SetLineState(bgTilemapBuffer, 0, 4);
        SetLineState(bgTilemapBuffer, 1, 4);
        SetLineState(bgTilemapBuffer, 2, 4);
        SetLineState(bgTilemapBuffer, 3, 4);
        SetLineState(bgTilemapBuffer, 4, 4);
        break;
    case 3:
        SetLineState(bgTilemapBuffer, 0, 5);
        SetLineState(bgTilemapBuffer, 4, 5);
    case 2:
        SetLineState(bgTilemapBuffer, 1, 5);
        SetLineState(bgTilemapBuffer, 3, 5);
    case 1:
        SetLineState(bgTilemapBuffer, 2, 5);
        break;
    }
}

static void SetLineState(u16 * bgTilemapBuffer, u16 whichLine, u16 paletteNum)
{
    s32 i;
    const u16 * tileIdxs = sLineStateTileIdxs[whichLine].tiles;
    u16 palMask = (paletteNum & 0xF) << 12;

    for (i = 0; i < sLineStateTileIdxs[whichLine].count; i++)
    {
        bgTilemapBuffer[*tileIdxs] &= 0x0FFF;
        bgTilemapBuffer[*tileIdxs] |= palMask;
        tileIdxs++;
    }
}

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

    switch (data[0])
    {
    case 0:
        LoadPalette(sBgPal_50, 0x60, 0x20);
        for (i = 0; i < 5; i++)
        {
            if (GetWinFlagByLine(i))
                SetLineState(GetBgTilemapBuffer(2), i, 6);
        }
        CopyBgTilemapBufferToVram(2);
        data[0]++;
        break;
    case 1:
        if (data[1] == 0)
        {
            u16 y = gSineTable[data[2]] >> 7;
            LoadPalette(&sBgPal_VictoryFlash[16 * y], 0x10, 0x20);
            data[2] += 32;
            data[2] &= 0x7F;
            data[1] = 8;
        }
        else
            data[1]--;

        if (data[3] == 0)
        {
            data[4] += 8;
            data[4] &= 0x7F;
            data[5] = gSineTable[data[4]] >> 5;
            BlendPalettes(0x00000040, data[5], RGB_BLACK);
        }
        else
        {
            data[4]++;
            if (data[4] > 1)
            {
                data[4] = 0;
                data[5]++;
                data[5] &= 1;
                BlendPalettes(0x00000040, data[5] * 8, RGB_BLACK);
            }
        }

        for (i = 0; i < NELEMS(sWInningLineFlashPalIdxs); i++)
        {
            gPlttBufferFaded[sWInningLineFlashPalIdxs[i] + 0x60] = gPlttBufferUnfaded[sWInningLineFlashPalIdxs[i] + 0x60];
        }
        break;
    case 2:
        for (i = 0; i < 5; i++)
        {
            if (GetWinFlagByLine(i))
                SetLineState(GetBgTilemapBuffer(2), i, 4);
        }
        LoadPalette(sBgPal_10, 0x10, 0x20);
        CopyBgTilemapBufferToVram(2);
        data[0]++;
        break;
    case 3:
        if (!IsDma3ManagerBusyWithBgCopy())
            DestroyTask(taskId);
        break;
    }
}

static void SignalStopWinningLineFlashTask(void)
{
    gTasks[FindTaskIdByFunc(Task_FlashWinningLine)].data[0] = 2;
}

static void Slot_CreateYesNoMenu(u8 cursorPos)
{
    CreateYesNoMenu(&sYesNoWindowTemplate, 2, 0, 2, 10, 13, cursorPos);
    Menu_MoveCursorNoWrapAround(cursorPos);
    GetSlotMachineSetupTaskDataPtr()->yesNoMenuActive = TRUE;
}

static void Slot_DestroyYesNoMenu(void)
{
    struct SlotMachineSetupTaskData * data = GetSlotMachineSetupTaskDataPtr();
    if (data->yesNoMenuActive)
    {
        DestroyYesNoMenu();
        data->yesNoMenuActive = FALSE;
    }
}

static void InitReelButtonTileMem(void)
{
    s32 i, j;
    struct SlotMachineSetupTaskData * data = GetSlotMachineSetupTaskDataPtr();
    u16 * buffer = GetBgTilemapBuffer(2);

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 4; j++)
        {
            u16 idx = sReelButtonMapTileIdxs[i][j];
            data->buttonReleasedTiles[i][j] = buffer[idx];
            data->buttonPressedTiles[i][j] = j + 0xC0;
        }
    }
}

static void SetReelButtonPressed(u8 reel)
{
    if (reel < 3)
    {
        s32 i;
        struct SlotMachineSetupTaskData * data = GetSlotMachineSetupTaskDataPtr();
        u16 * buffer = GetBgTilemapBuffer(2);
        for (i = 0; i < 4; i++)
        {
            u16 idx = sReelButtonMapTileIdxs[reel][i];
            buffer[idx] = data->buttonPressedTiles[reel][i];
        }
    }
}

static void ReleaseReelButtons(void)
{
    s32 i, j;
    struct SlotMachineSetupTaskData * data = GetSlotMachineSetupTaskDataPtr();
    u16 * buffer = GetBgTilemapBuffer(2);

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 4; j++)
        {
            u16 idx = sReelButtonMapTileIdxs[i][j];
            buffer[idx] = data->buttonReleasedTiles[i][j];
        }
    }
}

static void PressReelButton(u8 reel, u8 taskId)
{
    GetSlotMachineSetupTaskDataPtr()->reelButtonToPress = reel;
    SetSlotMachineSetupTask(SLOTTASK_PRESS_BUTTON, taskId);
}
