#include "global.h"
#include "gflib.h"
#include "task.h"
#include "decompress.h"
#include "text_window.h"
#include "pokemon_icon.h"
#include "graphics.h"
#include "link.h"
#include "link_rfu.h"
#include "cable_club.h"
#include "data.h"
#include "strings.h"
#include "menu.h"
#include "overworld.h"
#include "battle_anim.h"
#include "party_menu.h"
#include "daycare.h"
#include "event_data.h"
#include "battle_interface.h"
#include "pokemon_summary_screen.h"
#include "pokemon_storage_system.h"
#include "new_menu_helpers.h"
#include "trade_scene.h"
#include "constants/songs.h"
#include "constants/moves.h"
#include "constants/trade.h"

// IDs for CallTradeMenuFunc
enum {
    CB_MAIN_MENU,
    CB_SELECTED_MON,
    CB_SHOW_MON_SUMMARY,
    CB_CONFIRM_TRADE_PROMPT,
    CB_CANCEL_TRADE_PROMPT,
    CB_READY_WAIT, // Equivalent to CB_IDLE
    CB_SET_SELECTED_MONS,
    CB_PRINT_IS_THIS_OKAY,
    CB_HANDLE_TRADE_CANCELED,
    CB_FADE_TO_START_TRADE,
    CB_WAIT_TO_START_TRADE,
    CB_INIT_EXIT_CANCELED_TRADE,
    CB_EXIT_CANCELED_TRADE,
    CB_START_LINK_TRADE,
    CB_INIT_CONFIRM_TRADE_PROMPT,
    CB_UNUSED_CLOSE_MSG,
    CB_WAIT_TO_START_RFU_TRADE,
    CB_IDLE = 100,
};

// Indexes for sActionTexts
enum {
    TEXT_CANCEL,
    TEXT_CHOOSE_MON,
    TEXT_SUMMARY,
    TEXT_TRADE,
    TEXT_CANCEL_TRADE,
    TEXT_PRESS_B_TO_EXIT,
};

// Indexes for sMessages
enum {
    MSG_STANDBY,
    MSG_CANCELED,
    MSG_ONLY_MON1,
    MSG_ONLY_MON2,
    MSG_WAITING_FOR_FRIEND,
    MSG_FRIEND_WANTS_TO_TRADE,
    MSG_MON_CANT_BE_TRADED,
    MSG_EGG_CANT_BE_TRADED,
    MSG_FRIENDS_MON_CANT_BE_TRADED,
};

// IDs for QueueAction
enum {
    QUEUE_SEND_DATA,
    QUEUE_STANDBY,
    QUEUE_ONLY_MON1,
    QUEUE_ONLY_MON2,
    QUEUE_UNUSED1, // Presumably intended for MSG_WAITING_FOR_FRIEND
    QUEUE_UNUSED2, // Presumably intended for MSG_FRIEND_WANTS_TO_TRADE
    QUEUE_MON_CANT_BE_TRADED,
    QUEUE_EGG_CANT_BE_TRADED,
    QUEUE_FRIENDS_MON_CANT_BE_TRADED,
};

#define QUEUE_DELAY_MSG   3
#define QUEUE_DELAY_DATA  5

#define GFXTAG_MENU_TEXT       200 // Used as a base tag in CB2_CreateTradeMenu and CB2_ReturnToTradeMenuFromSummary
#define GFXTAG_CURSOR          300
#define GFXTAG_LINK_MON_GLOW   5550
#define GFXTAG_LINK_MON_SHADOW 5552
#define GFXTAG_CABLE_END       5554
#define GFXTAG_GBA_SCREEN      5556
#define GFXTAG_POKEBALL        5557

#define PALTAG_CURSOR    2345
#define PALTAG_MENU_TEXT 4925
#define PALTAG_LINK_MON  5551
#define PALTAG_GBA       5555
#define PALTAG_POKEBALL  5558

// The following tags are offsets from GFXTAG_MENU_TEXT
// They're looped over in CB2_CreateTradeMenu and CB2_ReturnToTradeMenuFromSummary
// and used as indexes into sMenuTextTileBuffers
enum {
    GFXTAG_PLAYER_NAME_L,
    GFXTAG_PLAYER_NAME_M,
    GFXTAG_PLAYER_NAME_R,
    GFXTAG_PARTNER_NAME_L,
    GFXTAG_PARTNER_NAME_M,
    GFXTAG_PARTNER_NAME_R,
    GFXTAG_CANCEL_L,
    GFXTAG_CANCEL_R,
    GFXTAG_CHOOSE_PKMN_L,
    GFXTAG_CHOOSE_PKMN_M,
    GFXTAG_CHOOSE_PKMN_R,
    GFXTAG_CHOOSE_PKMN_EMPTY_1, // 6 sprites to cover the full bottom bar, but only first 3 are needed
    GFXTAG_CHOOSE_PKMN_EMPTY_2,
    GFXTAG_CHOOSE_PKMN_EMPTY_3,
    NUM_MENU_TEXT_SPRITES
};
#define NUM_PLAYER_NAME_SPRITES (1 + GFXTAG_PLAYER_NAME_R - GFXTAG_PLAYER_NAME_L)
#define NUM_PARTNER_NAME_SPRITES (1 + GFXTAG_PARTNER_NAME_R - GFXTAG_PARTNER_NAME_L)
#define NUM_CHOOSE_PKMN_SPRITES (1 + GFXTAG_CHOOSE_PKMN_EMPTY_3 - GFXTAG_CHOOSE_PKMN_L)

enum {
    CURSOR_ANIM_NORMAL,
    CURSOR_ANIM_ON_CANCEL,
};

// Values for signaling to/from the link partner
enum {
    STATUS_NONE,
    STATUS_READY,
    STATUS_CANCEL,
};

// Checked to confirm DrawSelectedMonScreen has reached final state
#define DRAW_SELECTED_FINISH 5

static EWRAM_DATA u8 *sMenuTextTileBuffer = NULL;
static EWRAM_DATA u8 *sMenuTextTileBuffers[NUM_MENU_TEXT_SPRITES] = {};
EWRAM_DATA struct Mail gLinkPartnerMail[PARTY_SIZE] = {};
EWRAM_DATA u8 gSelectedTradeMonPositions[2] = {0};
static EWRAM_DATA struct {
    u8 bg2hofs;
    u8 bg3hofs;
    u8 filler_2[38];
    u8 partySpriteIds[2][PARTY_SIZE];
    u8 cursorSpriteId;
    u8 cursorPosition;
    u8 partyCounts[2];
    bool8 optionsActive[PARTY_SIZE * 2 + 1];
    bool8 isLiveMon[2][PARTY_SIZE];
    bool8 isEgg[2][PARTY_SIZE];
    u8 hpBarLevels[2][PARTY_SIZE];
    u8 bufferPartyState;
    u8 filler_6A[5];
    u8 callbackId;
    u8 unk_70; // Never read
    u16 bottomTextTileStart;
    u8 drawSelectedMonState[2];
    u8 selectedMonIdx[2];
    u8 playerSelectStatus;
    u8 partnerSelectStatus;
    u8 playerConfirmStatus;
    u8 partnerConfirmStatus;
    u8 filler_7C[2];
    u8 partnerCursorPosition;
    u16 linkData[20];
    u8 timer;
    u8 giftRibbons[GIFT_RIBBONS_COUNT];
    u8 filler_B4[0x81C];
    struct {
        bool8 active;
        u16 delay;
        u8 actionId;
    } queuedActions[4];
    u16 tilemapBuffer[BG_SCREEN_SIZE / 2];
} * sTradeMenu = NULL;

static void CB2_CreateTradeMenu(void);
static void VBlankCB_TradeMenu(void);
static void CB2_TradeMenu(void);
static void LoadTradeBgGfx(u8 state);
static void SetActiveMenuOptions(void);
static u8 BufferTradeParties(void);
static void CB1_UpdateLink(void);
static void RunTradeMenuCallback(void);
static void SetSelectedMon(u8 cursorPosition);
static void DrawSelectedMonScreen(u8 side);
static u8 GetMonNicknameWidth(u8 *str, u8 whichParty, u8 partyIdx);
static void BufferMovesString(u8 *str, u8 whichParty, u8 partyIdx);
static void PrintPartyNicknames(u8 side);
static void PrintLevelAndGender(u8 whichParty, u8 monIdx, u8 x, u8 y, u8 winLeft, u8 winTop);
static void PrintPartyLevelsAndGenders(u8 side);
static void PrintTradePartnerPartyNicknames(void);
static void RedrawPartyWindow(u8 whichParty);
static void Task_DrawSelectionSummary(u8 taskId);
static void Task_DrawSelectionTrade(u8 taskId);
static void QueueAction(u16 delay, u8 actionId);
static void DoQueuedActions(void);
static void PrintTradeMessage(u8 strIdx);
static bool8 LoadUISpriteGfx(void);
static void DrawBottomRowText(const u8 *name, u8 *dest, u8 unused);
static void ComputePartyTradeableFlags(u8 side);
static void ComputePartyHPBarLevels(u8 side);
static void SetTradePartyHPBarSprites(void);
static void SaveTradeGiftRibbons(void);
static u32 CanTradeSelectedMon(struct Pokemon * party, int partyCount, int cursorPos);

static const size_t sSizesAndOffsets[] = {
    sizeof(struct SaveBlock2),
    sizeof(struct SaveBlock1),
    sizeof(struct MapLayout),
    0x530, // unk
    0x34, // unk
    sizeof(struct Mail),
    sizeof(struct Pokemon),
    0x528 // unk
};

static const u16 sTradeMovesBoxTilemap[] = INCBIN_U16("graphics/trade/moves_box_map.bin");
static const u16 sTradePartyBoxTilemap[] = INCBIN_U16("graphics/trade/party_box_map.bin");
static const u8 sTradeStripesBG2Tilemap[] = INCBIN_U8("graphics/trade/stripes_bg2_map.bin");
static const u8 sTradeStripesBG3Tilemap[] = INCBIN_U8("graphics/trade/stripes_bg3_map.bin");

static const struct OamData sOamData_MenuText = {
    .shape = SPRITE_SHAPE(32x16),
    .size = SPRITE_SIZE(32x16),
    .priority = 1
};

static const struct OamData sOamData_Cursor = {
    .shape = SPRITE_SHAPE(64x32),
    .size = SPRITE_SIZE(64x32),
    .priority = 1
};

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

static const union AnimCmd sAnim_Cursor_OnCancel[] = {
    ANIMCMD_FRAME(32, 5),
    ANIMCMD_END
};

static const union AnimCmd *const sAnims_Cursor[] = {
    [CURSOR_ANIM_NORMAL]    = sAnim_Cursor_Normal,
    [CURSOR_ANIM_ON_CANCEL] = sAnim_Cursor_OnCancel
};

static const struct SpriteSheet sCursor_SpriteSheet = {
    .data = gTradeCursor_Gfx,
    .size = 0x800,
    .tag = GFXTAG_CURSOR
};

static const struct SpritePalette sCursor_SpritePalette = {
    .data = gTradeCursor_Pal,
    .tag = PALTAG_CURSOR
};


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

static const union AnimCmd sAnim_MenuText_1[] = {
    ANIMCMD_FRAME(8, 5),
    ANIMCMD_END
};

static const union AnimCmd sAnim_MenuText_2[] = {
    ANIMCMD_FRAME(16, 5),
    ANIMCMD_END
};

static const union AnimCmd sAnim_MenuText_3[] = {
    ANIMCMD_FRAME(24, 5),
    ANIMCMD_END
};

static const union AnimCmd sAnim_MenuText_4[] = {
    ANIMCMD_FRAME(32, 5),
    ANIMCMD_END
};

static const union AnimCmd sAnim_MenuText_5[] = {
    ANIMCMD_FRAME(40, 5),
    ANIMCMD_END
};

// These anims are not used
static const union AnimCmd *const sAnims_MenuText[] = {
    sAnim_MenuText_0,
    sAnim_MenuText_1,
    sAnim_MenuText_2,
    sAnim_MenuText_3,
    sAnim_MenuText_4,
    sAnim_MenuText_5,
};

