#include "global.h"
#include "battle.h" // to get rid of once gMonSpritesGfxPtr is put elsewhere
#include "bg.h"
#include "data2.h"
#include "decompress.h"
#include "event_data.h"
#include "gpu_regs.h"
#include "graphics.h"
#include "main.h"
#include "alloc.h"
#include "menu.h"
#include "menu_helpers.h"
#include "m4a.h"
#include "palette.h"
#include "party_menu.h"
#include "pokeblock.h"
#include "pokemon.h"
#include "sprite.h"
#include "string_util.h"
#include "strings.h"
#include "sound.h"
#include "task.h"
#include "text_window.h"
#include "trig.h"
#include "util.h"

struct PokeblockFeedStruct
{
    struct Sprite *monSpritePtr;
    struct Sprite savedMonSprite;
    u8 tilemapBuffer[0x808];
    s16 field_850[0x200];
    s16 field_C50[0x200];
    u8 field_1050;
    u8 animId;
    u8 field_1052;
    bool8 noMonFlip;
    u16 species;
    u16 field_1056;
    u16 field_1058;
    u8 nature;
    u8 monSpriteId_;
    u8 field_105C;
    u8 monSpriteId;
    u8 pokeblockCaseSpriteId;
    u8 pokeblockSpriteId;
    s16 field_1060[15];
    s16 loadGfxState;
    u8 unused;
};

extern u16 gSpecialVar_ItemId;
extern struct MusicPlayerInfo gMPlayInfo_BGM;

extern const struct CompressedSpriteSheet gMonFrontPicTable[];
extern const u16 gUnknown_0860F074[];

extern bool8 sub_81221EC(void);

// this file's functions
static void HandleInitBackgrounds(void);
static void HandleInitWindows(void);
static void LaunchPokeblockFeedTask(void);
static void SetPokeblockSpritePal(u8 pokeblockCaseId);
static void sub_817A5CC(void);
static void sub_8148108(u8 spriteId, bool8 a1);
static void DoPokeblockCaseThrowEffect(u8 spriteId, bool8 arg1);
static void PrepareMonToMoveToPokeblock(u8 spriteId);
static void Task_HandleMonAtePokeblock(u8 taskId);
static void Task_PaletteFadeToReturn(u8 taskId);
static void sub_817A634(void);
static void sub_817A468(struct Sprite *sprite);
static void sub_817AB68(void);
static void sub_817AA54(void);
static bool8 sub_817A91C(void);
static bool8 FreeMonSpriteOamMatrix(void);
static bool8 sub_817A9E4(void);
static bool8 LoadMonAndSceneGfx(struct Pokemon *mon);
static u8 CreatePokeblockSprite(void);
static u8 CreatePokeblockCaseSpriteForFeeding(void);
static u8 CreateMonSprite(struct Pokemon *mon);
static void SpriteCB_ThrownPokeblock(struct Sprite* sprite);

// ram variables
EWRAM_DATA static struct PokeblockFeedStruct *sPokeblockFeed = NULL;
EWRAM_DATA static struct CompressedSpritePalette sPokeblockSpritePal = {0};

// const rom data
static const u8 sNatureToMonPokeblockAnim[][2] =
{
    [NATURE_HARDY] = {  0, 0 },
    [NATURE_LONELY] = {  3, 0 },
    [NATURE_BRAVE] = {  4, 1 },
    [NATURE_ADAMANT] = {  5, 0 },
    [NATURE_NAUGHTY] = { 10, 0 },
    [NATURE_BOLD] = { 13, 0 },
    [NATURE_DOCILE] = { 15, 0 },
    [NATURE_RELAXED] = { 16, 2 },
    [NATURE_IMPISH] = { 18, 0 },
    [NATURE_LAX] = { 19, 0 },
    [NATURE_TIMID] = { 20, 0 },
    [NATURE_HASTY] = { 25, 0 },
    [NATURE_SERIOUS] = { 27, 3 },
    [NATURE_JOLLY] = { 28, 0 },
    [NATURE_NAIVE] = { 29, 0 },
    [NATURE_MODEST] = { 33, 4 },
    [NATURE_MILD] = { 36, 0 },
    [NATURE_QUIET] = { 37, 0 },
    [NATURE_BASHFUL] = { 39, 0 },
    [NATURE_RASH] = { 42, 0 },
    [NATURE_CALM] = { 45, 0 },
    [NATURE_GENTLE] = { 46, 5 },
    [NATURE_SASSY] = { 47, 6 },
    [NATURE_CAREFUL] = { 48, 0 },
    [NATURE_QUIRKY] = { 53, 0 },
};