static const struct SpriteTemplate sSpriteTemplate_Cursor = {
    .tileTag = GFXTAG_CURSOR,
    .paletteTag = PALTAG_CURSOR,
    .oam = &sOamData_Cursor,
    .anims = sAnims_Cursor,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

static const struct SpriteTemplate sSpriteTemplate_MenuText = {
    .tileTag = GFXTAG_MENU_TEXT,
    .paletteTag = PALTAG_MENU_TEXT,
    .oam = &sOamData_MenuText,
    .anims = sAnims_MenuText,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy
};

static const u16 sMenuText_Pal[] = INCBIN_U16("graphics/trade/text.gbapal");
static const struct SpritePalette sSpritePalette_MenuText = {
    .data = sMenuText_Pal,
    .tag = PALTAG_MENU_TEXT
};

#define DIR_UP    0
#define DIR_DOWN  1
#define DIR_LEFT  2
#define DIR_RIGHT 3

// This is used to determine the next mon to select when the D-Pad is
// pressed in a given direction.
// Note that the mons are laid out like this.
// 0-5 are the player's party and 6-11 are the trading partner's party.
// 12 is the cancel button.
//  0  1  6  7
//  2  3  8  9
//  4  5 10 11
//          12
// 1st array is the current positions
// 2nd array is directions of input
// 3rd array is the next positions to go to (unoccupied spaces are skipped over)
static const u8 sCursorMoveDestinations[(PARTY_SIZE * 2) + 1][4][PARTY_SIZE] = {
    // Player's party
    [0] = {
        [DIR_UP]    = { 4,  2, 12, 12,  0,  0},
        [DIR_DOWN]  = { 2,  4, 12, 12,  0,  0},
        [DIR_LEFT]  = { 7,  6,  1,  0,  0,  0},
        [DIR_RIGHT] = { 1,  6,  7,  0,  0,  0}
    },
    [1] = {
        [DIR_UP]    = { 5,  3, 12, 12,  0,  0},
        [DIR_DOWN]  = { 3,  5, 12, 12,  0,  0},
        [DIR_LEFT]  = { 0,  7,  6,  1,  0,  0},
        [DIR_RIGHT] = { 6,  7,  0,  1,  0,  0}
    },
    [2] = {
        [DIR_UP]    = { 0,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = { 4,  0,  0,  0,  0,  0},
        [DIR_LEFT]  = { 9,  8,  7,  6,  0,  0},
        [DIR_RIGHT] = { 3,  1,  0,  0,  0,  0}
    },
    [3] = {
        [DIR_UP]    = { 1,  1,  1,  1,  0,  0},
        [DIR_DOWN]  = { 5,  1,  1,  1,  0,  0},
        [DIR_LEFT]  = { 2,  9,  8,  7,  0,  0},
        [DIR_RIGHT] = { 8,  9,  6,  6,  0,  0}
    },
    [4] = {
        [DIR_UP]    = { 2,  2,  2,  2,  0,  0},
        [DIR_DOWN]  = { 0,  0,  0,  0,  0,  0},
        [DIR_LEFT]  = {11, 10,  9,  8,  7,  6},
        [DIR_RIGHT] = { 5,  3,  1,  0,  0,  0}
    },
    [5] = {
        [DIR_UP]    = { 3,  3,  3,  3,  0,  0},
        [DIR_DOWN]  = { 1,  1,  1,  1,  0,  0},
        [DIR_LEFT]  = { 4,  4,  4,  4,  0,  0},
        [DIR_RIGHT] = {10,  8,  6,  0,  0,  0}
    },
    // Partner's party
    [6] = {
        [DIR_UP]    = {10,  8, 12,  0,  0,  0},
        [DIR_DOWN]  = { 8, 10, 12,  0,  0,  0},
        [DIR_LEFT]  = { 1,  0,  0,  0,  0,  0},
        [DIR_RIGHT] = { 7,  0,  1,  0,  0,  0}
    },
    [7] = {
        [DIR_UP]    = {12,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = { 9, 12,  0,  0,  0,  0},
        [DIR_LEFT]  = { 6,  0,  0,  0,  0,  0},
        [DIR_RIGHT] = { 0,  0,  0,  0,  0,  0}
    },
    [8] = {
        [DIR_UP]    = { 6,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = {10,  6,  0,  0,  0,  0},
        [DIR_LEFT]  = { 3,  2,  1,  0,  0,  0},
        [DIR_RIGHT] = { 9,  7,  0,  0,  0,  0}
    },
    [9] = {
        [DIR_UP]    = { 7,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = {11, 12,  0,  0,  0,  0},
        [DIR_LEFT]  = { 8,  0,  0,  0,  0,  0},
        [DIR_RIGHT] = { 2,  1,  0,  0,  0,  0}
    },
    [10] = {
        [DIR_UP]    = { 8,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = { 6,  0,  0,  0,  0,  0},
        [DIR_LEFT]  = { 5,  4,  3,  2,  1,  0},
        [DIR_RIGHT] = {11,  9,  7,  0,  0,  0}
    },
    [11] = {
        [DIR_UP]    = { 9,  0,  0,  0,  0,  0},
        [DIR_DOWN]  = {12,  0,  0,  0,  0,  0},
        [DIR_LEFT]  = {10,  0,  0,  0,  0,  0},
        [DIR_RIGHT] = { 4,  2,  0,  0,  0,  0}
    },
    // Cancel
    [12] = {
        [DIR_UP]    = {11,  9,  7,  6,  0,  0},
        [DIR_DOWN]  = { 7,  6,  0,  0,  0,  0},
        [DIR_LEFT]  = {12,  0,  0,  0,  0,  0},
        [DIR_RIGHT] = {12,  0,  0,  0,  0,  0}
    }
};

#define COL0_X 1
#define COL1_X 8
#define COL2_X 16
#define COL3_X 23
#define ROW0_Y 5
#define ROW1_Y 10
#define ROW2_Y 15
#define ROW3_Y 18

static const u8 sTradeMonSpriteCoords[(PARTY_SIZE * 2) + 1][2] = {
    [TRADE_PLAYER] =
        {COL0_X, ROW0_Y},
        {COL1_X, ROW0_Y},
        {COL0_X, ROW1_Y},
        {COL1_X, ROW1_Y},
        {COL0_X, ROW2_Y},
        {COL1_X, ROW2_Y},
    [TRADE_PARTNER * PARTY_SIZE] =
        {COL2_X, ROW0_Y},
        {COL3_X, ROW0_Y},
        {COL2_X, ROW1_Y},
        {COL3_X, ROW1_Y},
        {COL2_X, ROW2_Y},
        {COL3_X, ROW2_Y},
    // Cancel
    {COL3_X, ROW3_Y},
};

static const u8 sTradeMonLevelCoords[PARTY_SIZE * 2][2] = {
    [TRADE_PLAYER] =
        { 5,  4},
        {12,  4},
        { 5,  9},
        {12,  9},
        { 5, 14},
        {12, 14},
    [TRADE_PARTNER * PARTY_SIZE] =
        {20,  4},
        {27,  4},
        {20,  9},
        {27,  9},
        {20, 14},
        {27, 14},
};

static const u8 sTradeMonBoxCoords[PARTY_SIZE * 2][2] = {
    [TRADE_PLAYER] =
        { 1,  3},
        { 8,  3},
        { 1,  8},
        { 8,  8},
        { 1, 13},
        { 8, 13},
    [TRADE_PARTNER * PARTY_SIZE] =
        {16,  3},
        {23,  3},
        {16,  8},
        {23,  8},
        {16, 13},
        {23, 13},
};

// x,y offset for player name, x/y offset for partner name
static const u8 sPlayerNameCoords[][4] = {
    {60, 9, 180, 9}, // Unused. For JP characters?
    {48, 9, 168, 9},
};

static const u8 sUnusedCoords[][2] =
{
    { 0, 14},
    {15, 29},
    { 3,  5},
    { 3,  7},
    {18,  5},
    {18,  7},
    { 8,  7},
    {22, 12},
    { 8,  7},
    {22, 12},
    { 6,  7},
    {24, 12},
    { 6,  7},
    {24, 12},
    { 8,  7},
    {22, 12},
    { 7,  7},
    {23, 12}
};

static const u8 sText_Dummy[] = _("");
static const u8 sText_ClrWhtHltTranspShdwDrkGry[] = _("{COLOR WHITE}{HIGHLIGHT TRANSPARENT}{SHADOW DARK_GRAY}");
const u8 gText_MaleSymbol4[] = _("♂");
const u8 gText_FemaleSymbol4[] = _("♀");
const u8 gText_GenderlessSymbol[] = _("");
static const u8 sText_Dummy2[] = _("");
static const u8 sText_Newline[] = _("\n");
static const u8 sText_Slash[] = _("/");

static const u8 *const sActionTexts[] = {
    [TEXT_CANCEL]          = gTradeText_Cancel,
    [TEXT_CHOOSE_MON]      = gTradeText_ChooseAPokemon,
    [TEXT_SUMMARY]         = gTradeText_Summary, // Unused, sMenuAction_SummaryTrade is used instead
    [TEXT_TRADE]           = gTradeText_Trade,   // Unused, sMenuAction_SummaryTrade is used instead
    [TEXT_CANCEL_TRADE]    = gText_CancelTrade,
    [TEXT_PRESS_B_TO_EXIT] = gTradeText_PressBButtonToExit // Unused
};

static const struct MenuAction sMenuAction_SummaryTrade[] = {
    {gText_TradeAction_Summary, { .void_u8 = Task_DrawSelectionSummary }},
    {gText_TradeAction_Trade, { .void_u8 = Task_DrawSelectionTrade }}
};

static const u8 *const sMessages[] = {
    [MSG_STANDBY]                    = gText_Trade_CommunicationStandby,
    [MSG_CANCELED]                   = gText_TradeHasBeenCanceled,
    [MSG_ONLY_MON1]                  = gText_Trade_OnlyPkmnForBattle,
    [MSG_ONLY_MON2]                  = gText_OnlyPkmnForBattle, // Same as above but without color formatting
    [MSG_WAITING_FOR_FRIEND]         = gText_WaitingForFriendToFinish,
    [MSG_FRIEND_WANTS_TO_TRADE]      = gText_FriendWantsToTrade,
    [MSG_MON_CANT_BE_TRADED]         = gText_PkmnCantBeTradedNow, 
    [MSG_EGG_CANT_BE_TRADED]         = gText_EggCantBeTradedNow,
    [MSG_FRIENDS_MON_CANT_BE_TRADED] = gText_OtherTrainersPkmnCantBeTraded
};

static const u8 sTextColor_PartyMonNickname[] = { TEXT_COLOR_TRANSPARENT, TEXT_COLOR_WHITE, TEXT_COLOR_DARK_GRAY };

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

static const struct WindowTemplate sWindowTemplates[] = {
    {
        .bg = 0,
        .tilemapLeft = 4,
        .tilemapTop = 7,
        .width = 22,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 0x01e
    }, {
        .bg = 0,
        .tilemapLeft = 17,
        .tilemapTop = 15,
        .width = 12,
        .height = 4,
        .paletteNum = 15,
        .baseBlock = 0x076
    }, {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 5,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0a6
    }, {
        .bg = 0,
        .tilemapLeft = 7,
        .tilemapTop = 5,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0b6
    }, {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 10,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0c6
    }, {
        .bg = 0,
        .tilemapLeft = 7,
        .tilemapTop = 10,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0d6
    }, {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 15,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0e6
    }, {
        .bg = 0,
        .tilemapLeft = 7,
        .tilemapTop = 15,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x0f6
    }, {
        .bg = 0,
        .tilemapLeft = 15,
        .tilemapTop = 5,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x106
    }, {
        .bg = 0,
        .tilemapLeft = 22,
        .tilemapTop = 5,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x116
    }, {
        .bg = 0,
        .tilemapLeft = 15,
        .tilemapTop = 10,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x126
    }, {
        .bg = 0,
        .tilemapLeft = 22,
        .tilemapTop = 10,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x136
    }, {
        .bg = 0,
        .tilemapLeft = 15,
        .tilemapTop = 15,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x146
    }, {
        .bg = 0,
        .tilemapLeft = 22,
        .tilemapTop = 15,
        .width = 8,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x156
    }, {
        .bg = 0,
        .tilemapLeft = 2,
        .tilemapTop = 5,
        .width = 14,
        .height = 2,
        .paletteNum = 13,
        .baseBlock = 0x166
    }, {
        .bg = 0,
        .tilemapLeft = 3,
        .tilemapTop = 8,
        .width = 11,
        .height = 8,
        .paletteNum = 15,
        .baseBlock = 0x182
    }, {
        .bg = 0,
        .tilemapLeft = 17,
        .tilemapTop = 5,
        .width = 14,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 0x1da
    }, {
        .bg = 0,
        .tilemapLeft = 18,
        .tilemapTop = 8,
        .width = 11,
        .height = 8,
        .paletteNum = 15,
        .baseBlock = 0x1f6
    }, DUMMY_WIN_TEMPLATE
};

static const struct WindowTemplate sWindowTemplate_YesNo = {
    .bg = 0,
    .tilemapLeft = 21,
    .tilemapTop = 13,
    .width = 6,
    .height = 4,
    .paletteNum = 15,
    .baseBlock = 0x24e
};

static const u8 sDebug_Messages[][13] = {
    _("かいめの そうしん"), // "xth communication sent"
    _("かいめの じゅしん"), // "xth communication received"
    _("ポケモンアイコンセット"), // "pokemon icon set"
    _("OBJテキストセット"), // "OBJ text set"
    _("セルセット"), // "cell set"
    _("OBJテキストADD"), // "OBJ text ADD"
    _("システムメッセージADD"), // "system message ADD"
    _("はいけいセット"), // "background set"
};

static const u8 sText_ShedinjaJP[] = _("ヌケニン");

static const u8 sDebug_TradeResultTexts[][14] = {
    _("こうかんせいりつ     "), // "trade successful"
    _("だめだたらしいよ     "), // "it seems like it didn't work"
};

// Position of level/gender when just the pokemon to trade are displayed
static const u8 sSelectedMonLevelGenderCoords[][2] = {
    [TRADE_PLAYER]  = { 4,  3},
    [TRADE_PARTNER] = {19,  3}
};

static void InitTradeMenu(void)
{
    static u16 dummy;

    ResetSpriteData();
    FreeAllSpritePalettes();
    ResetTasks();
    ResetPaletteFade();

    gPaletteFade.bufferTransferDisabled = TRUE;

    SetVBlankCallback(VBlankCB_TradeMenu);
    LoadPalette(gStandardMenuPalette, BG_PLTT_ID(15), PLTT_SIZEOF(10));
    LoadPalette(gStandardMenuPalette, BG_PLTT_ID(13), PLTT_SIZEOF(10));
    ResetBgsAndClearDma3BusyFlags(FALSE);
    InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
    SetBgTilemapBuffer(1, sTradeMenu->tilemapBuffer);

    if (InitWindows(sWindowTemplates))
    {
        int i;
        DeactivateAllTextPrinters();
        dummy = 590; // ?
        for (i = 0; i < ARRAY_COUNT(sWindowTemplates) - 1; i++)
        {
            ClearWindowTilemap(i);
            FillWindowPixelBuffer(i, PIXEL_FILL(0));
        }
        FillBgTilemapBufferRect(0, 0, 0, 0, 30, 20, 15);
        LoadStdWindowGfx(0, 0x014, BG_PLTT_ID(12));
        LoadUserWindowGfx(2, 0x001, BG_PLTT_ID(14));
        LoadMonIconPalettes();
        sTradeMenu->bufferPartyState = 0;
        sTradeMenu->callbackId = CB_MAIN_MENU;
        sTradeMenu->unk_70 = 0;
        sTradeMenu->drawSelectedMonState[TRADE_PLAYER] = 0;
        sTradeMenu->drawSelectedMonState[TRADE_PARTNER] = 0;
        sTradeMenu->playerConfirmStatus = STATUS_NONE;
        sTradeMenu->partnerConfirmStatus = STATUS_NONE;
        sTradeMenu->timer = 0;
    }
}

void CB2_StartCreateTradeMenu(void)
{
    SetMainCallback2(CB2_CreateTradeMenu);
}

static void CB2_CreateTradeMenu(void)
{
    int i;
    struct SpriteTemplate temp;
    u8 id;
    s32 width;
    u32 xPos;
    u8 *name;

    switch (gMain.state)
    {
    case 0:
        sTradeMenu = AllocZeroed(sizeof(*sTradeMenu));
        InitTradeMenu();
        sMenuTextTileBuffer = AllocZeroed(NUM_MENU_TEXT_SPRITES * 256);

        for (i = 0; i < NUM_MENU_TEXT_SPRITES; i++)
            sMenuTextTileBuffers[i] = &sMenuTextTileBuffer[i * 256];

        gMain.state++;
        break;
    case 1:
        gPaletteFade.bufferTransferDisabled = FALSE;

        for (i = 0; i < PARTY_SIZE; i++)
            CreateMon(&gEnemyParty[i], SPECIES_NONE, 0, USE_RANDOM_IVS, FALSE, 0, OT_ID_PLAYER_ID, 0);

        PrintTradeMessage(MSG_STANDBY);
        ShowBg(0);

        if (!gReceivedRemoteLinkPlayers)
        {
            gLinkType = LINKTYPE_TRADE_CONNECTING;
            sTradeMenu->timer = 0;

            if (gWirelessCommType)
            {
                SetWirelessCommType1();
                OpenLink();
                CreateTask_RfuIdle();
            }
            else
            {
                OpenLink();
                gMain.state++;
            }
            if (gWirelessCommType == 0)
                CreateTask(Task_WaitForLinkPlayerConnection, 1);
        }
        else
        {
            gMain.state = 4;
        }
        break;
    case 2:
        sTradeMenu->timer++;
        if (sTradeMenu->timer > 11)
        {
            sTradeMenu->timer = 0;
            gMain.state++;
        }
        break;
    case 3:
        if (GetLinkPlayerCount_2() >= GetSavedPlayerCount())
        {
            if (IsLinkMaster())
            {
                if (++sTradeMenu->timer > 30)
                {
                    CheckShouldAdvanceLinkState();
                    gMain.state++;
                }
            }
            else
            {
                gMain.state++;
            }
        }
        break;
    case 4:
        if (gReceivedRemoteLinkPlayers == TRUE && IsLinkPlayerDataExchangeComplete() == TRUE)
        {
            DestroyTask_RfuIdle();
            CalculatePlayerPartyCount();
            gMain.state++;
            sTradeMenu->timer = 0;
            if (gWirelessCommType)
            {
                Rfu_SetLinkRecovery(TRUE);
                SetLinkStandbyCallback();
            }
        }
        break;
    case 5:
        if (gWirelessCommType)
        {
            if (IsLinkRfuTaskFinished())
            {
                gMain.state++;
                LoadWirelessStatusIndicatorSpriteGfx();
                CreateWirelessStatusIndicatorSprite(0, 0);
            }
        }
        else
        {
            gMain.state++;
        }
        break;
    case 6:
        if (BufferTradeParties())
        {
            SaveTradeGiftRibbons();
            gMain.state++;
        }
        break;
    case 7:
        CalculateEnemyPartyCount();
        SetGpuReg(REG_OFFSET_DISPCNT, 0);
        SetGpuReg(REG_OFFSET_BLDCNT, 0);
        sTradeMenu->partyCounts[TRADE_PLAYER] = gPlayerPartyCount;
        sTradeMenu->partyCounts[TRADE_PARTNER] = gEnemyPartyCount;

        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PLAYER]; i++)
        {
            struct Pokemon * mon = &gPlayerParty[i];
            sTradeMenu->partySpriteIds[TRADE_PLAYER][i] = CreateMonIcon(GetMonData(mon, MON_DATA_SPECIES_OR_EGG),
                                                                SpriteCB_MonIcon,
                                                                (sTradeMonSpriteCoords[i][0] * 8) + 14,
                                                                (sTradeMonSpriteCoords[i][1] * 8) - 12,
                                                                1,
                                                                GetMonData(mon, MON_DATA_PERSONALITY),
                                                                TRUE);
        }

        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PARTNER]; i++)
        {
            struct Pokemon * mon = &gEnemyParty[i];
            sTradeMenu->partySpriteIds[TRADE_PARTNER][i] = CreateMonIcon(GetMonData(mon, MON_DATA_SPECIES_OR_EGG, NULL),
                                                                SpriteCB_MonIcon,
                                                                (sTradeMonSpriteCoords[i + PARTY_SIZE][0] * 8) + 14,
                                                                (sTradeMonSpriteCoords[i + PARTY_SIZE][1] * 8) - 12,
                                                                1,
                                                                GetMonData(mon, MON_DATA_PERSONALITY),
                                                                FALSE);
        }
        gMain.state++;
        break;
    case 8:
        LoadHeldItemIcons();
        DrawHeldItemIconsForTrade(sTradeMenu->partyCounts, sTradeMenu->partySpriteIds[0], TRADE_PLAYER);
        gMain.state++;
        break;
    case 9:
        DrawHeldItemIconsForTrade(sTradeMenu->partyCounts, sTradeMenu->partySpriteIds[0], TRADE_PARTNER);
        gMain.state++;
        break;
    case 10:
        DrawTextWindowAndBufferTiles(gSaveBlock2Ptr->playerName, sMenuTextTileBuffers[GFXTAG_PLAYER_NAME_L], 0, 0, gDecompressionBuffer, 3);
        id = GetMultiplayerId();
        DrawTextWindowAndBufferTiles(gLinkPlayers[id ^ 1].name, sMenuTextTileBuffers[GFXTAG_PARTNER_NAME_L], 0, 0, gDecompressionBuffer, 3);
        DrawTextWindowAndBufferTiles(sActionTexts[TEXT_CANCEL], sMenuTextTileBuffers[GFXTAG_CANCEL_L], 0, 0, gDecompressionBuffer, 2);
        DrawBottomRowText(sActionTexts[TEXT_CHOOSE_MON], sMenuTextTileBuffers[GFXTAG_CHOOSE_PKMN_L], 24);
        gMain.state++;
        sTradeMenu->timer = 0;
        break;
    case 11:
        if (LoadUISpriteGfx())
            gMain.state++;
        break;
    case 12:
        // Create player's name text sprites
        name = gSaveBlock2Ptr->playerName;
        width = GetStringWidth(FONT_NORMAL_COPY_1, name, 0);
        xPos = (56 - width) / 2;
        for (i = 0; i < NUM_PLAYER_NAME_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_PLAYER_NAME_L;
            CreateSprite(&temp, xPos + sPlayerNameCoords[1][0] + (i * 32), sPlayerNameCoords[1][1], 1);
        }

        // Create partner's name text sprites
        id = GetMultiplayerId();
        name = gLinkPlayers[id ^ 1].name;
        width = GetStringWidth(FONT_NORMAL_COPY_1, name, 0);
        xPos = (56 - width) / 2;
        for (i = 0; i < NUM_PARTNER_NAME_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_PARTNER_NAME_L;
            CreateSprite(&temp, xPos + sPlayerNameCoords[1][2] + (i * 32), sPlayerNameCoords[1][3], 1);
        }
        gMain.state++;
        break;
    case 13:
        // Create Cancel text sprites
        temp = sSpriteTemplate_MenuText;
        temp.tileTag += GFXTAG_CANCEL_L;
        CreateSprite(&temp, 215, 151, 1);
        temp = sSpriteTemplate_MenuText;
        temp.tileTag += GFXTAG_CANCEL_R;
        CreateSprite(&temp, 215 + 32, 151, 1);

        // Create Choose a Pokémon text sprites (only 3 are needed, other 3 are empty)
        for (i = 0; i < NUM_CHOOSE_PKMN_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_CHOOSE_PKMN_L;
            CreateSprite(&temp, (i * 32) + 24, 150, 1);
        }

        sTradeMenu->cursorSpriteId = CreateSprite(&sSpriteTemplate_Cursor, sTradeMonSpriteCoords[0][0] * 8 + 32, sTradeMonSpriteCoords[0][1] * 8, 2);
        sTradeMenu->cursorPosition = 0;
        gMain.state++;
        rbox_fill_rectangle(0);
        break;
    case 14:
        ComputePartyTradeableFlags(TRADE_PLAYER);
        PrintPartyNicknames(TRADE_PLAYER);
        sTradeMenu->bg2hofs = 0;
        sTradeMenu->bg3hofs = 0;
        SetActiveMenuOptions();
        gMain.state++;
        PlayBGM(MUS_GAME_CORNER);
        break;
    case 15:
        ComputePartyTradeableFlags(TRADE_PARTNER);
        PrintPartyNicknames(TRADE_PARTNER);
        gMain.state++;
        // fallthrough
    case 16:
        LoadTradeBgGfx(0);
        gMain.state++;
        break;
    case 17:
        LoadTradeBgGfx(1);
        gMain.state++;
        break;
    case 18:
        BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
        gMain.state++;
        break;
    case 19:
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON);
        LoadTradeBgGfx(2);
        gMain.state++;
        break;
    case 20:
        ComputePartyHPBarLevels(TRADE_PLAYER);
        gMain.state++;
        break;
    case 21:
        ComputePartyHPBarLevels(TRADE_PARTNER);
        SetTradePartyHPBarSprites();
        gMain.state++;
        break;
    case 22:
        if (!gPaletteFade.active)
        {
            gMain.callback1 = CB1_UpdateLink;
            SetMainCallback2(CB2_TradeMenu);
        }
        break;
    }

    RunTextPrinters();
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}

void CB2_ReturnToTradeMenuFromSummary(void)
{
    int i;
    struct SpriteTemplate temp;
    u8 id;
    s32 width;
    u32 xPos;
    u8 *name;

    switch (gMain.state)
    {
    case 0:
        InitTradeMenu();
        gMain.state++;
        break;
    case 1:
        gMain.state++;
        sTradeMenu->timer = 0;
        break;
    case 2:
        gMain.state++;
        break;
    case 3:
        gMain.state++;
        break;
    case 4:
        CalculatePlayerPartyCount();
        gMain.state++;
        break;
    case 5:
        if (gWirelessCommType != 0)
        {
            LoadWirelessStatusIndicatorSpriteGfx();
            CreateWirelessStatusIndicatorSprite(0, 0);
        }
        gMain.state++;
        break;
    case 6:
        gMain.state++;
        break;
    case 7:
        CalculateEnemyPartyCount();
        sTradeMenu->partyCounts[TRADE_PLAYER] = gPlayerPartyCount;
        sTradeMenu->partyCounts[TRADE_PARTNER] = gEnemyPartyCount;
        ClearWindowTilemap(0);
        PrintPartyNicknames(TRADE_PLAYER);
        PrintPartyNicknames(TRADE_PARTNER);
        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PLAYER]; i++)
        {
            sTradeMenu->partySpriteIds[TRADE_PLAYER][i] = CreateMonIcon(
                GetMonData(&gPlayerParty[i], MON_DATA_SPECIES_OR_EGG, NULL),
                SpriteCB_MonIcon,
                sTradeMonSpriteCoords[i][0] * 8 + 14,
                sTradeMonSpriteCoords[i][1] * 8 - 12,
                1,
                GetMonData(&gPlayerParty[i], MON_DATA_PERSONALITY),
                TRUE
            );
        }
        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PARTNER]; i++)
        {
            sTradeMenu->partySpriteIds[TRADE_PARTNER][i] = CreateMonIcon(
                GetMonData(&gEnemyParty[i], MON_DATA_SPECIES_OR_EGG, NULL),
                SpriteCB_MonIcon,
                sTradeMonSpriteCoords[i + PARTY_SIZE][0] * 8 + 14,
                sTradeMonSpriteCoords[i + PARTY_SIZE][1] * 8 - 12,
                1,
                GetMonData(&gEnemyParty[i], MON_DATA_PERSONALITY),
                FALSE
            );
        }
        gMain.state++;
        break;
    case 8:
        LoadHeldItemIcons();
        DrawHeldItemIconsForTrade(sTradeMenu->partyCounts, sTradeMenu->partySpriteIds[0], TRADE_PLAYER);
        gMain.state++;
        break;
    case 9:
        DrawHeldItemIconsForTrade(sTradeMenu->partyCounts, sTradeMenu->partySpriteIds[0], TRADE_PARTNER);
        gMain.state++;
        break;
    case 10:
        DrawTextWindowAndBufferTiles(gSaveBlock2Ptr->playerName, sMenuTextTileBuffers[GFXTAG_PLAYER_NAME_L], 0, 0, gDecompressionBuffer, 3);
        id = GetMultiplayerId();
        DrawTextWindowAndBufferTiles(gLinkPlayers[id ^ 1].name, sMenuTextTileBuffers[GFXTAG_PARTNER_NAME_L], 0, 0, gDecompressionBuffer, 3);
        DrawTextWindowAndBufferTiles(sActionTexts[TEXT_CANCEL], sMenuTextTileBuffers[GFXTAG_CANCEL_L], 0, 0, gDecompressionBuffer, 2);
        DrawBottomRowText(sActionTexts[TEXT_CHOOSE_MON], sMenuTextTileBuffers[GFXTAG_CHOOSE_PKMN_L], 24);
        gMain.state++;
        sTradeMenu->timer = 0;
        break;
    case 11:
        if (LoadUISpriteGfx())
            gMain.state++;
        break;
    case 12:
        // Create player's name text sprites
        name = gSaveBlock2Ptr->playerName;
        width = GetStringWidth(FONT_NORMAL_COPY_1, name, 0);
        xPos = (56 - width) / 2;
        for (i = 0; i < NUM_PLAYER_NAME_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_PLAYER_NAME_L;
            CreateSprite(&temp, xPos + sPlayerNameCoords[1][0] + (i * 32), sPlayerNameCoords[1][1], 1);
        }

        // Create partner's name text sprites
        id = GetMultiplayerId();
        name = gLinkPlayers[id ^ 1].name;
        width = GetStringWidth(FONT_NORMAL_COPY_1, name, 0);
        xPos = (56 - width) / 2;
        for (i = 0; i < NUM_PARTNER_NAME_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_PARTNER_NAME_L;
            CreateSprite(&temp, xPos + sPlayerNameCoords[1][2] + (i * 32), sPlayerNameCoords[1][3], 1);
        }
        gMain.state++;
        break;
    case 13:
        // Create Cancel text sprites
        temp = sSpriteTemplate_MenuText;
        temp.tileTag += GFXTAG_CANCEL_L;
        CreateSprite(&temp, 215, 151, 1);
        temp = sSpriteTemplate_MenuText;
        temp.tileTag += GFXTAG_CANCEL_R;
        CreateSprite(&temp, 215 + 32, 151, 1);

        // Create Choose a Pokémon text sprites
        for (i = 0; i < NUM_CHOOSE_PKMN_SPRITES; i++)
        {
            temp = sSpriteTemplate_MenuText;
            temp.tileTag += i + GFXTAG_CHOOSE_PKMN_L;
            CreateSprite(&temp, (i * 32) + 24, 150, 1);
        }

        if (sTradeMenu->cursorPosition < PARTY_SIZE)
            sTradeMenu->cursorPosition = GetLastViewedMonIndex();
        else
            sTradeMenu->cursorPosition = GetLastViewedMonIndex() + PARTY_SIZE;

        sTradeMenu->cursorSpriteId = CreateSprite(&sSpriteTemplate_Cursor,
                                                  sTradeMonSpriteCoords[sTradeMenu->cursorPosition][0] * 8 + 32,
                                                  sTradeMonSpriteCoords[sTradeMenu->cursorPosition][1] * 8, 2);
        gMain.state = 16;
        break;
    case 16:
        LoadTradeBgGfx(0);
        gMain.state++;
        break;
    case 17:
        LoadTradeBgGfx(1);
        sTradeMenu->bg2hofs = 0;
        sTradeMenu->bg3hofs = 0;
        SetActiveMenuOptions();
        gMain.state++;
        break;
    case 18:
        gPaletteFade.bufferTransferDisabled = FALSE;
        BlendPalettes(PALETTES_ALL, 16, RGB_BLACK);
        BeginNormalPaletteFade(PALETTES_ALL, 0, 16, 0, RGB_BLACK);
        gMain.state++;
        break;
    case 19:
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON);
        LoadTradeBgGfx(2);
        gMain.state++;
        break;
    case 20:
        gMain.state++;
        break;
    case 21:
        SetTradePartyHPBarSprites();
        gMain.state++;
        break;
    case 22:
        if (!gPaletteFade.active)
            SetMainCallback2(CB2_TradeMenu);
        break;
    }

    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}

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

static void CB_FadeToStartTrade(void)
{
    if (++sTradeMenu->timer >= 16)
    {
        BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
        sTradeMenu->callbackId = CB_WAIT_TO_START_TRADE;
    }
}

static void CB_WaitToStartTrade(void)
{
    if (!gPaletteFade.active)
    {
        gSelectedTradeMonPositions[TRADE_PLAYER] = sTradeMenu->cursorPosition;
        gSelectedTradeMonPositions[TRADE_PARTNER] = sTradeMenu->partnerCursorPosition;
        if (gWirelessCommType != 0)
        {
            sTradeMenu->callbackId = CB_WAIT_TO_START_RFU_TRADE;
        }
        else
        {
            SetCloseLinkCallbackAndType(32);
            sTradeMenu->callbackId = CB_START_LINK_TRADE;
        }
    }
}

static void CB_StartLinkTrade(void)
{
    gMain.savedCallback = CB2_StartCreateTradeMenu;
    if (gWirelessCommType != 0)
    {
        // Wireless Link Trade
        if (IsLinkRfuTaskFinished())
        {
            Free(sMenuTextTileBuffer);
            FreeAllWindowBuffers();
            Free(sTradeMenu);
            gMain.callback1 = NULL;
            DestroyWirelessStatusIndicatorSprite();
            SetMainCallback2(CB2_LinkTrade);
        }
    }
    else
    {
        // Cable Link Trade
        if (!gReceivedRemoteLinkPlayers)
        {
            Free(sMenuTextTileBuffer);
            FreeAllWindowBuffers();
            Free(sTradeMenu);
            gMain.callback1 = NULL;
            SetMainCallback2(CB2_LinkTrade);
        }
    }
}