static const s16 sMonPokeblockAnims[][10] =
{
    // HARDY
    {   0,   4,   0,   8,  24,   0,   0,   0,  12,   0},
    {   0,   4,   0,  16,  24,   0,   0,   0,  12,   0},
    {   0,   4,   0,  32,  32,   0,   0,   0,  16,   1},

    // LONELY
    {   0,   3,   6,   0,  48,   0,   0,   0,  24,   1},

    // BRAVE
    {  64,  16, -24,   0,  32,   0,   0,   0,   0,   1},

    // ADAMANT
    {   0,   4,   8,   0,  16,   0,  -8,   0,   0,   0},
    {   0,   0,   0,   0,  16,   0,   0,   0,   0,   0},
    {   0,   4,   8,   0,  16,   0,  -8,   0,   0,   0},
    {   0,   0,   0,   0,  16,   0,   0,   0,   0,   0},
    {   0,   4, -16,   0,   4,   0,  16,   0,   0,   1},

    // NAUGHTY
    {   0,   3,   6,   0,  12,   0,   0,   0,   6,   0},
    {   0,   3,  -6,   0,  12,   0,   0,   0,   6,   0},
    {   0,  16,  16,   0,  45,   1,   0,   0,   0,   1},

    // BOLD
    {   0,  16,   0,  24,  32,   0,   0,   0,  16,   0},
    {   0,  16,   0,  23,  32,   0,   0,   0,  16,   1},

    // DOCILE
    {   0,   0,   0,   0,  80,   0,   0,   0,   0,   1},

    // RELAXED
    {   0,   2,   8,   0,  32,   0,   0,   0,   0,   0},
    {   0,   2,  -8,   0,  32,   0,   0,   0,   0,   1},

    // IMPISH
    {   0,  32,   2,   1,  48,   1,   0,   0,  24,   1},

    // LAX
    {   0,   2,  16,  16, 128,   0,   0,   0,   0,   1},

    // TIMID
    {   0,   2,  -8,   0,  48,   0, -24,   0,   0,   0},
    {   0,   0,   0,   0,   8,   0,   0,   0,   0,   0},
    {  64,  32,   2,   0,  36,   0,   0,   0,   0,   0},
    {   0,   0,   0,   0,   8,   0,   0,   0,   0,   0},
    {   0,   2,   8,   0,  48,   0,  24,   0,   0,   1},

    // HASTY
    {  64,  24,  16,   0,  32,   0,   0,   0,   0,   0},
    {   0,  28,   2,   1,  32,   1,   0,   0,  16,   1},

    // SERIOUS
    {   0,   0,   0,   0,  32,   0,   0,   0,   0,   1},

    // JOLLY
    {  64,  16, -16,   2,  48,   0,   0,   0,  32,   1},

    // NAIVE
    {   0,  12,  -8,   4,  24,   0,   8,   0,  12,   0},
    {   0,  12,   8,   8,  24,   0, -16,   0,  12,   0},
    {   0,  12,  -8,  16,  24,   0,  16,   0,  12,   0},
    {   0,  12,   8,  28,  24,   0,  -8,   0,  12,   1},

    // MODEST
    {   0,   0,   0,   0,   8,   0,   0,   0,   0,   0},
    {  64,  16,  -4,   0,  32,   0,   0,   0,   0,   0},
    {   0,   0,   0,   0,   8,   0,   0,   0,   0,   1},

    // MILD
    { 128,   4,   0,   8,  64,   0,   0,   0,   0,   1},

    // QUIET
    {   0,   2,  16,   0,  48,   0,   0,   0,   0,   0},
    { 128,   2,  16,   0,  48,   0,   0,   0,   0,   1},

    // BASHFUL
    {   0,   2,  -4,   0,  48,   0, -48,   0,   0,   0},
    {   0,   0,   0,   0,  80,   0,   0,   0,   0,   0},
    {   0,   2,   8,   0,  24,   0,  48,   0,   0,   1},

    // RASH
    {  64,   4,  64,  58,  52,   0, -88,   0,   0,   0},
    {   0,   0,   0,   0,  80,   0,   0,   0,   0,   0},
    {   0,  24,  80,   0,  32,   0,  88,   0,   0,   1},

    // CALM
    {   0,   2,  16,   4,  64,   0,   0,   0,   0,   1},

    // GENTLE
    {   0,   0,   0,   0,  32,   0,   0,   0,   0,   1},

    // SASSY
    {   0,   0,   0,   0,  42,   0,   0,   0,   0,   1},

    // CAREFUL
    {   0,   4,   0,   8,  24,   0,   0,   0,  12,   0},
    {   0,   0,   0,   0,  12,   0,   0,   0,   0,   0},
    {   0,   4,   0,  12,  24,   0,   0,   0,  12,   0},
    {   0,   0,   0,   0,  12,   0,   0,   0,   0,   0},
    {   0,   4,   0,   4,  24,   0,   0,   0,  12,   1},

    // QUIRKY
    {   0,   4,  16,  12,  64,   0,   0,   0,   0,   0},
    {   0,  -4,  16,  12,  64,   0,   0,   0,   0,   1},
};

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