static void CB2_TradeMenu(void)
{
    RunTradeMenuCallback();
    DoQueuedActions();

    // As long as drawSelectedMonState is 0, these do nothing
    DrawSelectedMonScreen(TRADE_PLAYER);
    DrawSelectedMonScreen(TRADE_PARTNER);

    SetGpuReg(REG_OFFSET_BG2HOFS, sTradeMenu->bg2hofs++);
    SetGpuReg(REG_OFFSET_BG3HOFS, sTradeMenu->bg3hofs--);
    RunTextPrinters_CheckPrinter0Active();
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}

static void LoadTradeBgGfx(u8 state)
{
    int i;

    switch (state)
    {
    case 0:
        LoadPalette(gTradeMenu_Pal, BG_PLTT_ID(0), 3 * PLTT_SIZE_4BPP);
        LoadBgTiles(1, gTradeMenu_Gfx, 0x1280, 0);
        CopyToBgTilemapBufferRect_ChangePalette(1, gTradeMenu_Tilemap, 0, 0, 32, 20, 0);
        LoadBgTilemap(2, sTradeStripesBG2Tilemap, 0x800, 0);
        break;
    case 1:
        LoadBgTilemap(3, sTradeStripesBG3Tilemap, 0x800, 0);
        PrintPartyLevelsAndGenders(TRADE_PLAYER);
        PrintPartyLevelsAndGenders(TRADE_PARTNER);
        CopyBgTilemapBufferToVram(1);
        break;
    case 2:
        for (i = 0; i < 4; i++)
        {
            // BG0 and BG1 coords only
            SetGpuReg(REG_OFFSET_BG0HOFS + 2 * i, 0);
        }
        ShowBg(0);
        ShowBg(1);
        ShowBg(2);
        ShowBg(3);
        break;
    }
}

// Determine (based on party counts) where the main menu cursor can go
static void SetActiveMenuOptions(void)
{
    int i;
    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (i < sTradeMenu->partyCounts[TRADE_PLAYER])
        {
            // Present player pokemon
            gSprites[sTradeMenu->partySpriteIds[TRADE_PLAYER][i]].invisible = FALSE;
            sTradeMenu->optionsActive[i] = TRUE;
        }
        else
        {
            // Absent player pokemon
            sTradeMenu->optionsActive[i] = FALSE;
        }

        if (i < sTradeMenu->partyCounts[TRADE_PARTNER])
        {
            // Present partner pokemon
            gSprites[sTradeMenu->partySpriteIds[TRADE_PARTNER][i]].invisible = FALSE;
            sTradeMenu->optionsActive[i + PARTY_SIZE] = TRUE;
        }
        else
        {
            // Absent partner pokemon
            sTradeMenu->optionsActive[i + PARTY_SIZE] = FALSE;
        }
    }

    // Cancel is always active
    sTradeMenu->optionsActive[PARTY_SIZE * 2] = TRUE;
}

static void Trade_Memcpy(void *dest, const void *src, size_t size)
{
    int i;
    u8 *_dest = dest;
    const u8 *_src = src;
    for (i = 0; i < size; i++)
        _dest[i] = _src[i];
}

static bool8 BufferTradeParties(void)
{
    u8 id = GetMultiplayerId();
    int i;
    struct Pokemon * mon;

    switch (sTradeMenu->bufferPartyState)
    {
    case 0:
        // The parties are sent in pairs rather than all at once
        Trade_Memcpy(gBlockSendBuffer, &gPlayerParty[0], 2 * sizeof(struct Pokemon));
        sTradeMenu->bufferPartyState++;
        sTradeMenu->timer = 0;
        break;
    case 1:
        if (IsLinkTaskFinished())
        {
            if (GetBlockReceivedStatus() == 0)
            {
                sTradeMenu->bufferPartyState++;
            }
            else
            {
                ResetBlockReceivedFlags();
                sTradeMenu->bufferPartyState++;
            }
        }
        break;
    case 3:
        if (id == 0)
            SendBlockRequest(BLOCK_REQ_SIZE_200);
        sTradeMenu->bufferPartyState++;
        break;
    case 4:
        if (GetBlockReceivedStatus() == 3)
        {
            Trade_Memcpy(&gEnemyParty[0], gBlockRecvBuffer[id ^ 1], 2 * sizeof(struct Pokemon));
            ResetBlockReceivedFlags();
            sTradeMenu->bufferPartyState++;
        }
        break;
    case 5:
        Trade_Memcpy(gBlockSendBuffer, &gPlayerParty[2], 2 * sizeof(struct Pokemon));
        sTradeMenu->bufferPartyState++;
        break;
    case 7:
        if (id == 0)
            SendBlockRequest(BLOCK_REQ_SIZE_200);
        sTradeMenu->bufferPartyState++;
        break;
    case 8:
        if (GetBlockReceivedStatus() == 3)
        {
            Trade_Memcpy(&gEnemyParty[2], gBlockRecvBuffer[id ^ 1], 2 * sizeof(struct Pokemon));
            ResetBlockReceivedFlags();
            sTradeMenu->bufferPartyState++;
        }
        break;
    case 9:
        Trade_Memcpy(gBlockSendBuffer, &gPlayerParty[4], 2 * sizeof(struct Pokemon));
        sTradeMenu->bufferPartyState++;
        break;
    case 11:
        if (id == 0)
            SendBlockRequest(BLOCK_REQ_SIZE_200);
        sTradeMenu->bufferPartyState++;
        break;
    case 12:
        if (GetBlockReceivedStatus() == 3)
        {
            Trade_Memcpy(&gEnemyParty[4], gBlockRecvBuffer[id ^ 1], 2 * sizeof(struct Pokemon));
            ResetBlockReceivedFlags();
            sTradeMenu->bufferPartyState++;
        }
        break;
    case 13:
        Trade_Memcpy(gBlockSendBuffer, gSaveBlock1Ptr->mail, PARTY_SIZE * sizeof(struct Mail) + 4); // why the extra 4 bytes?
        sTradeMenu->bufferPartyState++;
        break;
    case 15:
        if (id == 0)
            SendBlockRequest(BLOCK_REQ_SIZE_220);
        sTradeMenu->bufferPartyState++;
        break;
    case 16:
        if (GetBlockReceivedStatus() == 3)
        {
            Trade_Memcpy(gLinkPartnerMail, gBlockRecvBuffer[id ^ 1], PARTY_SIZE * sizeof(struct Mail));
            ResetBlockReceivedFlags();
            sTradeMenu->bufferPartyState++;
        }
        break;
    case 17:
        Trade_Memcpy(gBlockSendBuffer, gSaveBlock1Ptr->giftRibbons, sizeof(sTradeMenu->giftRibbons));
        sTradeMenu->bufferPartyState++;
        break;
    case 19:
        if (id == 0)
            SendBlockRequest(BLOCK_REQ_SIZE_40);
        sTradeMenu->bufferPartyState++;
        break;
    case 20:
        if (GetBlockReceivedStatus() == 3)
        {
            Trade_Memcpy(sTradeMenu->giftRibbons, gBlockRecvBuffer[id ^ 1], sizeof(sTradeMenu->giftRibbons));
            ResetBlockReceivedFlags();
            sTradeMenu->bufferPartyState++;
        }
        break;
    case 21:
        for (i = 0, mon = gEnemyParty; i < PARTY_SIZE; mon++, i++)
        {
            u8 name[POKEMON_NAME_LENGTH + 1];
            u16 species = GetMonData(mon, MON_DATA_SPECIES);

            if (species != SPECIES_NONE)
            {
                if (species == SPECIES_SHEDINJA && GetMonData(mon, MON_DATA_LANGUAGE) != LANGUAGE_JAPANESE)
                {
                    GetMonData(mon, MON_DATA_NICKNAME, name);

                    if (!StringCompareWithoutExtCtrlCodes(name, sText_ShedinjaJP))
                        SetMonData(mon, MON_DATA_NICKNAME, gSpeciesNames[SPECIES_SHEDINJA]);
                }
            }
        }
        return TRUE;
    // Delay until next state
    case 2:
    case 6:
    case 10:
    case 14:
    case 18:
        sTradeMenu->timer++;
        if (sTradeMenu->timer > 10)
        {
            sTradeMenu->timer = 0;
            sTradeMenu->bufferPartyState++;
        }
        break;
    }
    return FALSE;
}

static void PrintIsThisTradeOkay(void)
{
    DrawBottomRowText(gText_IsThisTradeOkay, (u8 *)OBJ_VRAM0 + sTradeMenu->bottomTextTileStart * 32, 0x18);
}

static void Leader_ReadLinkBuffer(u8 mpId, u8 status)
{
    if (status & 1)
    {
        switch (gBlockRecvBuffer[0][0])
        {
        case LINKCMD_REQUEST_CANCEL:
            sTradeMenu->playerSelectStatus = STATUS_CANCEL;
            break;
        case LINKCMD_READY_TO_TRADE:
            sTradeMenu->playerSelectStatus = STATUS_READY;
            break;
        case LINKCMD_INIT_BLOCK:
            sTradeMenu->playerConfirmStatus = STATUS_READY;
            break;
        case LINKCMD_READY_CANCEL_TRADE:
            sTradeMenu->playerConfirmStatus = STATUS_CANCEL;
            break;
        }
        ResetBlockReceivedFlag(0);
    }

    if (status & 2)
    {
        switch (gBlockRecvBuffer[1][0])
        {
        case LINKCMD_REQUEST_CANCEL:
            sTradeMenu->partnerSelectStatus = STATUS_CANCEL;
            break;
        case LINKCMD_READY_TO_TRADE:
            sTradeMenu->partnerCursorPosition = gBlockRecvBuffer[1][1] + PARTY_SIZE;
            sTradeMenu->partnerSelectStatus = STATUS_READY;
            break;
        case LINKCMD_INIT_BLOCK:
            sTradeMenu->partnerConfirmStatus = STATUS_READY;
            break;
        case LINKCMD_READY_CANCEL_TRADE:
            sTradeMenu->partnerConfirmStatus = STATUS_CANCEL;
            break;
        }
        ResetBlockReceivedFlag(1);
    }
}

static void Follower_ReadLinkBuffer(u8 mpId, u8 status)
{
    if (status & 1)
    {
        switch (gBlockRecvBuffer[0][0])
        {
        case LINKCMD_BOTH_CANCEL_TRADE:
            BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
            PrintTradeMessage(MSG_WAITING_FOR_FRIEND);
            sTradeMenu->callbackId = CB_INIT_EXIT_CANCELED_TRADE;
            break;
        case LINKCMD_PARTNER_CANCEL_TRADE:
            PrintTradeMessage(MSG_FRIEND_WANTS_TO_TRADE);
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
            break;
        case LINKCMD_SET_MONS_TO_TRADE:
            sTradeMenu->partnerCursorPosition = gBlockRecvBuffer[0][1] + PARTY_SIZE;
            rbox_fill_rectangle(0);
            SetSelectedMon(sTradeMenu->cursorPosition);
            SetSelectedMon(sTradeMenu->partnerCursorPosition);
            sTradeMenu->callbackId = CB_PRINT_IS_THIS_OKAY;
            break;
        case LINKCMD_START_TRADE:
            BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
            sTradeMenu->callbackId = CB_WAIT_TO_START_TRADE;
            break;
        case LINKCMD_PLAYER_CANCEL_TRADE:
            PrintTradeMessage(MSG_CANCELED);
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
        }
        ResetBlockReceivedFlag(0);
    }

    if (status & 2)
        ResetBlockReceivedFlag(1);
}

#define QueueLinkData(linkCmd, cursorPosition) \
{ \
    sTradeMenu->linkData[0] = (linkCmd); \
    sTradeMenu->linkData[1] = (cursorPosition); \
    QueueAction(QUEUE_DELAY_DATA, QUEUE_SEND_DATA); \
}

static void Leader_HandleCommunication(void)
{
    if (sTradeMenu->playerSelectStatus != STATUS_NONE
     && sTradeMenu->partnerSelectStatus != STATUS_NONE)
    {
        if (sTradeMenu->playerSelectStatus == STATUS_READY
         && sTradeMenu->partnerSelectStatus == STATUS_READY)
        {
            // Both players have selected a pokemon to trade 
            sTradeMenu->callbackId = CB_SET_SELECTED_MONS;
            QueueLinkData(LINKCMD_SET_MONS_TO_TRADE, sTradeMenu->cursorPosition);
            sTradeMenu->playerSelectStatus = sTradeMenu->partnerSelectStatus = STATUS_NONE;
        }
        else if (sTradeMenu->playerSelectStatus == STATUS_READY
              && sTradeMenu->partnerSelectStatus == STATUS_CANCEL)
        {
            // The player has selected a pokemon to trade,
            // but the partner has selected Cancel
            PrintTradeMessage(MSG_CANCELED);
            QueueLinkData(LINKCMD_PARTNER_CANCEL_TRADE, 0);
            sTradeMenu->playerConfirmStatus = sTradeMenu->partnerConfirmStatus = STATUS_NONE;
            sTradeMenu->playerSelectStatus = sTradeMenu->partnerSelectStatus = STATUS_NONE;
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
        }
        else if (sTradeMenu->playerSelectStatus == STATUS_CANCEL
              && sTradeMenu->partnerSelectStatus == STATUS_READY)
        {
            // The partner has selected a pokemon to trade,
            // but the player has selected cancel
            PrintTradeMessage(MSG_FRIEND_WANTS_TO_TRADE);
            QueueLinkData(LINKCMD_PLAYER_CANCEL_TRADE, 0);
            sTradeMenu->playerConfirmStatus = sTradeMenu->partnerConfirmStatus = STATUS_NONE;
            sTradeMenu->playerSelectStatus = sTradeMenu->partnerSelectStatus = STATUS_NONE;
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
        }
        else if (sTradeMenu->playerSelectStatus == STATUS_CANCEL
              && sTradeMenu->partnerSelectStatus == STATUS_CANCEL)
        {
            // Both players have selected Cancel
            QueueLinkData(LINKCMD_BOTH_CANCEL_TRADE, 0);
            BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
            sTradeMenu->playerSelectStatus = sTradeMenu->partnerSelectStatus = STATUS_NONE;
            sTradeMenu->callbackId = CB_INIT_EXIT_CANCELED_TRADE;
        }
    }

    if (sTradeMenu->playerConfirmStatus != STATUS_NONE
     && sTradeMenu->partnerConfirmStatus != STATUS_NONE)
    {
        if (sTradeMenu->playerConfirmStatus == STATUS_READY
         && sTradeMenu->partnerConfirmStatus == STATUS_READY)
        {
            // Both players have confirmed the trade
            QueueLinkData(LINKCMD_START_TRADE, 0);
            sTradeMenu->playerConfirmStatus = STATUS_NONE;
            sTradeMenu->partnerConfirmStatus = STATUS_NONE;
            sTradeMenu->callbackId = CB_FADE_TO_START_TRADE;
        }

        if (sTradeMenu->playerConfirmStatus == STATUS_CANCEL
         || sTradeMenu->partnerConfirmStatus == STATUS_CANCEL)
        {
            // One of the players has decided not to confirm the trade,
            // or the trade was not allowed.
            PrintTradeMessage(MSG_CANCELED);
            QueueLinkData(LINKCMD_PLAYER_CANCEL_TRADE, 0);
            sTradeMenu->playerConfirmStatus = STATUS_NONE;
            sTradeMenu->partnerConfirmStatus = STATUS_NONE;
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
        }
    }
}

static void CB1_UpdateLink(void)
{
    u8 mpId = GetMultiplayerId();
    u8 status;

    if ((status = GetBlockReceivedStatus()))
    {
        if (mpId == 0)
            Leader_ReadLinkBuffer(mpId, status);
        else
            Follower_ReadLinkBuffer(mpId, status);
    }
    if (mpId == 0)
        Leader_HandleCommunication();
}

static u8 GetNewCursorPosition(u8 oldPosition, u8 direction)
{
    int i;
    u8 newPosition = 0;

    for (i = 0; i < PARTY_SIZE; i++)
    {
        if (sTradeMenu->optionsActive[sCursorMoveDestinations[oldPosition][direction][i]] == TRUE)
        {
            newPosition = sCursorMoveDestinations[oldPosition][direction][i];
            break;
        }
    }

    return newPosition;
}


static void TradeMenuMoveCursor(u8 *cursorPosition, u8 direction)
{
    u8 newPosition = GetNewCursorPosition(*cursorPosition, direction);

    if (newPosition == PARTY_SIZE * 2) // CANCEL
    {
        StartSpriteAnim(&gSprites[sTradeMenu->cursorSpriteId], CURSOR_ANIM_ON_CANCEL);
        gSprites[sTradeMenu->cursorSpriteId].x = DISPLAY_WIDTH - 16;
        gSprites[sTradeMenu->cursorSpriteId].y = DISPLAY_HEIGHT;
    }
    else
    {
        StartSpriteAnim(&gSprites[sTradeMenu->cursorSpriteId], CURSOR_ANIM_NORMAL);
        gSprites[sTradeMenu->cursorSpriteId].x = sTradeMonSpriteCoords[newPosition][0] * 8 + 32;
        gSprites[sTradeMenu->cursorSpriteId].y = sTradeMonSpriteCoords[newPosition][1] * 8;
    }

    if (*cursorPosition != newPosition)
        PlaySE(SE_SELECT);

    *cursorPosition = newPosition;
}

static void SetReadyToTrade(void)
{
    PrintTradeMessage(MSG_STANDBY);
    sTradeMenu->callbackId = CB_READY_WAIT;

    if (GetMultiplayerId() == 1)
    {
        // Communicate to the link leader that we're ready to trade
        sTradeMenu->linkData[0] = LINKCMD_READY_TO_TRADE;
        sTradeMenu->linkData[1] = sTradeMenu->cursorPosition;
        SendBlock(BitmaskAllOtherLinkPlayers(), sTradeMenu->linkData, 20);
    }
    else
    {
        // We are the link leader, no communication necessary
        sTradeMenu->playerSelectStatus = STATUS_READY;
    }
}

static void CB_ProcessMenuInput(void)
{
    int i;

    if (JOY_REPT(DPAD_UP))
        TradeMenuMoveCursor(&sTradeMenu->cursorPosition, DIR_UP);
    else if (JOY_REPT(DPAD_DOWN))
        TradeMenuMoveCursor(&sTradeMenu->cursorPosition, DIR_DOWN);
    else if (JOY_REPT(DPAD_LEFT))
        TradeMenuMoveCursor(&sTradeMenu->cursorPosition, DIR_LEFT);
    else if (JOY_REPT(DPAD_RIGHT))
        TradeMenuMoveCursor(&sTradeMenu->cursorPosition, DIR_RIGHT);

    if (JOY_NEW(A_BUTTON))
    {
        PlaySE(SE_SELECT);

        if (sTradeMenu->cursorPosition < PARTY_SIZE)
        {
            // Selected pokemon in player's party
            DrawTextBorderOuter(1, 1, 14);
            FillWindowPixelBuffer(1, PIXEL_FILL(1));
            PrintMenuTable(1, FONT_NORMAL_COPY_2, 16, ARRAY_COUNT(sMenuAction_SummaryTrade), sMenuAction_SummaryTrade);
            Menu_InitCursor(1, FONT_NORMAL_COPY_2, 0, 0, 16, 2, 0);
            PutWindowTilemap(1);
            CopyWindowToVram(1, COPYWIN_FULL);
            sTradeMenu->callbackId = CB_SELECTED_MON;
        }
        else if (sTradeMenu->cursorPosition < PARTY_SIZE * 2)
        {
            // Selected pokemon in partner's party
            BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
            sTradeMenu->callbackId = CB_SHOW_MON_SUMMARY;
        }
        else if (sTradeMenu->cursorPosition == PARTY_SIZE * 2)
        {
            // Selected Cancel
            CreateYesNoMenu(&sWindowTemplate_YesNo, FONT_NORMAL_COPY_2, 0, 2, 0x001, 14, 0);
            sTradeMenu->callbackId = CB_CANCEL_TRADE_PROMPT;
            DrawBottomRowText(sActionTexts[TEXT_CANCEL_TRADE], (void *)OBJ_VRAM0 + sTradeMenu->bottomTextTileStart * 32, 24);
        }
    }

    // This option is unavailable in Emerald
    if (JOY_NEW(R_BUTTON))
    {
        for (i = 0; i < 10; i++)
            sTradeMenu->linkData[i] = i;
        SendBlock(BitmaskAllOtherLinkPlayers(), sTradeMenu->linkData, 20);
    }
}

static void RedrawChooseAPokemonWindow(void)
{
    PrintTradePartnerPartyNicknames();
    sTradeMenu->callbackId = CB_MAIN_MENU;
    gSprites[sTradeMenu->cursorSpriteId].invisible = FALSE;
    DrawBottomRowText(sActionTexts[TEXT_CHOOSE_MON], (void *)OBJ_VRAM0 + sTradeMenu->bottomTextTileStart * 32, 24);
}