static const union AffineAnimCmd sSpriteAffineAnim_8411EA0[] =
{
    AFFINEANIMCMD_FRAME(0, 0, 12, 1),
    AFFINEANIMCMD_FRAME(0, 0, 0, 30),
    AFFINEANIMCMD_FRAME(0, 0, -12, 1),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411EC0[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0, 0, 12, 1),
    AFFINEANIMCMD_FRAME(0, 0, 0, 28),
    AFFINEANIMCMD_FRAME(0, 0, -4, 3),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411EE8[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 32),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 16),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411F08[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 32),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 16),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411F30[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411F50[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411F78[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 32),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411F98[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 32),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411FC0[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 4),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 24),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 4),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8411FE0[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -1, 4),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 24),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 4),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8412008[] =
{
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 24),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -12, 2),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8412028[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 1, 24),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 16),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -12, 2),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd *const sSpriteAffineAnimTable_85F04FC[] =
{
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411EA0,
    sSpriteAffineAnim_8411EE8,
    sSpriteAffineAnim_8411F30,
    sSpriteAffineAnim_8411F78,
    sSpriteAffineAnim_8411FC0,
    sSpriteAffineAnim_8412008,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411EC0,
    sSpriteAffineAnim_8411F08,
    sSpriteAffineAnim_8411F50,
    sSpriteAffineAnim_8411F98,
    sSpriteAffineAnim_8411FE0,
    sSpriteAffineAnim_8412028,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
    sSpriteAffineAnim_8411E90,
};

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

static const struct WindowTemplate sWindowTemplates[] =
{
    {0, 1, 0xF, 0x1C, 4, 0xF, 0xA},
    DUMMY_WIN_TEMPLATE
};

static const u32* const sPokeblocksPals[] =
{
    gPokeblockRed_Pal,
    gPokeblockBlue_Pal,
    gPokeblockPink_Pal,
    gPokeblockGreen_Pal,
    gPokeblockYellow_Pal,
    gPokeblockPurple_Pal,
    gPokeblockIndigo_Pal,
    gPokeblockBrown_Pal,
    gPokeblockLiteBlue_Pal,
    gPokeblockOlive_Pal,
    gPokeblockGray_Pal,
    gPokeblockBlack_Pal,
    gPokeblockWhite_Pal,
    gPokeblockGold_Pal
};

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

static const union AffineAnimCmd *const sSpriteAffineAnimTable_MonNoFlip[] =
{
    sSpriteAffineAnim_84120DC
};

static const union AffineAnimCmd sSpriteAffineAnim_84120F0[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 16, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 16, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 16, 1),
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sSpriteAffineAnim_8412148[] =
{
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 8, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, 0, 8),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -16, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -16, 1),
    AFFINEANIMCMD_FRAME(0x0, 0x0, -16, 1),
    AFFINEANIMCMD_FRAME(-0x100, 0x100, 0, 0),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd *const sSpriteAffineAnimTable_85F0664[] =
{
    sSpriteAffineAnim_84120DC
};

static const union AffineAnimCmd *const sSpriteAffineAnimTable_85F0668[] =
{
    sSpriteAffineAnim_84120F0
};

static const union AffineAnimCmd *const sSpriteAffineAnimTable_85F066C[] =
{
    sSpriteAffineAnim_8412148
};

static const struct OamData sThrownPokeblockOamData =
{
    .y = 0,
    .affineMode = 3,
    .objMode = 0,
    .mosaic = 0,
    .bpp = 0,
    .shape = 0,
    .x = 0,
    .matrixNum = 0,
    .size = 0,
    .tileNum = 0,
    .priority = 1,
    .paletteNum = 0,
    .affineParam = 0,
};

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

static const union AnimCmd *const sThrownPokeblockAnimTable[] =
{
    sThrownPokeblockSpriteAnim,
};

static const union AffineAnimCmd sSpriteAffineAnim_84121C0[] =
{
    AFFINEANIMCMD_FRAME(0x100, 0x100, 0, 0),
    AFFINEANIMCMD_FRAME(-8, -8, 0, 1),
    AFFINEANIMCMD_JUMP(1)
};

static const union AffineAnimCmd *const sThrownPokeblockAffineAnimTable[] =
{
    sSpriteAffineAnim_84121C0
};

static const struct CompressedSpriteSheet sPokeblock_SpriteSheet =
{
    gPokeblock_Gfx, 0x20, GFX_TAG_POKEBLOCK
};

static const struct SpriteTemplate sThrownPokeblockSpriteTemplate =
{
    .tileTag = GFX_TAG_POKEBLOCK,
    .paletteTag = GFX_TAG_POKEBLOCK,
    .oam = &sThrownPokeblockOamData,
    .anims = sThrownPokeblockAnimTable,
    .images = NULL,
    .affineAnims = sThrownPokeblockAffineAnimTable,
    .callback = SpriteCB_ThrownPokeblock
};

// code
static void CB2_PokeblockFeed(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    do_scheduled_bg_tilemap_copies_to_vram();
    UpdatePaletteFade();
}

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

static bool8 TransitionToPokeblockFeedScene(void)
{
    switch (gMain.state)
    {
    case 0:
        sPokeblockFeed = AllocZeroed(sizeof(*sPokeblockFeed));
        SetVBlankHBlankCallbacksToNull();
        clear_scheduled_bg_copies_to_vram();
        gMain.state++;
        break;
    case 1:
        ResetPaletteFade();
        gPaletteFade.bufferTransferDisabled = 1;
        gMain.state++;
        break;
    case 2:
        ResetSpriteData();
        gMain.state++;
        break;
    case 3:
        FreeAllSpritePalettes();
        gMain.state++;
        break;
    case 4:
        AllocateMonSpritesGfx();
        gMain.state++;
        break;
    case 5:
        HandleInitBackgrounds();
        gMain.state++;
        break;
    case 6:
        HandleInitWindows();
        gMain.state++;
        break;
    case 7:
        if (LoadMonAndSceneGfx(&gPlayerParty[gPokeblockMonId]))
        {
            gMain.state++;
        }
        break;
    case 8:
        sPokeblockFeed->pokeblockCaseSpriteId = CreatePokeblockCaseSpriteForFeeding();
        gMain.state++;
        break;
    case 9:
        sPokeblockFeed->monSpriteId = CreateMonSprite(&gPlayerParty[gPokeblockMonId]);
        gMain.state++;
        break;
    case 10:
        SetWindowBorderStyle(0, 1, 1, 14);
        gMain.state++;
        break;
    case 11:
        LaunchPokeblockFeedTask();
        gMain.state++;
        break;
    case 12:
        BlendPalettes(-1, 0x10, 0);
        gMain.state++;
        break;
    case 13:
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, 0);
        gPaletteFade.bufferTransferDisabled = 0;
        gMain.state++;
        break;
    default:
        SetVBlankCallback(VBlankCB_PokeblockFeed);
        SetMainCallback2(CB2_PokeblockFeed);
        return TRUE;
    }
    return FALSE;
}

void CB2_PreparePokeblockFeedScene(void)
{
    while (1)
    {
        if (sub_81221EC() == TRUE)
            break;
        if (TransitionToPokeblockFeedScene() == TRUE)
            break;
        if (sub_81221AC() == TRUE)
            break;
    }
}

static void HandleInitBackgrounds(void)
{
    ResetVramOamAndBgCntRegs();

    ResetBgsAndClearDma3BusyFlags(0);
    InitBgsFromTemplates(0, sBackgroundTemplates, ARRAY_COUNT(sBackgroundTemplates));
    SetBgTilemapBuffer(1, sPokeblockFeed->tilemapBuffer);
    ResetAllBgsCoordinates();
    schedule_bg_copy_tilemap_to_vram(1);

    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);

    ShowBg(0);
    ShowBg(1);

    SetGpuReg(REG_OFFSET_BLDCNT, 0);
}