static void CB_ProcessSelectedMonInput(void)
{
    switch (Menu_ProcessInputNoWrapAround())
    {
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
        RedrawChooseAPokemonWindow();
        break;
    case MENU_NOTHING_CHOSEN:
        break;
    case 0: // Summary
        BeginNormalPaletteFade(PALETTES_ALL, 0, 0, 16, RGB_BLACK);
        sTradeMenu->callbackId = CB_SHOW_MON_SUMMARY;
        break;
    case 1: // Trade
        switch (CanTradeSelectedMon(gPlayerParty, gPlayerPartyCount, sTradeMenu->cursorPosition))
        {
        case CAN_TRADE_MON:
            SetReadyToTrade();
            gSprites[sTradeMenu->cursorSpriteId].invisible = TRUE;
            break;
        case CANT_TRADE_LAST_MON:
            QueueAction(QUEUE_DELAY_MSG, QUEUE_ONLY_MON2);
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
            break;
        case CANT_TRADE_NATIONAL:
        case CANT_TRADE_INVALID_MON:
            QueueAction(QUEUE_DELAY_MSG, QUEUE_MON_CANT_BE_TRADED);
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
            break;
        case CANT_TRADE_EGG_YET:
        case CANT_TRADE_PARTNER_EGG_YET:
            QueueAction(QUEUE_DELAY_MSG, QUEUE_EGG_CANT_BE_TRADED);
            sTradeMenu->callbackId = CB_HANDLE_TRADE_CANCELED;
            break;
        }
        break;
    }
}

static void CB_ChooseMonAfterButtonPress(void)
{
    if (JOY_NEW(A_BUTTON) || JOY_NEW(B_BUTTON))
    {
        PlaySE(SE_SELECT);
        RedrawChooseAPokemonWindow();
    }
}

static void CB_ShowTradeMonSummaryScreen(void)
{
    if (!gPaletteFade.active)
    {
        if (sTradeMenu->cursorPosition < PARTY_SIZE)
            ShowPokemonSummaryScreen(gPlayerParty, sTradeMenu->cursorPosition, sTradeMenu->partyCounts[0] - 1, CB2_ReturnToTradeMenuFromSummary, PSS_MODE_TRADE);
        else
            ShowPokemonSummaryScreen(gEnemyParty, sTradeMenu->cursorPosition - PARTY_SIZE, sTradeMenu->partyCounts[1] - 1, CB2_ReturnToTradeMenuFromSummary, PSS_MODE_TRADE);
        FreeAllWindowBuffers();
    }
}

static u8 CheckValidityOfTradeMons(u8 *aliveMons, u8 playerPartyCount, u8 cursorPos)
{
    s32 i;
    u16 partnerSpecies;
    u8 hasLiveMon = 0;

    // Make sure mon to be traded isn't player's last alive mon
    for (i = 0; i < playerPartyCount; i++)
    {
        if (cursorPos != i)
            hasLiveMon += aliveMons[i];
    }

    // Partner cant trade illegitimate Deoxys or Mew
    partnerSpecies = GetMonData(&gEnemyParty[sTradeMenu->partnerCursorPosition % PARTY_SIZE], MON_DATA_SPECIES);
    if ((partnerSpecies == SPECIES_DEOXYS || partnerSpecies == SPECIES_MEW)
        && !GetMonData(&gEnemyParty[sTradeMenu->partnerCursorPosition % PARTY_SIZE], MON_DATA_MODERN_FATEFUL_ENCOUNTER))
        return PARTNER_MON_INVALID;

    if (hasLiveMon != 0)
        hasLiveMon = BOTH_MONS_VALID;

    return hasLiveMon; // PLAYER_MON_INVALID or BOTH_MONS_VALID
}

static void CommunicateWhetherMonCanBeTraded(void)
{
    int i;
    u8 aliveMons[PARTY_SIZE * 2];

    for (i = 0; i < sTradeMenu->partyCounts[TRADE_PLAYER]; i++)
        aliveMons[i] = sTradeMenu->isLiveMon[TRADE_PLAYER][i];

    switch (CheckValidityOfTradeMons(aliveMons, sTradeMenu->partyCounts[0], sTradeMenu->cursorPosition))
    {
    case PLAYER_MON_INVALID:
        QueueAction(QUEUE_DELAY_MSG, QUEUE_ONLY_MON2);
        sTradeMenu->linkData[0] = LINKCMD_READY_CANCEL_TRADE;
        QueueAction(180, QUEUE_SEND_DATA);
        break;
    case BOTH_MONS_VALID:
        QueueAction(QUEUE_DELAY_MSG, QUEUE_STANDBY);
        sTradeMenu->linkData[0] = LINKCMD_INIT_BLOCK;
        if (IsLinkTaskFinished())
            SendBlock(BitmaskAllOtherLinkPlayers(), sTradeMenu->linkData, 20);
        break;
    case PARTNER_MON_INVALID:
        QueueAction(QUEUE_DELAY_MSG, QUEUE_FRIENDS_MON_CANT_BE_TRADED);
        sTradeMenu->linkData[0] = LINKCMD_READY_CANCEL_TRADE;
        QueueAction(180, QUEUE_SEND_DATA);
        break;
    default:
        // Both pokemon are valid to trade
        break;
    }
}

static void CB_ProcessConfirmTradeInput(void)
{
    switch (Menu_ProcessInputNoWrapClearOnChoose())
    {
    case 0: // YES, Confirm selection
        CommunicateWhetherMonCanBeTraded();
        sTradeMenu->callbackId = CB_IDLE;
        PutWindowTilemap(17);
        break;
    case 1: // NO, Cancel Trade
    case MENU_B_PRESSED:
        QueueAction(QUEUE_DELAY_MSG, QUEUE_STANDBY);
        if (IsLinkTaskFinished())
        {
            sTradeMenu->linkData[0] = LINKCMD_READY_CANCEL_TRADE;
            SendBlock(BitmaskAllOtherLinkPlayers(), sTradeMenu->linkData, 20);
        }
        sTradeMenu->callbackId = CB_IDLE;
        PutWindowTilemap(17);
        break;
    }
}

// Only when choosing Yes to cancel, when No is chosen all are redrawn anyway
static void RestoreNicknamesCoveredByYesNo(void)
{
    int i;

    for (i = 0; i < sTradeMenu->partyCounts[1] - 4; i++)
    {
        PutWindowTilemap(i + PARTY_SIZE * 2);
        CopyWindowToVram(i + PARTY_SIZE * 2, COPYWIN_MAP);
    }
}

static void CB_ProcessCancelTradeInput(void)
{
    switch (Menu_ProcessInputNoWrapClearOnChoose())
    {
    case 0: // YES, Cancel
        PrintTradeMessage(MSG_WAITING_FOR_FRIEND);
        QueueLinkData(LINKCMD_REQUEST_CANCEL, 0);
        gSprites[sTradeMenu->cursorSpriteId].invisible = TRUE;
        sTradeMenu->callbackId = CB_IDLE;
        RestoreNicknamesCoveredByYesNo();
        break;
    case 1: // NO, Continue
    case MENU_B_PRESSED:
        PlaySE(SE_SELECT);
        RedrawChooseAPokemonWindow();
        break;
    }
}

static void CB_SetSelectedMons(void)
{
    if (GetMultiplayerId() == 0)
    {
        rbox_fill_rectangle(0);
        SetSelectedMon(sTradeMenu->cursorPosition);
        SetSelectedMon(sTradeMenu->partnerCursorPosition);
    }
    sTradeMenu->callbackId = CB_PRINT_IS_THIS_OKAY;
}

static void CB_PrintIsThisTradeOkay(void)
{
    if (sTradeMenu->drawSelectedMonState[TRADE_PLAYER] == DRAW_SELECTED_FINISH
     && sTradeMenu->drawSelectedMonState[TRADE_PARTNER] == DRAW_SELECTED_FINISH)
    {
        PrintIsThisTradeOkay();
        sTradeMenu->callbackId = CB_INIT_CONFIRM_TRADE_PROMPT;
    }
}

static void CB_InitConfirmTradePrompt(void)
{
    sTradeMenu->timer++;
    if (sTradeMenu->timer > 120)
    {
        CreateYesNoMenu(&sWindowTemplate_YesNo, FONT_NORMAL_COPY_2, 0, 2, 1, 14, 0);
        sTradeMenu->timer = 0;
        sTradeMenu->callbackId = CB_CONFIRM_TRADE_PROMPT;
    }
}

static void CB_HandleTradeCanceled(void)
{
    int i;

    if (JOY_NEW(A_BUTTON))
    {
        PlaySE(SE_SELECT);
        rbox_fill_rectangle(0);
        rbox_fill_rectangle(1);

        for (i = 0; i < 4; i++)
        {
            FillWindowPixelBuffer(i + 14, PIXEL_FILL(0));
            rbox_fill_rectangle(i + 14);
        }

        RedrawPartyWindow(TRADE_PLAYER);
        RedrawPartyWindow(TRADE_PARTNER);
        sTradeMenu->callbackId = CB_MAIN_MENU;
        gSprites[sTradeMenu->cursorSpriteId].invisible = FALSE;
    }
}

static void CB_InitExitCanceledTrade(void)
{
#if REVISION >= 0xA
    if (IsLinkTaskFinished() && !gPaletteFade.active)
#else
    if (!gPaletteFade.active)
#endif
    {
        if (gWirelessCommType)
            SetLinkStandbyCallback();
        else
            SetCloseLinkCallbackAndType(12);

        sTradeMenu->callbackId = CB_EXIT_CANCELED_TRADE;
    }
}

static void CB_ExitCanceledTrade(void)
{
    if (gWirelessCommType)
    {
        if (IsLinkTaskFinished())
        {
            Free(sMenuTextTileBuffer);
            Free(sTradeMenu);
            FreeAllWindowBuffers();
            DestroyWirelessStatusIndicatorSprite();
            SetMainCallback2(CB2_ReturnToFieldFromMultiplayer);
        }
    }
    else
    {
        if (!gReceivedRemoteLinkPlayers)
        {
            Free(sMenuTextTileBuffer);
            Free(sTradeMenu);
            FreeAllWindowBuffers();
            SetMainCallback2(CB2_ReturnToFieldFromMultiplayer);
        }
    }
}

static void CB_WaitToStartRfuTrade(void)
{
    if (!Rfu_SetLinkRecovery(FALSE))
    {
        SetLinkStandbyCallback();
        sTradeMenu->callbackId = CB_START_LINK_TRADE;
    }
}

static void RunTradeMenuCallback(void)
{
    switch (sTradeMenu->callbackId)
    {
    case CB_MAIN_MENU:
        CB_ProcessMenuInput();
        break;
    case CB_SELECTED_MON:
        CB_ProcessSelectedMonInput();
        break;
    case CB_SHOW_MON_SUMMARY:
        CB_ShowTradeMonSummaryScreen();
        break;
    case CB_CONFIRM_TRADE_PROMPT:
        CB_ProcessConfirmTradeInput();
        break;
    case CB_CANCEL_TRADE_PROMPT:
        CB_ProcessCancelTradeInput();
        break;
    case CB_READY_WAIT:
        // nop
        break;
    case CB_SET_SELECTED_MONS:
        CB_SetSelectedMons();
        break;
    case CB_PRINT_IS_THIS_OKAY:
        CB_PrintIsThisTradeOkay();
        break;
    case CB_HANDLE_TRADE_CANCELED:
        CB_HandleTradeCanceled();
        break;
    case CB_FADE_TO_START_TRADE:
        CB_FadeToStartTrade();
        break;
    case CB_WAIT_TO_START_TRADE:
        CB_WaitToStartTrade();
        break;
    case CB_INIT_EXIT_CANCELED_TRADE:
        CB_InitExitCanceledTrade();
        break;
    case CB_EXIT_CANCELED_TRADE:
        CB_ExitCanceledTrade();
        break;
    case CB_START_LINK_TRADE:
        CB_StartLinkTrade();
        break;
    case CB_INIT_CONFIRM_TRADE_PROMPT:
        CB_InitConfirmTradePrompt();
        break;
    case CB_UNUSED_CLOSE_MSG:
        CB_ChooseMonAfterButtonPress();
        break;
    case CB_WAIT_TO_START_RFU_TRADE:
        CB_WaitToStartRfuTrade();
        break;
    }
    // CB_IDLE is nop
}

static void SetSelectedMon(u8 cursorPosition)
{
    // cursorPosition 0-5 are the player's mons, 6-11 are the partner's
    u8 whichParty = cursorPosition / PARTY_SIZE;

    if (sTradeMenu->drawSelectedMonState[whichParty] == 0)
    {
        // Start the animation to display just the selected
        // pokemon in the middle of the screen
        sTradeMenu->drawSelectedMonState[whichParty] = 1;
        sTradeMenu->selectedMonIdx[whichParty] = cursorPosition;
    }
}