static bool8 LoadMonAndSceneGfx(struct Pokemon *mon)
{
    u16 species;
    u32 personality, trainerId;
    const struct CompressedSpritePalette *palette;

    switch (sPokeblockFeed->loadGfxState)
    {
    case 0:
        species = GetMonData(mon, MON_DATA_SPECIES2);
        personality = GetMonData(mon, MON_DATA_PERSONALITY);
        HandleLoadSpecialPokePic_2(&gMonFrontPicTable[species], gMonSpritesGfxPtr->sprites[1], species, personality);
        sPokeblockFeed->loadGfxState++;
        break;
    case 1:
        species = GetMonData(mon, MON_DATA_SPECIES2);
        personality = GetMonData(mon, MON_DATA_PERSONALITY);
        trainerId = GetMonData(mon, MON_DATA_OT_ID);
        palette = GetMonSpritePalStructFromOtIdPersonality(species, trainerId, personality);

        LoadCompressedSpritePalette(palette);
        SetMultiuseSpriteTemplateToPokemon(palette->tag, 1);
        sPokeblockFeed->loadGfxState++;
        break;
    case 2:
        LoadCompressedSpriteSheet(&gPokeblockCase_SpriteSheet);
        sPokeblockFeed->loadGfxState++;
        break;
    case 3:
        LoadCompressedSpritePalette(&gPokeblockCase_SpritePal);
        sPokeblockFeed->loadGfxState++;
        break;
    case 4:
        LoadCompressedSpriteSheet(&sPokeblock_SpriteSheet);
        sPokeblockFeed->loadGfxState++;
        break;
    case 5:
        SetPokeblockSpritePal(gSpecialVar_ItemId);
        LoadCompressedSpritePalette(&sPokeblockSpritePal);
        sPokeblockFeed->loadGfxState++;
        break;
    case 6:
        reset_temp_tile_data_buffers();
        decompress_and_copy_tile_data_to_vram(1, gBattleTerrainTiles_Building, 0, 0, 0);
        sPokeblockFeed->loadGfxState++;
        break;
    case 7:
        if (free_temp_tile_data_buffers_if_possible() != TRUE)
        {
            LZDecompressWram(gUnknown_08D9BA44, sPokeblockFeed->tilemapBuffer);
            sPokeblockFeed->loadGfxState++;
        }
        break;
    case 8:
        LoadCompressedPalette(gBattleTerrainPalette_Frontier, 0x20, 0x60);
        sPokeblockFeed->loadGfxState = 0;
        return TRUE;
    }

    return FALSE;
}

static void HandleInitWindows(void)
{
    InitWindows(sWindowTemplates);
    DeactivateAllTextPrinters();
    LoadUserWindowBorderGfx(0, 1, 0xE0);
    LoadPalette(gUnknown_0860F074, 0xF0, 0x20);
    FillWindowPixelBuffer(0, 0);
    PutWindowTilemap(0);
    schedule_bg_copy_tilemap_to_vram(0);
}

static void SetPokeblockSpritePal(u8 pokeblockCaseId)
{
    u8 colorId = GetPokeblockData(&gSaveBlock1Ptr->pokeblocks[pokeblockCaseId], PBLOCK_COLOR);
    sPokeblockSpritePal.data = sPokeblocksPals[colorId - 1];
    sPokeblockSpritePal.tag = GFX_TAG_POKEBLOCK;
}

// defines for task data fields

#define tFrames     data[0]
#define tData1      data[1]

static void Task_HandlePokeblockFeed(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        switch (gTasks[taskId].tFrames)
        {
        case 0:
            sPokeblockFeed->field_1050 = 0;
            sPokeblockFeed->field_1058 = 0;
            sub_817A5CC();
            break;
        case 255:
            DoPokeblockCaseThrowEffect(sPokeblockFeed->pokeblockCaseSpriteId, gTasks[taskId].tData1);
            break;
        case 269:
            sPokeblockFeed->pokeblockSpriteId = CreatePokeblockSprite();
            break;
        case 281:
            PrepareMonToMoveToPokeblock(sPokeblockFeed->monSpriteId);
            break;
        case 297:
            gTasks[taskId].func = Task_HandleMonAtePokeblock;
            return;
        }

        if (sPokeblockFeed->field_1058 < sPokeblockFeed->field_1056)
            sub_817A634();
        else if (sPokeblockFeed->field_1058 == sPokeblockFeed->field_1056)
            gTasks[taskId].tFrames = 254;

        sPokeblockFeed->field_1058++;
        gTasks[taskId].tFrames++;
    }
}

static void LaunchPokeblockFeedTask(void)
{
    u8 taskId = CreateTask(Task_HandlePokeblockFeed, 0);
    gTasks[taskId].tFrames = 0;
    gTasks[taskId].tData1 = 1;
}

static void Task_WaitForAtePokeblockText(u8 taskId)
{
    if (RunTextPrintersRetIsActive(0) != TRUE)
        gTasks[taskId].func = Task_PaletteFadeToReturn;
}

static void Task_HandleMonAtePokeblock(u8 taskId)
{
    struct Pokemon *mon = &gPlayerParty[gPokeblockMonId];
    struct Pokeblock *pokeblock = &gSaveBlock1Ptr->pokeblocks[gSpecialVar_ItemId];

    gPokeblockGain = PokeblockGetGain(GetNature(mon), pokeblock);
    GetMonNickname(mon, gStringVar1);
    PokeblockCopyName(pokeblock, gStringVar2);

    if (gPokeblockGain == 0)
        StringExpandPlaceholders(gStringVar4, gText_Var1AteTheVar2);
    else if (gPokeblockGain > 0)
        StringExpandPlaceholders(gStringVar4, gText_Var1HappilyAteVar2);
    else
        StringExpandPlaceholders(gStringVar4, gText_Var1DisdainfullyAteVar2);

    gTextFlags.canABSpeedUpPrint = 1;
    AddTextPrinterParameterized2(0, 1, gStringVar4, GetPlayerTextSpeedDelay(), NULL, 2, 1, 3);
    gTasks[taskId].func = Task_WaitForAtePokeblockText;
}

static void Task_ReturnAfterPaletteFade(u8 taskId)
{
    if (!gPaletteFade.active)
    {
        ResetSpriteData();
        FreeAllSpritePalettes();
        m4aMPlayVolumeControl(&gMPlayInfo_BGM, -1, 0x100);
        SetMainCallback2(gMain.savedCallback);
        DestroyTask(taskId);
        FreeAllWindowBuffers();
        Free(sPokeblockFeed);
        FreeMonSpritesGfx();
    }
}

static void Task_PaletteFadeToReturn(u8 taskId)
{
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, 0);
    gTasks[taskId].func = Task_ReturnAfterPaletteFade;
}

#undef tFrames
#undef tData1

// defines for mon sprite data fields

#define tDelta          data[0]
#define tDeltaMod       data[1]
#define tSpecies        data[2]

static u8 CreateMonSprite(struct Pokemon* mon)
{
    u16 species = GetMonData(mon, MON_DATA_SPECIES2);
    u8 spriteId = CreateSprite(&gMultiuseSpriteTemplate, 48, 80, 2);

    sPokeblockFeed->species = species;
    sPokeblockFeed->monSpriteId_ = spriteId;
    sPokeblockFeed->nature = GetNature(mon);
    gSprites[spriteId].tSpecies = species;
    gSprites[spriteId].callback = SpriteCallbackDummy;

    sPokeblockFeed->noMonFlip = TRUE;
    if (!IsMonSpriteNotFlipped(species))
    {
        gSprites[spriteId].affineAnims = sSpriteAffineAnimTable_MonNoFlip;
        gSprites[spriteId].oam.affineMode = 3;
        CalcCenterToCornerVec(&gSprites[spriteId], gSprites[spriteId].oam.shape, gSprites[spriteId].oam.size, gSprites[spriteId].oam.affineMode);
        sPokeblockFeed->noMonFlip = FALSE;
    }

    return spriteId;
}

static void PrepareMonToMoveToPokeblock(u8 spriteId)
{
    gSprites[spriteId].pos1.x = 48;
    gSprites[spriteId].pos1.y = 80;
    gSprites[spriteId].tDelta = -8;
    gSprites[spriteId].tDeltaMod = 1;
    gSprites[spriteId].callback = sub_817A468;
}

static void sub_817A468(struct Sprite* sprite)
{
    sprite->pos1.x += 4;
    sprite->pos1.y += sprite->tDelta;
    sprite->tDelta += sprite->tDeltaMod;

    if (sprite->tDelta == 0)
        PlayCry1(sprite->tSpecies, 0);
    if (sprite->tDelta == 9)
        sprite->callback = SpriteCallbackDummy;
}

#undef tDelta
#undef tDeltaMod
#undef tSpecies

static u8 CreatePokeblockCaseSpriteForFeeding(void)
{
    u8 spriteId = CreatePokeblockCaseSprite(188, 100, 2);
    gSprites[spriteId].oam.affineMode = 1;
    gSprites[spriteId].affineAnims = sSpriteAffineAnimTable_85F0664;
    gSprites[spriteId].callback = SpriteCallbackDummy;
    InitSpriteAffineAnim(&gSprites[spriteId]);
    return spriteId;
}