static void DrawSelectedMonScreen(u8 whichParty)
{
    s8 nameStringWidth;
    u8 nickname[20];
    u8 movesString[56];
    u8 i;
    u8 partyIdx;
    u8 selectedMonParty;
    u8 selectedMonIdx = sTradeMenu->selectedMonIdx[whichParty];

    selectedMonParty = TRADE_PARTNER;
    if (sTradeMenu->selectedMonIdx[whichParty] < PARTY_SIZE)
        selectedMonParty = TRADE_PLAYER;
    partyIdx = selectedMonIdx % PARTY_SIZE;
    nameStringWidth = 0;

    switch (sTradeMenu->drawSelectedMonState[whichParty])
    {
    default:
        // Idle while state is 0, and once it reaches the final state (DRAW_SELECTED_FINISH)
        break;
    case 1:
        // Erase the rest of the party
        for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
            gSprites[sTradeMenu->partySpriteIds[selectedMonParty][i]].invisible = TRUE;
        for (i = 0; i < PARTY_SIZE; i++)
            ClearWindowTilemap(i + (whichParty * PARTY_SIZE + 2));

        // Re-display the selected pokemon
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].invisible = FALSE;

        // Move the selected pokemon to the center
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].data[0] = 20;
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].data[2] = (sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE][0]
                                                                                  + sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE + 1][0]) / 2 * 8 + 14;
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].data[4] = (sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE][1] * 8) - 12;
        StoreSpriteCallbackInData6(&gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]], SpriteCB_MonIcon);
        sTradeMenu->drawSelectedMonState[whichParty]++;
        Trade_MoveSelectedMonToTarget(&gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]]);
        
        CopyToBgTilemapBufferRect_ChangePalette(1, sTradePartyBoxTilemap, whichParty * 15, 0, 15, 17, 0);
        CopyBgTilemapBufferToVram(1);
        CopyBgTilemapBufferToVram(0);

        if (selectedMonParty == TRADE_PLAYER)
            PrintTradePartnerPartyNicknames();
        break;
    case 2:
        // Wait for the selected pokemon's sprite to move to the correct position
        if (gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].callback == SpriteCB_MonIcon)
            sTradeMenu->drawSelectedMonState[whichParty] = 3;
        break;
    case 3:
        // Redisplay the bg box
        CopyToBgTilemapBufferRect_ChangePalette(1, sTradeMovesBoxTilemap, selectedMonParty * 15, 0, 15, 17, 0);
        CopyBgTilemapBufferToVram(1);

        // Finalize the selected pokemon's position
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].x = (sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE][0]
                                                                            + sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE + 1][0]) / 2 * 8 + 14;
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].y = (sTradeMonSpriteCoords[selectedMonParty * PARTY_SIZE][1] * 8) - 12;
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].x2 = 0;
        gSprites[sTradeMenu->partySpriteIds[selectedMonParty][partyIdx]].y2 = 0;

        // Print selected pokemon's name and moves
        nameStringWidth = GetMonNicknameWidth(nickname, selectedMonParty, partyIdx);
        AddTextPrinterParameterized3((whichParty * 2) + 14, FONT_SMALL, (80 - nameStringWidth) / 2, 4, sTextColor_PartyMonNickname, 0, nickname);
        BufferMovesString(movesString, selectedMonParty, partyIdx);
        AddTextPrinterParameterized4((whichParty * 2) + 15, FONT_NORMAL_COPY_1, 0, 0, 0, 0, sTextColor_PartyMonNickname, 0, movesString);
        PutWindowTilemap((whichParty * 2) + 14);
        CopyWindowToVram((whichParty * 2) + 14, COPYWIN_FULL);
        PutWindowTilemap((whichParty * 2) + 15);
        CopyWindowToVram((whichParty * 2) + 15, COPYWIN_FULL);

        sTradeMenu->drawSelectedMonState[whichParty]++;
        break;
    case 4:
        PrintLevelAndGender(whichParty, partyIdx,
            sSelectedMonLevelGenderCoords[whichParty][0] + 4,
            sSelectedMonLevelGenderCoords[whichParty][1] + 1,
            sSelectedMonLevelGenderCoords[whichParty][0],
            sSelectedMonLevelGenderCoords[whichParty][1]);
        sTradeMenu->drawSelectedMonState[whichParty]++;
        break;
    }
}

static u8 GetMonNicknameWidth(u8 *dest, u8 whichParty, u8 partyIdx)
{
    u8 nickname[POKEMON_NAME_LENGTH];
    if (whichParty == TRADE_PLAYER)
        GetMonData(&gPlayerParty[partyIdx], MON_DATA_NICKNAME, nickname);
    else
        GetMonData(&gEnemyParty[partyIdx], MON_DATA_NICKNAME, nickname);
    StringCopy_Nickname(dest, nickname);
    return GetStringWidth(FONT_SMALL, dest, GetFontAttribute(FONT_SMALL, FONTATTR_LETTER_SPACING));
}

static void BufferMovesString(u8 *movesString, u8 whichParty, u8 partyIdx)
{
    u16 moves[MAX_MON_MOVES];
    u16 i;

    if (!sTradeMenu->isEgg[whichParty][partyIdx])
    {
        for (i = 0; i < MAX_MON_MOVES; i++)
        {
            if (whichParty == TRADE_PLAYER)
                moves[i] = GetMonData(&gPlayerParty[partyIdx], i + MON_DATA_MOVE1, NULL);
            else
                moves[i] = GetMonData(&gEnemyParty[partyIdx], i + MON_DATA_MOVE1, NULL);
        }

        StringCopy(movesString, sText_Dummy);

        for (i = 0; i < MAX_MON_MOVES; i++)
        {
            if (moves[i] != MOVE_NONE)
                StringAppend(movesString, gMoveNames[moves[i]]);

            StringAppend(movesString, sText_Newline);
        }
    }
    else
    {
        StringCopy(movesString, sText_Dummy);
        StringAppend(movesString, gText_4Qmark);
    }
}

static void PrintPartyMonNickname(u8 whichParty, u8 windowId, const u8 *str)
{
    u8 xPos;
    s8 speed;
    windowId += (whichParty * PARTY_SIZE) + 2;
    speed = 0;
    xPos = (64u - GetStringWidth(FONT_SMALL, str, GetFontAttribute(FONT_SMALL, FONTATTR_LETTER_SPACING))) / 2;
    AddTextPrinterParameterized3(windowId, FONT_SMALL, xPos, 4, sTextColor_PartyMonNickname, speed, str);
    PutWindowTilemap(windowId);
    CopyWindowToVram(windowId, COPYWIN_FULL);
}

static void PrintPartyNicknames(u8 whichParty)
{
    u8 buff[20];
    u8 nickname[30];
    struct Pokemon * party = (whichParty == TRADE_PLAYER) ? gPlayerParty : gEnemyParty;
    u8 i;
    for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
    {
        GetMonData(&party[i], MON_DATA_NICKNAME, buff);
        StringCopy_Nickname(nickname, buff);
        PrintPartyMonNickname(whichParty, i, nickname);
    }
}

static void PrintLevelAndGender(u8 whichParty, u8 monIdx, u8 x, u8 y, u8 winLeft, u8 winTop)
{
    u8 level;
    u32 symbolTile;
    u8 gender;
    u8 nickname[POKEMON_NAME_LENGTH];

    CopyToBgTilemapBufferRect_ChangePalette(1, gTradeMenuMonBox_Tilemap, winLeft, winTop, 6, 3, 0);
    CopyBgTilemapBufferToVram(1);

    if (whichParty == TRADE_PLAYER)
        level = GetMonData(&gPlayerParty[monIdx], MON_DATA_LEVEL, NULL);
    else
        level = GetMonData(&gEnemyParty[monIdx], MON_DATA_LEVEL, NULL);

    if (!sTradeMenu->isEgg[whichParty][monIdx])
    {
        if (level / 10 != 0)
            sTradeMenu->tilemapBuffer[x + (y * 32)] = (level / 10) + 0x60;

        sTradeMenu->tilemapBuffer[x + (y * 32) + 1] = (level % 10) + 0x70;
    }
    else
    {
        sTradeMenu->tilemapBuffer[x + (y * 32) - 32] = sTradeMenu->tilemapBuffer[x + (y * 32) - 33];
        sTradeMenu->tilemapBuffer[x + (y * 32) - 31] = sTradeMenu->tilemapBuffer[x + (y * 32) - 36] | 0x400;
    }

    if (sTradeMenu->isEgg[whichParty][monIdx])
    {
        symbolTile = 0x480;
    }
    else
    {
        if (whichParty == TRADE_PLAYER)
        {
            gender = GetMonGender(&gPlayerParty[monIdx]);
            GetMonData(&gPlayerParty[monIdx], MON_DATA_NICKNAME, nickname);
        }
        else
        {
            gender = GetMonGender(&gEnemyParty[monIdx]);
            GetMonData(&gEnemyParty[monIdx], MON_DATA_NICKNAME, nickname);
        }

        switch (gender)
        {
        case MON_MALE:
            symbolTile = !NameHasGenderSymbol(nickname, MON_MALE) ? 0x84 : 0x83;
            break;
        case MON_FEMALE:
            symbolTile = !NameHasGenderSymbol(nickname, MON_FEMALE) ? 0x85 : 0x83;
            break;
        default:
            symbolTile = 0x83;
            break;
        }
    }
    sTradeMenu->tilemapBuffer[(y - 1) * 32 + x + 1] = symbolTile;
}

static void PrintPartyLevelsAndGenders(u8 whichParty)
{
    s32 i;
    for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
    {
        s32 j = i + PARTY_SIZE * whichParty;
        PrintLevelAndGender(
            whichParty,
            i,
            sTradeMonLevelCoords[j][0],
            sTradeMonLevelCoords[j][1],
            sTradeMonBoxCoords[j][0],
            sTradeMonBoxCoords[j][1]
        );
    }
}

static void ShowTradePartyMonIcons(u8 whichParty)
{
    int i;

    for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
    {
        gSprites[sTradeMenu->partySpriteIds[whichParty][i]].invisible = FALSE;
        gSprites[sTradeMenu->partySpriteIds[whichParty][i]].x = sTradeMonSpriteCoords[(whichParty * PARTY_SIZE) + i][0] * 8 + 14;
        gSprites[sTradeMenu->partySpriteIds[whichParty][i]].y = sTradeMonSpriteCoords[(whichParty * PARTY_SIZE) + i][1] * 8 - 12;
        gSprites[sTradeMenu->partySpriteIds[whichParty][i]].x2 = 0;
        gSprites[sTradeMenu->partySpriteIds[whichParty][i]].y2 = 0;
    }
}

static void PrintTradePartnerPartyNicknames(void)
{
    rbox_fill_rectangle(1);
    PrintPartyNicknames(1);
}

// Returning to the party selection screen from the "is this trade ok?" screen
static void RedrawPartyWindow(u8 whichParty)
{
    CopyToBgTilemapBufferRect_ChangePalette(1, sTradePartyBoxTilemap, 15 * whichParty, 0, 15, 17, 0);
    CopyBgTilemapBufferToVram(1);
    PrintPartyLevelsAndGenders(whichParty);
    PrintPartyNicknames(whichParty);
    ShowTradePartyMonIcons(whichParty);
    DrawBottomRowText(sActionTexts[TEXT_CHOOSE_MON], (void *)OBJ_VRAM0 + 32 * sTradeMenu->bottomTextTileStart, 24);
    sTradeMenu->drawSelectedMonState[whichParty] = 0;
}

static void Task_DrawSelectionSummary(u8 taskId)
{
    FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 30, 20);
    CopyBgTilemapBufferToVram(0);
}

static void Task_DrawSelectionTrade(u8 taskId)
{
    FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 30, 20);
    CopyBgTilemapBufferToVram(0);
}

static void QueueAction(u16 delay, u8 actionId)
{
    int i;
    for (i = 0; i < (int)ARRAY_COUNT(sTradeMenu->queuedActions); i++)
    {
        // Find first available spot
        if (!sTradeMenu->queuedActions[i].active)
        {
            sTradeMenu->queuedActions[i].delay = delay;
            sTradeMenu->queuedActions[i].actionId = actionId;
            sTradeMenu->queuedActions[i].active = TRUE;
            break;
        }
    }
}

static void DoQueuedActions(void)
{
    int i;

    for (i = 0; i < (int)ARRAY_COUNT(sTradeMenu->queuedActions); i++)
    {
        if (sTradeMenu->queuedActions[i].active)
        {
            if (sTradeMenu->queuedActions[i].delay != 0)
            {
                sTradeMenu->queuedActions[i].delay--;
            }
            else
            {
                switch (sTradeMenu->queuedActions[i].actionId)
                {
                case QUEUE_SEND_DATA:
                    SendBlock(BitmaskAllOtherLinkPlayers(), sTradeMenu->linkData, 20);
                    break;
                case QUEUE_STANDBY:
                    PrintTradeMessage(MSG_STANDBY);
                    break;
                case QUEUE_ONLY_MON1:
                    PrintTradeMessage(MSG_ONLY_MON1);
                    break;
                case QUEUE_ONLY_MON2:
                case QUEUE_UNUSED1:
                case QUEUE_UNUSED2:
                    PrintTradeMessage(MSG_ONLY_MON2);
                    break;
                case QUEUE_MON_CANT_BE_TRADED:
                    PrintTradeMessage(MSG_MON_CANT_BE_TRADED);
                    break;
                case QUEUE_EGG_CANT_BE_TRADED:
                    PrintTradeMessage(MSG_EGG_CANT_BE_TRADED);
                    break;
                case QUEUE_FRIENDS_MON_CANT_BE_TRADED:
                    PrintTradeMessage(MSG_FRIENDS_MON_CANT_BE_TRADED);
                    break;
                }
                sTradeMenu->queuedActions[i].active = FALSE;
            }
        }
    }
}

static void PrintTradeMessage(u8 messageId)
{
    FillWindowPixelBuffer(0, PIXEL_FILL(1));
    AddTextPrinterParameterized(0, FONT_NORMAL_COPY_2, sMessages[messageId], 0, 2, TEXT_SKIP_DRAW, NULL);
    DrawTextBorderOuter(0, 0x014, 12);
    PutWindowTilemap(0);
    CopyWindowToVram(0, COPYWIN_FULL);
}