static void DoPokeblockCaseThrowEffect(u8 spriteId, bool8 a1)
{
    FreeOamMatrix(gSprites[spriteId].oam.matrixNum);
    gSprites[spriteId].oam.affineMode = 3;

    if (!a1)
        gSprites[spriteId].affineAnims = sSpriteAffineAnimTable_85F0668;
    else
        gSprites[spriteId].affineAnims = sSpriteAffineAnimTable_85F066C;

    InitSpriteAffineAnim(&gSprites[spriteId]);
}

// defines for the pokeblock sprite data fields
#define tDelta          data[0]
#define tDeltaMod       data[1]

static u8 CreatePokeblockSprite(void)
{
    u8 spriteId = CreateSprite(&sThrownPokeblockSpriteTemplate, 174, 84, 1);
    gSprites[spriteId].tDelta = -12;
    gSprites[spriteId].tDeltaMod = 1;
    return spriteId;
}

static void SpriteCB_ThrownPokeblock(struct Sprite* sprite)
{
    sprite->pos1.x -= 4;
    sprite->pos1.y += sprite->tDelta;
    sprite->tDelta += sprite->tDeltaMod;
    if (sprite->tDelta == 10)
        DestroySprite(sprite);
}

#undef tDelta
#undef tDeltaMod

static void sub_817A5CC(void)
{
    u8 animId, i;
    struct PokeblockFeedStruct *pokeblockFeed;

    pokeblockFeed = sPokeblockFeed;
    pokeblockFeed->field_1056 = 1;
    animId = sNatureToMonPokeblockAnim[pokeblockFeed->nature][0];
    for (i = 0; i < 8; i++, animId++)
    {
        pokeblockFeed->field_1056 += sMonPokeblockAnims[animId][4];
        if (sMonPokeblockAnims[animId][9] == 1)
            break;
    }
}

static void sub_817A634(void)
{
    struct PokeblockFeedStruct *pokeblockFeed = sPokeblockFeed;

    switch (pokeblockFeed->field_1050)
    {
    case 0:
        pokeblockFeed->animId = sNatureToMonPokeblockAnim[pokeblockFeed->nature][0];
        pokeblockFeed->monSpritePtr = &gSprites[pokeblockFeed->monSpriteId_];
        pokeblockFeed->savedMonSprite = *pokeblockFeed->monSpritePtr;
        pokeblockFeed->field_1050 = 10;
        break;
    case 1 ... 9:
        break;
    case 10:
        sub_817A91C();
        if (sNatureToMonPokeblockAnim[pokeblockFeed->nature][1] != 0)
        {
            pokeblockFeed->monSpritePtr->oam.affineMode = 3;
            pokeblockFeed->monSpritePtr->oam.matrixNum = 0;
            pokeblockFeed->monSpritePtr->affineAnims = sSpriteAffineAnimTable_85F04FC;
            InitSpriteAffineAnim(pokeblockFeed->monSpritePtr);
        }
        pokeblockFeed->field_1050 = 50;
    case 50:
        if (sNatureToMonPokeblockAnim[pokeblockFeed->nature][1] != 0)
        {
            if (!pokeblockFeed->noMonFlip) // double negation, so mon's sprite is flipped
                StartSpriteAffineAnim(pokeblockFeed->monSpritePtr, sNatureToMonPokeblockAnim[pokeblockFeed->nature][1] + 10);
            else
                StartSpriteAffineAnim(pokeblockFeed->monSpritePtr, sNatureToMonPokeblockAnim[pokeblockFeed->nature][1]);
        }
        pokeblockFeed->field_1050 = 60;
        break;
    case 60:
        if (sub_817A9E4() == TRUE)
        {
            if (pokeblockFeed->field_1060[9] == 0)
            {
                pokeblockFeed->animId++;
                sub_817A91C();
                pokeblockFeed->field_1050 = 60;
            }
            else
            {
                FreeOamMatrix(pokeblockFeed->monSpritePtr->oam.matrixNum);
                pokeblockFeed->field_1050 = 70;
            }
        }
        break;
    case 70:
        FreeMonSpriteOamMatrix();
        pokeblockFeed->animId = 0;
        pokeblockFeed->field_1050 = 0;
        break;
    case 71 ... 90:
        break;
    }
}

static bool8 sub_817A91C(void)
{
    struct PokeblockFeedStruct *pokeblockFeed = sPokeblockFeed;
    u8 i;

    for (i = 0; i < 10; i++)
        pokeblockFeed->field_1060[i] = sMonPokeblockAnims[pokeblockFeed->animId][i];

    if (pokeblockFeed->field_1060[4] == 0)
    {
        return TRUE;
    }
    else
    {
        pokeblockFeed->field_1060[10] = Sin(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[2]);
        pokeblockFeed->field_1060[11] = Cos(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[3]);
        pokeblockFeed->field_1060[12] = pokeblockFeed->field_1060[4];
        pokeblockFeed->field_1060[13] = pokeblockFeed->monSpritePtr->pos2.x;
        pokeblockFeed->field_1060[14] = pokeblockFeed->monSpritePtr->pos2.y;
        sub_817AB68();
        pokeblockFeed->field_1060[4] = pokeblockFeed->field_1060[12];
        sub_817AA54();
        pokeblockFeed->field_1060[4] = pokeblockFeed->field_1060[12];
        return FALSE;
    }
}

static bool8 sub_817A9E4(void)
{
    u16 var = sPokeblockFeed->field_1060[12] - sPokeblockFeed->field_1060[4];

    sPokeblockFeed->monSpritePtr->pos2.x = sPokeblockFeed->field_850[var];
    sPokeblockFeed->monSpritePtr->pos2.y = sPokeblockFeed->field_C50[var];

    if (--sPokeblockFeed->field_1060[4] == 0)
        return TRUE;
    else
        return FALSE;
}

static bool8 FreeMonSpriteOamMatrix(void)
{
    FreeSpriteOamMatrix(sPokeblockFeed->monSpritePtr);
    return FALSE;
}

static void sub_817AA54(void)
{
    struct PokeblockFeedStruct *pokeblockFeed = sPokeblockFeed;
    u16 i;
    u16 r8 = pokeblockFeed->field_1060[8];
    u16 r7 = pokeblockFeed->field_1060[12] - r8;
    s16 var3 = pokeblockFeed->field_1060[13] + pokeblockFeed->field_1060[6];
    s16 r9 = pokeblockFeed->field_1060[14] + pokeblockFeed->field_1060[7];

    for (i = 0; i < r7 - 1; i++)
    {
        s16 r1 = pokeblockFeed->field_850[r8 + i] - (var3);
        s16 r4 = pokeblockFeed->field_C50[r8 + i] - r9;

        pokeblockFeed->field_850[r8 + i] -= r1 * (i + 1) / r7;
        pokeblockFeed->field_C50[r8 + i] -= r4 * (i + 1) / r7;
    }

    pokeblockFeed->field_850[(r8 + r7) - 1] = var3;
    pokeblockFeed->field_C50[(r8 + r7) - 1] = r9;
}

static void sub_817AB68(void)
{
    struct PokeblockFeedStruct *pokeblockFeed = sPokeblockFeed;
    bool8 var_24 = FALSE;
    s16 r8 = pokeblockFeed->field_1060[13] - pokeblockFeed->field_1060[10];
    s16 r7 = pokeblockFeed->field_1060[14] - pokeblockFeed->field_1060[11];

    while (1)
    {
        u16 r5;
        u16 r4;
        u16 var;

        var = abs(pokeblockFeed->field_1060[5]);
        r5 = var + pokeblockFeed->field_1060[3];
        pokeblockFeed->field_1060[3] = r5;

        if (pokeblockFeed->field_1060[2] < 0)
            var_24 = TRUE;

        r4 = pokeblockFeed->field_1060[12] - pokeblockFeed->field_1060[4];

        if (pokeblockFeed->field_1060[4] == 0)
            break;

        if (!var_24)
        {
            pokeblockFeed->field_850[r4] = Sin(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[2] + r5 / 0x100) + r8;
            pokeblockFeed->field_C50[r4] = Cos(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[3] + r5 / 0x100) + r7;
        }
        else
        {
            pokeblockFeed->field_850[r4] = Sin(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[2] - r5 / 0x100) + r8;
            pokeblockFeed->field_C50[r4] = Cos(pokeblockFeed->field_1060[0], pokeblockFeed->field_1060[3] - r5 / 0x100) + r7;
        }

        pokeblockFeed->field_1060[0] += pokeblockFeed->field_1060[1];
        pokeblockFeed->field_1060[0] &= 0xFF;
        pokeblockFeed->field_1060[4]--;
    }
}