static bool8 LoadUISpriteGfx(void)
{
    struct SpriteSheet sheet;

    if (sTradeMenu->timer < NUM_MENU_TEXT_SPRITES)
    {
        sheet.data = sMenuTextTileBuffers[sTradeMenu->timer];
        sheet.size = 0x100;
        sheet.tag = GFXTAG_MENU_TEXT + sTradeMenu->timer;
    }

    switch (sTradeMenu->timer)
    {
    case GFXTAG_PLAYER_NAME_L:
    case GFXTAG_PLAYER_NAME_M:
    case GFXTAG_PLAYER_NAME_R:
    case GFXTAG_PARTNER_NAME_L:
    case GFXTAG_PARTNER_NAME_M:
    case GFXTAG_PARTNER_NAME_R:
    case GFXTAG_CANCEL_L:
    case GFXTAG_CANCEL_R:
        LoadSpriteSheet(&sheet);
        sTradeMenu->timer++;
        break;
    case GFXTAG_CHOOSE_PKMN_L:
        sTradeMenu->bottomTextTileStart = LoadSpriteSheet(&sheet);
        sTradeMenu->timer++;
        break;
    case GFXTAG_CHOOSE_PKMN_M:
    case GFXTAG_CHOOSE_PKMN_R:
    case GFXTAG_CHOOSE_PKMN_EMPTY_1:
    case GFXTAG_CHOOSE_PKMN_EMPTY_2:
    case GFXTAG_CHOOSE_PKMN_EMPTY_3:
        LoadSpriteSheet(&sheet);
        sTradeMenu->timer++;
        break;
    case NUM_MENU_TEXT_SPRITES:
        LoadSpritePalette(&sSpritePalette_MenuText);
        sTradeMenu->timer++;
        break;
    case NUM_MENU_TEXT_SPRITES + 1:
        LoadSpritePalette(&sCursor_SpritePalette);
        sTradeMenu->timer++;
        break;
    case NUM_MENU_TEXT_SPRITES + 2:
        LoadSpriteSheet(&sCursor_SpriteSheet);
        sTradeMenu->timer++;
        break;
    case NUM_MENU_TEXT_SPRITES + 3:
        sTradeMenu->timer = 0;
        return TRUE;
    }

    return FALSE;
}

static void DrawBottomRowText(const u8 *name, u8 *dest, u8 unused)
{
    DrawTextWindowAndBufferTiles(name, dest, 0, 0, gDecompressionBuffer, 6);
}

static void ComputePartyTradeableFlags(u8 whichParty)
{
    int i;

    switch (whichParty)
    {
    case TRADE_PLAYER:
        for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
        {
            if (GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG) == TRUE)
            {
                sTradeMenu->isLiveMon[whichParty][i] = FALSE;
                sTradeMenu->isEgg[whichParty][i] = TRUE;
            }
            else if (GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0)
            {
                sTradeMenu->isLiveMon[whichParty][i] = FALSE;
                sTradeMenu->isEgg[whichParty][i] = FALSE;
            }
            else
            {
                sTradeMenu->isLiveMon[whichParty][i] = TRUE;
                sTradeMenu->isEgg[whichParty][i] = FALSE;
            }
        }
        break;
    case TRADE_PARTNER:
        for (i = 0; i < sTradeMenu->partyCounts[whichParty]; i++)
        {
            if (GetMonData(&gEnemyParty[i], MON_DATA_IS_EGG) == TRUE)
            {
                sTradeMenu->isLiveMon[whichParty][i] = FALSE;
                sTradeMenu->isEgg[whichParty][i] = TRUE;
            }
            else if (GetMonData(&gEnemyParty[i], MON_DATA_HP) == 0)
            {
                sTradeMenu->isLiveMon[whichParty][i] = FALSE;
                sTradeMenu->isEgg[whichParty][i] = FALSE;
            }
            else
            {
                sTradeMenu->isLiveMon[whichParty][i] = TRUE;
                sTradeMenu->isEgg[whichParty][i] = FALSE;
            }
        }
        break;
    }
}

static void ComputePartyHPBarLevels(u8 whichParty)
{
    u16 i, curHp, maxHp;

    switch (whichParty)
    {
    case TRADE_PLAYER:
        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PLAYER]; i++)
        {
            curHp = GetMonData(&gPlayerParty[i], MON_DATA_HP);
            maxHp = GetMonData(&gPlayerParty[i], MON_DATA_MAX_HP);
            sTradeMenu->hpBarLevels[TRADE_PLAYER][i] = GetHPBarLevel(curHp, maxHp);
        }
        break;
    case TRADE_PARTNER:
        for (i = 0; i < sTradeMenu->partyCounts[TRADE_PARTNER]; i++)
        {
            curHp = GetMonData(&gEnemyParty[i], MON_DATA_HP);
            maxHp = GetMonData(&gEnemyParty[i], MON_DATA_MAX_HP);
            sTradeMenu->hpBarLevels[TRADE_PARTNER][i] = GetHPBarLevel(curHp, maxHp);
        }
        break;
    }
}

static void SetTradePartyHPBarSprites(void)
{
    int i, j;
    for (i = 0; i < 2; i++)
    {
        for (j = 0; j < sTradeMenu->partyCounts[i]; j++)
            SetPartyHPBarSprite(&gSprites[sTradeMenu->partySpriteIds[i][j]], 4 - sTradeMenu->hpBarLevels[i][j]);
    }
}

static void SaveTradeGiftRibbons(void)
{
    int i;
    for (i = 0; i < (int)ARRAY_COUNT(sTradeMenu->giftRibbons); i++)
    {
        if (gSaveBlock1Ptr->giftRibbons[i] == 0 && sTradeMenu->giftRibbons[i] != 0)
            gSaveBlock1Ptr->giftRibbons[i] = sTradeMenu->giftRibbons[i];
    }
}

static u32 CanTradeSelectedMon(struct Pokemon * playerParty, int partyCount, int monIdx)
{
    int i, numMonsLeft;
    struct LinkPlayer * partner;
    int species[PARTY_SIZE];
    int species2[PARTY_SIZE];

    for (i = 0; i < partyCount; i++)
    {
        species2[i] = GetMonData(&playerParty[i], MON_DATA_SPECIES_OR_EGG);
        species[i] = GetMonData(&playerParty[i], MON_DATA_SPECIES);
    }

    // Cant trade Eggs or non-Kanto mons if player doesn't have National Dex
    if (!IsNationalPokedexEnabled())
    {
        // See comment below
    #ifdef BUGFIX
        if (species2[monIdx] == SPECIES_EGG)
            return CANT_TRADE_EGG_YET;
    #endif

        if (species2[monIdx] > KANTO_SPECIES_END)
            return CANT_TRADE_NATIONAL;

        // This is meant to be SPECIES_EGG. There are obviously no circumstances
        // where you're allowed to trade SPECIES_NONE, so it wouldn't make sense to
        // only check this if the National Dex is missing. SPECIES_EGG will accidentally
        // be handled instead by the conditional above. Both of these problems are fixed in Emerald.
    #ifndef BUGFIX
        if (species2[monIdx] == SPECIES_NONE)
            return CANT_TRADE_EGG_YET;
    #endif
    }

    partner = &gLinkPlayers[GetMultiplayerId() ^ 1];
    if ((partner->version & 0xFF) != VERSION_RUBY &&
        (partner->version & 0xFF) != VERSION_SAPPHIRE)
    {
        // Does partner not have National Dex
        if (!(partner->progressFlagsCopy & 0xF))
        {
            if (species2[monIdx] == SPECIES_EGG)
                return CANT_TRADE_PARTNER_EGG_YET;

            if (species2[monIdx] > KANTO_SPECIES_END)
                return CANT_TRADE_INVALID_MON;
        }
    }

    if (species[monIdx] == SPECIES_DEOXYS || species[monIdx] == SPECIES_MEW)
    {
        if (!GetMonData(&playerParty[monIdx], MON_DATA_MODERN_FATEFUL_ENCOUNTER))
            return CANT_TRADE_INVALID_MON;
    }

    // Make Eggs not count for numMonsLeft
    for (i = 0; i < partyCount; i++)
    {
        if (species2[i] == SPECIES_EGG)
            species2[i] = SPECIES_NONE;
    }

    // Count alive mons in party, excluding selected trade mon
    for (numMonsLeft = 0, i = 0; i < partyCount; i++)
    {
        if (i != monIdx)
            numMonsLeft += species2[i];
    }

    if (numMonsLeft != 0)
        return CAN_TRADE_MON;
    else
        return CANT_TRADE_LAST_MON;
}

s32 GetGameProgressForLinkTrade(void)
{
    s32 versionId; // 0: FRLG, 1: RS, 2: Emerald (or anything else)
    u16 version;

    if (gReceivedRemoteLinkPlayers)
    {
        versionId = 0;
        version = (gLinkPlayers[GetMultiplayerId() ^ 1].version & 0xFF);

        if (version == VERSION_FIRE_RED || version == VERSION_LEAF_GREEN)
            versionId = 0;
        else if (version == VERSION_RUBY || version == VERSION_SAPPHIRE)
            versionId = 1;
        else
            versionId = 2;

        // If trading with RSE, both players must have progessed the story enough
        if (versionId > 0)
        {
            // Has player finished the Sevii Islands
            if (gLinkPlayers[GetMultiplayerId()].progressFlagsCopy & 0xF0)
            {
                if (versionId == 2)
                {
                    // Is RSE partner champion
                    if (gLinkPlayers[GetMultiplayerId() ^ 1].progressFlagsCopy & 0xF0)
                        return TRADE_BOTH_PLAYERS_READY;
                    else
                        return TRADE_PARTNER_NOT_READY;
                }
            }
            else
            {
                return TRADE_PLAYER_NOT_READY;
            }
        }
    }
    return TRADE_BOTH_PLAYERS_READY;
}

static bool32 IsDeoxysOrMewUntradable(u16 species, bool8 isModernFatefulEncounter)
{
    if (species == SPECIES_DEOXYS || species == SPECIES_MEW)
    {
        if (!isModernFatefulEncounter)
            return TRUE;
    }
    return FALSE;
}

int GetUnionRoomTradeMessageId(struct RfuGameCompatibilityData player, struct RfuGameCompatibilityData partner, u16 playerSpecies2, u16 partnerSpecies, u8 requestedType, u16 playerSpecies, bool8 isModernFatefulEncounter)
{
    bool8 playerHasNationalDex = player.hasNationalDex;
    bool8 playerCanLinkNationally = player.canLinkNationally;
    bool8 partnerHasNationalDex = partner.hasNationalDex;
    bool8 partnerCanLinkNationally = partner.canLinkNationally;
    u8 partnerVersion = partner.version;
    bool8 isNotFRLG;

    if (partnerVersion == VERSION_FIRE_RED || partnerVersion == VERSION_LEAF_GREEN)
        isNotFRLG = FALSE;
    else
        isNotFRLG = TRUE;

    // If partner is not using FRLG, both players must have progressed the story
    // to a certain point (becoming champion in RSE, finishing the Sevii islands in FRLG)
    if (isNotFRLG)
    {
        if (!playerCanLinkNationally)
            return UR_TRADE_MSG_CANT_TRADE_WITH_PARTNER_1;
        else if (!partnerCanLinkNationally)
            return UR_TRADE_MSG_CANT_TRADE_WITH_PARTNER_2;
    }

    // Cannot trade illegitimate Deoxys/Mew
    if (IsDeoxysOrMewUntradable(playerSpecies, isModernFatefulEncounter))
        return UR_TRADE_MSG_MON_CANT_BE_TRADED_2;

    if (partnerSpecies == SPECIES_EGG)
    {
        // If partner is trading an Egg then the player must also be trading an Egg
        if (playerSpecies2 != partnerSpecies)
            return UR_TRADE_MSG_NOT_EGG;
    }
    else
    {
        // Player's Pokémon must be of the type the partner requested
        if (gSpeciesInfo[playerSpecies2].types[0] != requestedType
         && gSpeciesInfo[playerSpecies2].types[1] != requestedType)
            return UR_TRADE_MSG_NOT_MON_PARTNER_WANTS;
    }

    // If the player is trading an Egg then the partner must also be trading an Egg
    // Odd that this wasn't checked earlier, as by this point we know either the partner doesn't have an Egg or that both do.
    if (playerSpecies2 == SPECIES_EGG && playerSpecies2 != partnerSpecies)
        return UR_TRADE_MSG_MON_CANT_BE_TRADED_1;

    // If the player doesn't have the National Dex then Eggs and non-Kanto Pokémon can't be traded
    if (!playerHasNationalDex)
    {
        if (playerSpecies2 == SPECIES_EGG)
            return UR_TRADE_MSG_EGG_CANT_BE_TRADED;

        if (playerSpecies2 > KANTO_SPECIES_END)
            return UR_TRADE_MSG_MON_CANT_BE_TRADED_2;

        if (partnerSpecies > KANTO_SPECIES_END)
            return UR_TRADE_MSG_PARTNERS_MON_CANT_BE_TRADED;
    }

    // If the partner doesn't have the National Dex then the player's offer has to be a Kanto Pokémon
    if (!partnerHasNationalDex && playerSpecies2 > KANTO_SPECIES_END)
        return UR_TRADE_MSG_PARTNER_CANT_ACCEPT_MON;

    // Trade is allowed
    return UR_TRADE_MSG_NONE;
}

int CanRegisterMonForTradingBoard(struct RfuGameCompatibilityData player, u16 species2, u16 species, bool8 isModernFatefulEncounter)
{
    bool8 hasNationalDex = player.hasNationalDex;

    if (IsDeoxysOrMewUntradable(species, isModernFatefulEncounter))
        return CANT_REGISTER_MON;

    if (hasNationalDex)
        return CAN_REGISTER_MON;

    // Eggs can only be traded if the player has the National Dex
    if (species2 == SPECIES_EGG)
        return CANT_REGISTER_EGG;

    if (species2 > KANTO_SPECIES_END && species2 != SPECIES_EGG)
        return CANT_REGISTER_MON;

    return CAN_REGISTER_MON;
}
