#include "global.h"
#include "text.h"
#include "battle.h"
#include "main.h"
#include "palette.h"
#include "constants/songs.h"
#include "sound.h"
#include "string_util.h"
#include "start_menu.h"

enum
{
    WIN_STATE_END,
    WIN_STATE_BEGIN,
    WIN_STATE_NORMAL,
    WIN_STATE_CHAR_DELAY,
    WIN_STATE_PAUSE,
    WIN_STATE_WAIT_BUTTON,
    WIN_STATE_NEWLINE,
    WIN_STATE_PLACEHOLDER,
    WIN_STATE_WAIT_CLEAR,
    WIN_STATE_WAIT_SCROLL,
    WIN_STATE_WAIT_SOUND,
};

enum
{
    TEXT_MODE_UNKNOWN0,
    TEXT_MODE_MONOSPACE,
    TEXT_MODE_UNKNOWN2,  // variable width?
};

enum
{
    WAIT_TYPE_NORMAL,      // allows the player to interrupt the text delay with A or B
    WAIT_TYPE_BATTLE,      // disables interrupting the text delay if in a link battle
    WAIT_TYPE_AUTOSCROLL,  // disables interrupting the text delay
    WAIT_TYPE_CONTEST,     // disables interrupting the text delay if in a link contest
};

struct Font
{
    u32 type;
    u8 *glyphs;
    u16 glyphSize;
    u16 lowerTileOffset;
};

struct GlyphBuffer
{
    // 0-7  left tile
    // 8-15 right tile (if it exists)
    u32 pixelRows[16];

    // pixel row filled with BG color
    u32 background;

    // table of color indices (not a palette)
    u32 colors[16];
};

struct GlyphTileInfo
{
    u8 textMode;
    u8 startPixel;
    u8 width;
    u8 *src;
    u32 *dest;
    u32 *colors;
};

struct ShiftAmount
{
    u32 left;
    u32 right;
};

static u16 InitVariableWidthFontTileData(struct Window *, u16);
static u16 LoadFixedWidthFont(struct Window *, u16);
static u16 LoadFixedWidthFont_Font1Latin(struct Window *, u16);
static u16 LoadFixedWidthFont_Font4Latin(struct Window *, u16);
static u16 LoadFixedWidthFont_Braille(struct Window *, u16);
static void MultistepLoadFont_LoadGlyph(struct Window *, u16, u8);
static u8 sub_8002FA0(struct Window *, const u8 *);
static u8 PrintNextChar(struct Window *);
static u8 HandleExtCtrlCode(struct Window *);
static u8 UpdateWindowText(struct Window *);
static u8 DrawGlyph_TextMode0(struct Window *, u32);
static void DrawGlyph_TextMode2(struct Window *, u32);
static void LoadFixedWidthGlyph(struct Window *, u32, u8 *);
static void WriteGlyphTilemap(struct Window *, u16, u16);
static void GetGlyphTilePointers(u8, u8, u16, u8 **, u8 **);
static u16 *GetCursorTilemapPointer(struct Window *);
static void ApplyColors_UnshadowedFont(const u8 *, u32 *, u8, u8);
static void ApplyColors_ShadowedFont(const void *, void *, u8, u8, u8);
static void SetCursorX(struct Window *, u8);
static void AddToCursorX(struct Window *, u8);
static void AddToCursorY(struct Window *, u8);
static void ClipLeft(struct Window *);
static void ClipRight(struct Window *);
static void SetWindowDefaultColors(struct Window *);
static void SetWindowBackgroundColor(struct Window *, u8);
static void SetWindowShadowColor(struct Window *, u8);
static void SetWindowForegroundColor(struct Window *, u8);
static u8 GetTextDelay(struct Window *);
static bool8 PlayerCanInterruptDelay(struct Window *);
static void ScrollWindowTextLines(struct Window *);
static void ScrollWindowTextLines_TextMode0(struct Window *);
static void DoScroll_TextMode0(struct Window *, u16);
static void ScrollWindowTextLines_TextModeMonospace(struct Window *);
static void DoScroll_TextModeMonospace(struct Window *, u16);
static void ScrollWindowTextLines_TextMode2(struct Window *);
static void DoScroll_TextMode2(struct Window *, u8);
void Text_ClearWindow(struct Window *);
static void ClearWindowTextLines_TextMode0_TextMode1(struct Window *, u8);
static void ClearWindowTextLines_TextMode2(struct Window *, u8);
static void TryEraseDownArrow(struct Window *);
static u16 GetBlankTileNum(struct Window *);
static u8 WaitWithDownArrow(struct Window *);
static void DrawInitialDownArrow(struct Window *);
static void UpdateDownArrowAnimation(struct Window *);
static u16 GetCursorTileNum(struct Window *, u32, u32);
static s32 DrawGlyphTiles(struct Window *, u32, u32);
static void UpdateTilemap(struct Window *, u32);
static u8 GetGlyphWidth(struct Window *, u32);
static s32 DrawGlyphTile_ShadowedFont(struct GlyphTileInfo *);

static void PrintGlyph_TextMode0(struct Window *, u32);
static void PrintGlyph_TextMode1(struct Window *, u32);
static void PrintGlyph_TextMode2(struct Window *, u32);

static void WriteGlyphTilemap_Font0_Font3(struct Window *, u32);
static void WriteGlyphTilemap_Font1_Font4(struct Window *, u32);
static void WriteGlyphTilemap_Font2_Font5(struct Window *, u32);
static void WriteGlyphTilemap_Font1_Font4(struct Window *, u32);
static void WriteGlyphTilemap_Font2_Font5(struct Window *, u32);
static void WriteGlyphTilemap_Font6(struct Window *, u32);

static u8 ExtCtrlCode_Nop(struct Window *);
static u8 ExtCtrlCode_ForegroundColor(struct Window *);
static u8 ExtCtrlCode_BackgroundColor(struct Window *);
static u8 ExtCtrlCode_ShadowColor(struct Window *);
static u8 ExtCtrlCode_AllColors(struct Window *);
static u8 ExtCtrlCode_Palette(struct Window *);
static u8 ExtCtrlCode_Font(struct Window *);
static u8 ExtCtrlCode_DefaultFont(struct Window *);
static u8 ExtCtrlCode_Pause(struct Window *);
static u8 ExtCtrlCode_WaitButton(struct Window *);
static u8 ExtCtrlCode_WaitSound(struct Window *);
static u8 ExtCtrlCode_PlayBGM(struct Window *);
static u8 ExtCtrlCode_Escape(struct Window *);
static u8 ExtCtrlCode_Nop2(struct Window *);
static u8 ExtCtrlCode_SetCursorY(struct Window *);
static u8 ExtCtrlCode_ClearWindowTextLines(struct Window *);
static u8 ExtCtrlCode_PlaySE(struct Window *);
static u8 ExtCtrlCode_Skip(struct Window *);
static u8 ExtCtrlCode_SetCursorX(struct Window *);
static u8 ExtCtrlCode_SkipTo(struct Window *);
static u8 ExtCtrlCode_Spacing(struct Window *);
static u8 ExtCtrlCode_Japanese(struct Window *);
static u8 ExtCtrlCode_Latin(struct Window *);

static void ShiftGlyphTile_UnshadowedFont_Width0(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width1(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width2(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width3(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width4(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width5(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width6(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width7(struct GlyphBuffer *, u8 *, u32 *, u8);
static void ShiftGlyphTile_UnshadowedFont_Width8(struct GlyphBuffer *, u8 *, u32 *, u8);

static void ShiftGlyphTile_ShadowedFont_Width0(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width1(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width2(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width3(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width4(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width5(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width6(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width7(struct GlyphBuffer *, u32 *, u32 *, u8);
static void ShiftGlyphTile_ShadowedFont_Width8(struct GlyphBuffer *, u32 *, u32 *, u8);

static struct Window *sMultistepLoadFont_Window;
static u16 sMultistepLoadFont_StartOffset;
static u16 sMultistepLoadFont_Index;
static struct Window sTempWindow;
static u8 sWaitType;
static u8 sLineLength;
static struct GlyphBuffer sGlyphBuffer;

EWRAM_DATA u16 gBGTilemapBuffers[4][0x400] = {0};

EWRAM_DATA u8 gStringVar1[0x100] = {0};
EWRAM_DATA u8 gStringVar2[0x100] = {0};
EWRAM_DATA u8 gStringVar3[0x100] = {0};
EWRAM_DATA u8 gStringVar4[0x100] = {0};

extern u16 gBattleTypeFlags;
extern u8 gIsLinkContest;
extern u8 gTileBuffer[];

vu16 *const gBGControlRegs[] =
{
    &REG_BG0CNT,
    &REG_BG1CNT,
    &REG_BG2CNT,
    &REG_BG3CNT,
};

vu16 *const gBGHOffsetRegs[] =
{
    &REG_BG0HOFS,
    &REG_BG1HOFS,
    &REG_BG2HOFS,
    &REG_BG3HOFS,
};

vu16 *const gBGVOffsetRegs[] =
{
    &REG_BG0VOFS,
    &REG_BG1VOFS,
    &REG_BG2VOFS,
    &REG_BG3VOFS,
};

const u16 gUnknown_081E29D8[] = { 0x100, 0x200, 0x400, 0x800 };
const u16 gUnknown_081E29E0[] = { 0x100, 0x200, 0x400, 0x800 };
const u16 gUnknown_081E29E8[] = { 1, 2, 4, 8 };

static const u8 sFont0LatinGlyphs[] = INCBIN_U8("graphics/fonts/font0_lat.1bpp");
static const u8 sFont1LatinGlyphs[] = INCBIN_U8("graphics/fonts/font1_lat.1bpp");
static const u8 sFont0JapaneseGlyphs[] = INCBIN_U8("graphics/fonts/font0_jpn.1bpp");
static const u8 sFont1JapaneseGlyphs[] = INCBIN_U8("graphics/fonts/font1_jpn.1bpp");
static const u8 sBrailleGlyphs[] = INCBIN_U8("graphics/fonts/font6_braille.1bpp");
static const u32 sDownArrowTiles[] = INCBIN_U32("graphics/fonts/down_arrow.4bpp");

// clang-format off
#include "data/text/type1_map.h"
#include "data/text/type3_map.h"
#include "data/text/font1_widths.h"
#include "data/text/font4_widths.h"
#include "data/text/font0_widths.h"
#include "data/text/font3_widths.h"
// clang-format on

const u16 gUnknownPalette_81E6692[] = INCBIN_U16("graphics/fonts/unknown_81E6692.gbapal");
const u16 gFontDefaultPalette[] = INCBIN_U16("graphics/fonts/default.gbapal");

const u8 sBlankTile[8] = { 0, 0, 0, 0, 0, 0, 0, 0, };

static const u32 sGlyphMasks[9][8][3] =
{
    {
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
        { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFFFFFFF0, },
        { 0x0000000F,0xFFFFFFFF,0xFFFFFF00, },
        { 0x000000FF,0xFFFFFFFF,0xFFFFF000, },
        { 0x00000FFF,0xFFFFFFFF,0xFFFF0000, },
        { 0x0000FFFF,0xFFFFFFFF,0xFFF00000, },
        { 0x000FFFFF,0xFFFFFFFF,0xFF000000, },
        { 0x00FFFFFF,0xFFFFFFFF,0xF0000000, },
        { 0x0FFFFFFF,0xFFFFFFFF,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFFFFFF00, },
        { 0x0000000F,0xFFFFFFFF,0xFFFFF000, },
        { 0x000000FF,0xFFFFFFFF,0xFFFF0000, },
        { 0x00000FFF,0xFFFFFFFF,0xFFF00000, },
        { 0x0000FFFF,0xFFFFFFFF,0xFF000000, },
        { 0x000FFFFF,0xFFFFFFFF,0xF0000000, },
        { 0x00FFFFFF,0xFFFFFFFF,0x00000000, },
        { 0x0FFFFFFF,0xFFFFFFF0,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFFFFF000, },
        { 0x0000000F,0xFFFFFFFF,0xFFFF0000, },
        { 0x000000FF,0xFFFFFFFF,0xFFF00000, },
        { 0x00000FFF,0xFFFFFFFF,0xFF000000, },
        { 0x0000FFFF,0xFFFFFFFF,0xF0000000, },
        { 0x000FFFFF,0xFFFFFFFF,0x00000000, },
        { 0x00FFFFFF,0xFFFFFFF0,0x00000000, },
        { 0x0FFFFFFF,0xFFFFFF00,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFFFF0000, },
        { 0x0000000F,0xFFFFFFFF,0xFFF00000, },
        { 0x000000FF,0xFFFFFFFF,0xFF000000, },
        { 0x00000FFF,0xFFFFFFFF,0xF0000000, },
        { 0x0000FFFF,0xFFFFFFFF,0x00000000, },
        { 0x000FFFFF,0xFFFFFFF0,0x00000000, },
        { 0x00FFFFFF,0xFFFFFF00,0x00000000, },
        { 0x0FFFFFFF,0xFFFFF000,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFFF00000, },
        { 0x0000000F,0xFFFFFFFF,0xFF000000, },
        { 0x000000FF,0xFFFFFFFF,0xF0000000, },
        { 0x00000FFF,0xFFFFFFFF,0x00000000, },
        { 0x0000FFFF,0xFFFFFFF0,0x00000000, },
        { 0x000FFFFF,0xFFFFFF00,0x00000000, },
        { 0x00FFFFFF,0xFFFFF000,0x00000000, },
        { 0x0FFFFFFF,0xFFFF0000,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xFF000000, },
        { 0x0000000F,0xFFFFFFFF,0xF0000000, },
        { 0x000000FF,0xFFFFFFFF,0x00000000, },
        { 0x00000FFF,0xFFFFFFF0,0x00000000, },
        { 0x0000FFFF,0xFFFFFF00,0x00000000, },
        { 0x000FFFFF,0xFFFFF000,0x00000000, },
        { 0x00FFFFFF,0xFFFF0000,0x00000000, },
        { 0x0FFFFFFF,0xFFF00000,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0xF0000000, },
        { 0x0000000F,0xFFFFFFFF,0x00000000, },
        { 0x000000FF,0xFFFFFFF0,0x00000000, },
        { 0x00000FFF,0xFFFFFF00,0x00000000, },
        { 0x0000FFFF,0xFFFFF000,0x00000000, },
        { 0x000FFFFF,0xFFFF0000,0x00000000, },
        { 0x00FFFFFF,0xFFF00000,0x00000000, },
        { 0x0FFFFFFF,0xFF000000,0x00000000, },
    },
    {
        { 0x00000000,0xFFFFFFFF,0x00000000, },
        { 0x0000000F,0xFFFFFFF0,0x00000000, },
        { 0x000000FF,0xFFFFFF00,0x00000000, },
        { 0x00000FFF,0xFFFFF000,0x00000000, },
        { 0x0000FFFF,0xFFFF0000,0x00000000, },
        { 0x000FFFFF,0xFFF00000,0x00000000, },
        { 0x00FFFFFF,0xFF000000,0x00000000, },
        { 0x0FFFFFFF,0xF0000000,0x00000000, },
    },
};

static const struct ShiftAmount sGlyphShiftAmounts[8] =
{
    {  0, 32 },
    {  4, 28 },
    {  8, 24 },
    { 12, 20 },
    { 16, 16 },
    { 20, 12 },
    { 24,  8 },
    { 28,  4 },
};

typedef void (*PrintGlyphFunc)(struct Window *, u32);

static const PrintGlyphFunc sPrintGlyphFuncs[] =
{
    PrintGlyph_TextMode0,
    PrintGlyph_TextMode1,
    PrintGlyph_TextMode2,
};

typedef void (*WriteGlyphTilemapFunc)(struct Window *, u32);

static const WriteGlyphTilemapFunc sWriteGlyphTilemapFuncs[] =
{
    WriteGlyphTilemap_Font0_Font3,
    WriteGlyphTilemap_Font1_Font4,
    WriteGlyphTilemap_Font2_Font5,
    WriteGlyphTilemap_Font0_Font3,
    WriteGlyphTilemap_Font1_Font4,
    WriteGlyphTilemap_Font2_Font5,
    WriteGlyphTilemap_Font6,
};

static const struct Window sDefaultWindow = { .language = GAME_LANGUAGE };

typedef u8 (*ExtCtrlCodeFunc)(struct Window *);

static const ExtCtrlCodeFunc sExtCtrlCodeFuncs[] =
{
    ExtCtrlCode_Nop,
    ExtCtrlCode_ForegroundColor,
    ExtCtrlCode_BackgroundColor,
    ExtCtrlCode_ShadowColor,
    ExtCtrlCode_AllColors,
    ExtCtrlCode_Palette,
    ExtCtrlCode_Font,
    ExtCtrlCode_DefaultFont,
    ExtCtrlCode_Pause,
    ExtCtrlCode_WaitButton,
    ExtCtrlCode_WaitSound,
    ExtCtrlCode_PlayBGM,
    ExtCtrlCode_Escape,
    ExtCtrlCode_Nop2,
    ExtCtrlCode_SetCursorY,
    ExtCtrlCode_ClearWindowTextLines,
    ExtCtrlCode_PlaySE,
    ExtCtrlCode_Skip,
    ExtCtrlCode_SetCursorX,
    ExtCtrlCode_SkipTo,
    ExtCtrlCode_Spacing,
    ExtCtrlCode_Japanese,
    ExtCtrlCode_Latin,
};

extern const u32 gFont3LatinGlyphs[];
extern const u32 gFont4LatinGlyphs[];
extern const u32 gFont3JapaneseGlyphs[];
extern const u32 gFont4JapaneseGlyphs[];

// Getting rid of the u8 recasting will make the ROM no longer match due to
// a const being required to pass the elements directly to the rodata structs.
static const struct Font sFonts[] =
{
    // Japanese fonts
    { 0, (u8 *)sFont0JapaneseGlyphs, 16,   8 },
    { 1, (u8 *)sFont1JapaneseGlyphs,  8,   0 },
    { 2, (u8 *)sFont1JapaneseGlyphs,  8,   0 },
    { 4, (u8 *)gFont3JapaneseGlyphs, 64, 512 },
    { 1, (u8 *)gFont4JapaneseGlyphs, 32,   0 },
    { 2, (u8 *)gFont4JapaneseGlyphs, 32,   0 },
    { 3, (u8 *)sBrailleGlyphs,  8,   0 },
    // Latin
    { 0, (u8 *)sFont0LatinGlyphs, 16,   8 },
    { 1, (u8 *)sFont1LatinGlyphs,  8,   0 },
    { 2, (u8 *)sFont1LatinGlyphs,  8,   0 },
    { 0, (u8 *)gFont3LatinGlyphs, 64,  32 },
    { 1, (u8 *)gFont4LatinGlyphs, 32,   0 },
    { 2, (u8 *)gFont4LatinGlyphs, 32,   0 },
    { 3, (u8 *)sBrailleGlyphs,  8,   0 },
};

static const u8 sTextSpeedDelays[] = { 6, 3, 1 }; // slow, mid, fast

static const u8 sExtCtrlCodeLengths[] =
{
    1,
    2,
    2,
    2,
    4,
    2,
    2,
    1,
    2,
    1,
    1,
    3,
    2,
    2,
    2,
    1,
    3,
    2,
    2,
    2,
    2,
    1,
    1,
};

typedef void (*ShiftGlyphTileUnshadowedFunc)(struct GlyphBuffer *, u8 *, u32 *, u8);

static const ShiftGlyphTileUnshadowedFunc sShiftGlyphTileUnshadowedFuncs[] =
{
    ShiftGlyphTile_UnshadowedFont_Width0,
    ShiftGlyphTile_UnshadowedFont_Width1,
    ShiftGlyphTile_UnshadowedFont_Width2,
    ShiftGlyphTile_UnshadowedFont_Width3,
    ShiftGlyphTile_UnshadowedFont_Width4,
    ShiftGlyphTile_UnshadowedFont_Width5,
    ShiftGlyphTile_UnshadowedFont_Width6,
    ShiftGlyphTile_UnshadowedFont_Width7,
    ShiftGlyphTile_UnshadowedFont_Width8,
};

typedef void (*ShiftGlyphTileShadowedFunc)(struct GlyphBuffer *, u32 *, u32 *, u8);

static const ShiftGlyphTileShadowedFunc sShiftGlyphTileShadowedFuncs[] =
{
    ShiftGlyphTile_ShadowedFont_Width0,
    ShiftGlyphTile_ShadowedFont_Width1,
    ShiftGlyphTile_ShadowedFont_Width2,
    ShiftGlyphTile_ShadowedFont_Width3,
    ShiftGlyphTile_ShadowedFont_Width4,
    ShiftGlyphTile_ShadowedFont_Width5,
    ShiftGlyphTile_ShadowedFont_Width6,
    ShiftGlyphTile_ShadowedFont_Width7,
    ShiftGlyphTile_ShadowedFont_Width8,
};

const struct WindowTemplate gWindowTemplate_81E6C3C =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    0, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6C58 =
{
    0, // BG number
    0, // BG character base block
    24, // BG screen base block
    0, // BG priority
    0, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    26, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(24), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6C74 =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    1, // foreground color
    0, // background color
    3, // shadow color
    4, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    OBJ_VRAM0, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6C90 =
{
    0, // BG number
    1, // BG character base block
    30, // BG screen base block
    2, // BG priority
    31, // palette number
    15, // foreground color
    0, // background color
    1, // shadow color
    4, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(1), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6CAC =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    15, // foreground color
    0, // background color
    1, // shadow color
    4, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    8, // width
    60, // height
    gTileBuffer, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6CC8 =
{
    2, // BG number
    2, // BG character base block
    15, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gMenuTextWindowTemplate =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D00 =
{
    0, // BG number
    0, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    2, // foreground color
    15, // background color
    3, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D1C =
{
    1, // BG number
    0, // BG character base block
    31, // BG screen base block
    1, // BG priority
    15, // palette number
    2, // foreground color
    15, // background color
    3, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D38 =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    15, // foreground color
    0, // background color
    14, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    8, // width
    64, // height
    gTileBuffer, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D54 =
{
    3, // BG number
    3, // BG character base block
    15, // BG screen base block
    3, // BG priority
    2, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(3), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D70 =
{
    3, // BG number
    3, // BG character base block
    15, // BG screen base block
    3, // BG priority
    3, // palette number
    1, // foreground color
    3, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(3), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6D8C =
{
    1, // BG number
    0, // BG character base block
    14, // BG screen base block
    1, // BG priority
    0, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(14), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6DA8 =
{
    0, // BG number
    0, // BG character base block
    12, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(11), // tilemap
};

const struct WindowTemplate gWindowTemplate_TrainerCard_Back_Values =
{
    0, // BG number
    2, // BG character base block
    30, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_TrainerCard_Back_Labels =
{
    0, // BG number
    2, // BG character base block
    30, // BG screen base block
    0, // BG priority
    14, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6DFC =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6E18 =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    1, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6E34 =
{
    1, // BG number
    0, // BG character base block
    31, // BG screen base block
    1, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6E50 =
{
    0, // BG number
    2, // BG character base block
    28, // BG screen base block
    0, // BG priority
    13, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6E6C =
{
    0, // BG number
    2, // BG character base block
    30, // BG screen base block
    0, // BG priority
    13, // palette number
    15, // foreground color
    0, // background color
    10, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6E88 =
{
    0, // BG number
    0, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6EA4 =
{
    1, // BG number
    0, // BG character base block
    28, // BG screen base block
    1, // BG priority
    1, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6EC0 =
{
    2, // BG number
    2, // BG character base block
    29, // BG screen base block
    2, // BG priority
    1, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(29), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6EDC =
{
    1, // BG number
    0, // BG character base block
    28, // BG screen base block
    1, // BG priority
    2, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6EF8 =
{
    2, // BG number
    2, // BG character base block
    29, // BG screen base block
    2, // BG priority
    2, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(29), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6F14 =
{
    1, // BG number
    0, // BG character base block
    28, // BG screen base block
    1, // BG priority
    3, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6F30 =
{
    2, // BG number
    2, // BG character base block
    29, // BG screen base block
    2, // BG priority
    3, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    8, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(29), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6F4C =
{
    3, // BG number
    0, // BG character base block
    30, // BG screen base block
    3, // BG priority
    0, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6F68 =
{
    0, // BG number
    2, // BG character base block
    13, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(13), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6F84 =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    15, // foreground color
    0, // background color
    1, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6FA0 =
{
    1, // BG number
    0, // BG character base block
    24, // BG screen base block
    3, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(24), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6FBC =
{
    0, // BG number
    0, // BG character base block
    30, // BG screen base block
    0, // BG priority
    15, // palette number
    15, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6FD8 =
{
    0, // BG number
    0, // BG character base block
    24, // BG screen base block
    0, // BG priority
    15, // palette number
    15, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(24), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E6FF4 =
{
    0, // BG number
    0, // BG character base block
    24, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(24), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7010 =
{
    0, // BG number
    0, // BG character base block
    30, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E702C =
{
    3, // BG number
    2, // BG character base block
    15, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7048 =
{
    2, // BG number
    2, // BG character base block
    14, // BG screen base block
    2, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    16, // tilemap left coordinate
    0, // tilemap top coordinate
    16, // width
    32, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(14), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7064 =
{
    2, // BG number
    2, // BG character base block
    14, // BG screen base block
    2, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(14), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7080 =
{
    3, // BG number
    0, // BG character base block
    30, // BG screen base block
    3, // BG priority
    11, // palette number
    1, // foreground color
    15, // background color
    5, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E709C =
{
    0, // BG number
    0, // BG character base block
    31, // BG screen base block
    1, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E70B8 =
{
    2, // BG number
    0, // BG character base block
    30, // BG screen base block
    2, // BG priority
    11, // palette number
    1, // foreground color
    0, // background color
    5, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E70D4 =
{
    3, // BG number
    0, // BG character base block
    30, // BG screen base block
    3, // BG priority
    11, // palette number
    1, // foreground color
    15, // background color
    5, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E70F0 =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    15, // foreground color
    1, // background color
    14, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    32, // width
    32, // height
    gTileBuffer, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E710C =
{
    0, // BG number
    0, // BG character base block
    31, // BG screen base block
    1, // BG priority
    15, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7128 =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7144 =
{
    0, // BG number
    2, // BG character base block
    31, // BG screen base block
    0, // BG priority
    13, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_ContestPainting =
{
    1, // BG number
    1, // BG character base block
    10, // BG screen base block
    1, // BG priority
    14, // palette number
    1, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(1), // tile data
    BG_SCREEN_ADDR(10), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E717C =
{
    0, // BG number
    3, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    15, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(3), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7198 =
{
    0, // BG number
    2, // BG character base block
    15, // BG screen base block
    0, // BG priority
    15, // palette number
    15, // foreground color
    0, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E71B4 =
{
    0, // BG number
    2, // BG character base block
    15, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(15), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E71D0 =
{
    1, // BG number
    1, // BG character base block
    28, // BG screen base block
    0, // BG priority
    5, // palette number
    13, // foreground color
    14, // background color
    15, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(1), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E71EC =
{
    2, // BG number
    1, // BG character base block
    30, // BG screen base block
    0, // BG priority
    5, // palette number
    13, // foreground color
    14, // background color
    15, // shadow color
    3, // font
    0, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(1), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7208 =
{
    0, // BG number
    2, // BG character base block
    28, // BG screen base block
    0, // BG priority
    8, // palette number
    1, // foreground color
    0, // background color
    2, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(28), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7224 =
{
    0, // BG number
    0, // BG character base block
    31, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(0), // tile data
    BG_SCREEN_ADDR(31), // tilemap
};

const struct WindowTemplate gMoveTutorMenuFramesWindowTemplate =
{
    1, // BG number
    2, // BG character base block
    30, // BG screen base block
    0, // BG priority
    15, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    BG_CHAR_ADDR(2), // tile data
    BG_SCREEN_ADDR(30), // tilemap
};

const struct WindowTemplate gWindowTemplate_81E725C =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    15, // foreground color
    0, // background color
    14, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    30, // width
    20, // height
    OBJ_VRAM0, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7278 =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    3, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    16, // width
    32, // height
    gTileBuffer, // tile data
    NULL, // tilemap
};

const struct WindowTemplate gWindowTemplate_81E7294 =
{
    0, // BG number
    0, // BG character base block
    0, // BG screen base block
    0, // BG priority
    0, // palette number
    1, // foreground color
    15, // background color
    8, // shadow color
    4, // font
    2, // text mode
    0, // spacing
    0, // tilemap left coordinate
    0, // tilemap top coordinate
    16, // width
    32, // height
    gTileBuffer, // tile data
    NULL, // tilemap
};

static void UpdateBGRegs(const struct WindowTemplate *winTemplate)
{
    u8 bgNum = winTemplate->bgNum;
    *gBGHOffsetRegs[bgNum] = 0;
    *gBGVOffsetRegs[bgNum] = 0;
    *gBGControlRegs[bgNum] = winTemplate->priority | (winTemplate->screenBaseBlock << 8) | (winTemplate->charBaseBlock << 2);
}

static void ClearBGMem(const struct WindowTemplate *winTemplate)
{
    CpuFastFill(0, winTemplate->tileData, 32);

    if (winTemplate->tilemap)
        CpuFastFill(0, winTemplate->tilemap, 0x800);
}

void LoadFontDefaultPalette(const struct WindowTemplate *winTemplate)
{
    LoadPalette(gFontDefaultPalette, 16 * winTemplate->paletteNum, 32);
}

void Text_LoadWindowTemplate(const struct WindowTemplate *winTemplate)
{
    UpdateBGRegs(winTemplate);
    ClearBGMem(winTemplate);
    LoadFontDefaultPalette(winTemplate);
}

u16 InitWindowTileData(struct Window *win, u16 startOffset)
{
    u16 retVal;

    win->tileDataStartOffset = startOffset;
    retVal = 0;

    switch (win->template->textMode)
    {
    case TEXT_MODE_UNKNOWN2:
        retVal = InitVariableWidthFontTileData(win, startOffset);
        break;
    case TEXT_MODE_MONOSPACE:
        switch (win->template->fontNum)
        {
        case 0:
        case 3:
            retVal = LoadFixedWidthFont(win, startOffset);
            break;
        case 1:
        case 2:
            retVal = LoadFixedWidthFont_Font1Latin(win, startOffset);
            break;
        case 4:
        case 5:
            retVal = LoadFixedWidthFont_Font4Latin(win, startOffset);
            break;
        case 6:
            retVal = LoadFixedWidthFont_Braille(win, startOffset);
            break;
        }
        break;
    }

    return retVal;
}

static u16 InitVariableWidthFontTileData(struct Window *win, u16 startOffset)
{
    u8 *buffer;

    win->tileDataStartOffset = startOffset;
    win->tileDataOffset = 2;
    buffer =  win->tileData + 32 * win->tileDataStartOffset;
    CpuFastFill(0, buffer, 32);
    ApplyColors_UnshadowedFont(sBlankTile, (u32 *)(buffer + 32), win->template->foregroundColor, win->template->backgroundColor);
    return win->tileDataStartOffset + win->tileDataOffset + win->width * win->height;
}

static u16 LoadFixedWidthFont(struct Window *win, u16 startOffset)
{
    s32 glyph;
    u8 *buffer = win->tileData + 32 * startOffset;
    for (glyph = 0; glyph < 256; glyph++)
    {
        LoadFixedWidthGlyph(win, glyph, buffer);
        buffer += 64;
    }
    return 2 * glyph;
}

static u16 LoadFixedWidthFont_Font1Latin(struct Window *win, u16 startOffset)
{
    s32 i;
    u8 *buffer = win->tileData + 32 * startOffset;
    for (i = 0; i < 256; i++)
    {
        ApplyColors_UnshadowedFont(&sFont1LatinGlyphs[8 * i], (u32 *)buffer, win->foregroundColor, win->backgroundColor);
        buffer += 32;
    }
    return i;
}

static u16 LoadFixedWidthFont_Font4Latin(struct Window *win, u16 startOffset)
{
    s32 i;
    u8 *buffer = win->tileData + 32 * startOffset;
    for (i = 0; i < 256; i++)
    {
        ApplyColors_ShadowedFont(&gFont4LatinGlyphs[8 * i], buffer, win->foregroundColor, win->shadowColor, win->backgroundColor);
        buffer += 32;
    }
    return i;
}

static u16 LoadFixedWidthFont_Braille(struct Window *win, u16 startOffset)
{
    s32 i;
    u8 *buffer = win->tileData + 32 * startOffset;
    for (i = 0; i < 256; i++)
    {
        ApplyColors_UnshadowedFont(&sBrailleGlyphs[8 * i], (u32 *)buffer, win->foregroundColor, win->backgroundColor);
        buffer += 32;
    }
    return i;
}

u32 MultistepInitWindowTileData(struct Window *win, u16 startOffset)
{
    u32 retVal;
    sMultistepLoadFont_Window = win;
    sMultistepLoadFont_Index = 0;
    sMultistepLoadFont_StartOffset = startOffset;
    win->tileDataStartOffset = startOffset;
    retVal = 0;

    switch (win->template->textMode)
    {
    case TEXT_MODE_UNKNOWN2:
        retVal = InitVariableWidthFontTileData(win, startOffset);
        break;
    case TEXT_MODE_MONOSPACE:
        retVal = 256;
        if (win->template->fontNum == 0
         || win->template->fontNum == 3)
            retVal *= 2;
        break;
    }

    return retVal;
}

bool32 MultistepLoadFont(void)
{
    bool32 retVal = TRUE;

    if (sMultistepLoadFont_Window->template->textMode == TEXT_MODE_MONOSPACE)
    {
        s32 i;

        for (i = sMultistepLoadFont_Index; i < sMultistepLoadFont_Index + 16; i++)
            MultistepLoadFont_LoadGlyph(sMultistepLoadFont_Window, sMultistepLoadFont_StartOffset, i);

        sMultistepLoadFont_Index += 16;

        if (sMultistepLoadFont_Index < 256)
            retVal = FALSE;
    }

    return retVal;
}

static void MultistepLoadFont_LoadGlyph(struct Window *win, u16 startOffset, u8 glyph)
{
    u8 *buffer;

    switch (win->template->fontNum)
    {
    case 0:
    case 3:
        buffer = win->tileData + 32 * startOffset + 64 * glyph;
        LoadFixedWidthGlyph(win, glyph, buffer);
        break;
    case 1:
    case 2:
        buffer = win->tileData + 32 * (glyph + startOffset);
        ApplyColors_UnshadowedFont(
            &sFont1LatinGlyphs[8 * glyph],
            (u32 *)buffer,
            win->foregroundColor,
            win->backgroundColor);
        break;
    case 4:
    case 5:
        buffer = win->tileData + 32 * (glyph + startOffset);
        ApplyColors_ShadowedFont(
            &gFont4LatinGlyphs[8 * glyph],
            buffer,
            win->foregroundColor,
            win->shadowColor,
            win->backgroundColor);
        break;
    }
}

void EmptyFunc(void)
{
}

void Text_InitWindowWithTemplate(struct Window *win, const struct WindowTemplate *winTemplate)
{
    *win = sDefaultWindow;
    win->template = (struct WindowTemplate *)winTemplate;
    win->textMode = winTemplate->textMode;
    win->spacing = winTemplate->spacing;
    win->fontNum = winTemplate->fontNum;
    win->paletteNum = winTemplate->paletteNum;
    win->tilemapLeft = winTemplate->tilemapLeft;
    win->tilemapTop = winTemplate->tilemapTop;
    win->width = winTemplate->width;
    win->height = winTemplate->height;
    win->tileData = winTemplate->tileData;
    win->tilemap = winTemplate->tilemap;
    SetWindowDefaultColors(win);
    SetWindowBackgroundColor(win, winTemplate->backgroundColor);
    SetWindowShadowColor(win, winTemplate->shadowColor);
    SetWindowForegroundColor(win, winTemplate->foregroundColor);
}

void Text_InitWindow(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
{
    const struct WindowTemplate *winTemplate = win->template;
    win->textMode = winTemplate->textMode;
    win->fontNum = winTemplate->fontNum;
    win->language = GAME_LANGUAGE;
    win->paletteNum = winTemplate->paletteNum;
    win->win_field_B = 0;
    win->win_field_C = 0;
    win->delayCounter = 0;
    win->spacing = winTemplate->spacing;
    win->win_field_F = 0;
    win->tilemapLeft = winTemplate->tilemapLeft;
    win->tilemapTop = winTemplate->tilemapTop;
    win->width = winTemplate->width;
    win->height = winTemplate->height;
    win->text = text;
    win->textIndex = 0;
    win->tileDataStartOffset = tileDataStartOffset;
    win->tileDataOffset = 0;
    win->left = 8 * left;
    win->cursorX = 0;
    win->top = 8 * top;
    win->cursorY = 0;
    win->state = WIN_STATE_BEGIN;
    win->downArrowCounter = 0;
    win->tileData = winTemplate->tileData;
    win->tilemap = winTemplate->tilemap;
    SetWindowDefaultColors(win);
    SetWindowBackgroundColor(win, winTemplate->backgroundColor);
    SetWindowShadowColor(win, winTemplate->shadowColor);
    SetWindowForegroundColor(win, winTemplate->foregroundColor);
}

void Text_InitWindow8002E4C(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u16 top, u32 a6)
{
    u8 val;

    Text_InitWindow(win, text, tileDataStartOffset, 0, 0);
    win->left = left;
    win->top = top;
    val = 0;
    if (a6)
        val = 255;
    win->win_field_F = val;
    if (win->win_field_F)
        ClipLeft(win);
}

void Text_SetWindowText(struct Window *win, const u8 *text)
{
    win->state = WIN_STATE_NORMAL;
    win->text = text;
    win->textIndex = 0;
    win->downArrowCounter = 0;
    win->win_field_B = -1;
    win->win_field_C = 0;
    win->delayCounter = 0;
}

void Contest_StartTextPrinter(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
{
    gMain.watchedKeysMask = A_BUTTON | B_BUTTON;
    gMain.watchedKeysPressed = 0;
    sWaitType = WAIT_TYPE_NORMAL;
    sLineLength = 26;
    Text_InitWindow(win, text, tileDataStartOffset, left, top);
    win->win_field_B = -1;
    if (win->textMode == TEXT_MODE_UNKNOWN0)
    {
        u16 val = GetCursorTileNum(win, 0, 0);
        u8 *buffer = win->tileData + 32 * val;
        ApplyColors_UnshadowedFont(sBlankTile, (u32 *)buffer, win->backgroundColor, win->backgroundColor);
        ApplyColors_UnshadowedFont(&sFont1LatinGlyphs[8 * 0x6E], (u32 *)(buffer + 32), win->foregroundColor, win->backgroundColor);
        win->tileDataOffset = 2;
    }
}

u8 Text_PrintWindow8002F44(struct Window *win)
{
    while (win->state != WIN_STATE_END)
    {
        if (win->state == WIN_STATE_NEWLINE)
        {
            AddToCursorY(win, 16);
            win->cursorX = 0;
            if (win->win_field_F)
                ClipLeft(win);
            win->state = WIN_STATE_NORMAL;
        }
        else if (win->state == WIN_STATE_PLACEHOLDER)
        {
            sub_8002FA0(win, GetExpandedPlaceholder(win->text[win->textIndex++]));
        }

        PrintNextChar(win);
    }
    return 1;
}

static u8 sub_8002FA0(struct Window *win, const u8 *text)
{
    u8 retVal;
    u8 savedLanguage = win->language;
    const u8 *savedText = win->text;

    u16 savedTextIndex = win->textIndex;
    win->text = text;
    win->textIndex = 0;
    win->state = WIN_STATE_NORMAL;
    retVal = Text_PrintWindow8002F44(win);
    win->text = savedText;
    win->textIndex = savedTextIndex;
    win->state = WIN_STATE_NORMAL;
    win->language = savedLanguage;
    return retVal;
}

static u8 PrintNextChar(struct Window *win)
{
    u32 c = win->text[win->textIndex++];

    // Handle special control characters
    switch (c)
    {
    case EOS:
        ClipRight(win);
        win->state = WIN_STATE_END;
        return 0;
    case PLACEHOLDER_BEGIN:
        win->state = WIN_STATE_PLACEHOLDER;
        return 2;
    case CHAR_NEWLINE:
        ClipRight(win);
        win->state = WIN_STATE_NEWLINE;
        return 2;
    case CHAR_PROMPT_CLEAR:
        DrawInitialDownArrow(win);
        win->state = WIN_STATE_WAIT_CLEAR;
        return 2;
    case CHAR_PROMPT_SCROLL:
        DrawInitialDownArrow(win);
        win->state = WIN_STATE_WAIT_SCROLL;
        return 2;
    case EXT_CTRL_CODE_BEGIN:
        return HandleExtCtrlCode(win);
    }

// TODO: see if this is in rev1+
#if (DEBUG && ENGLISH && REVISION == 0)
    // Code related to the Murakawa task.
    if ((gUnknown_Debug_03004BD0) && (!gUnknown_Debug_Murakawa2))
        c = win->textMode + CHAR_0;
#endif
    sPrintGlyphFuncs[win->textMode](win, c);
    return 1;
}

// Extended 0xFC control functions

static u8 HandleExtCtrlCode(struct Window *win)
{
    return sExtCtrlCodeFuncs[win->text[win->textIndex++]](win);
}

static u8 ExtCtrlCode_Nop(struct Window *win)
{
    return 2;
}

static u8 ExtCtrlCode_ForegroundColor(struct Window *win)
{
    SetWindowForegroundColor(win, win->text[win->textIndex++]);
    return 2;
}

static u8 ExtCtrlCode_BackgroundColor(struct Window *win)
{
    SetWindowBackgroundColor(win, win->text[win->textIndex++]);
    return 2;
}

static u8 ExtCtrlCode_ShadowColor(struct Window *win)
{
    SetWindowShadowColor(win, win->text[win->textIndex++]);
    return 2;
}

static u8 ExtCtrlCode_AllColors(struct Window *win)
{
    SetWindowForegroundColor(win, win->text[win->textIndex++]);
    SetWindowBackgroundColor(win, win->text[win->textIndex++]);
    SetWindowShadowColor(win, win->text[win->textIndex++]);
    return 2;
}

static u8 ExtCtrlCode_Palette(struct Window *win)
{
    win->paletteNum = win->text[win->textIndex++];
    return 2;
}

static u8 ExtCtrlCode_Font(struct Window *win)
{
    win->fontNum = win->text[win->textIndex++];
    return 2;
}

static u8 ExtCtrlCode_DefaultFont(struct Window *win)
{
    win->fontNum = win->template->fontNum;
    return 2;
}

static u8 ExtCtrlCode_Pause(struct Window *win)
{
    ClipRight(win);
    win->state = WIN_STATE_PAUSE;
    win->delayCounter = win->text[win->textIndex++];
    return 2;
}

static u8 ExtCtrlCode_WaitButton(struct Window *win)
{
    ClipRight(win);
    win->state = WIN_STATE_WAIT_BUTTON;
    return 2;
}

static u8 ExtCtrlCode_WaitSound(struct Window *win)
{
    ClipRight(win);
    win->state = WIN_STATE_WAIT_SOUND;
    return 2;
}

static u8 ExtCtrlCode_PlayBGM(struct Window *win)
{
    u16 loByte = win->text[win->textIndex++];
    u16 hiByte = win->text[win->textIndex++] << 8;
    PlayBGM(loByte | hiByte);
    return 2;
}

static u8 ExtCtrlCode_Escape(struct Window *win)
{
    sPrintGlyphFuncs[win->textMode](win, win->text[win->textIndex++]);
    return 1;
}

u8 ExtCtrlCode_Nop2(struct Window *win)
{
    return 1;
}

static u8 ExtCtrlCode_SetCursorY(struct Window *win)
{
    ClipRight(win);
    AddToCursorY(win, 8 * win->text[win->textIndex++]);
    return 1;
}

static u8 ExtCtrlCode_ClearWindowTextLines(struct Window *win)
{
    Text_ClearWindow(win);
    return 2;
}

static u8 ExtCtrlCode_PlaySE(struct Window *win)
{
    u16 loByte = win->text[win->textIndex++];
    u16 hiByte = win->text[win->textIndex++] << 8;
    PlaySE(loByte | hiByte);
    return 2;
}

static void DrawSpace(struct Window *win)
{
    if (win->textMode == TEXT_MODE_MONOSPACE || (win->left + win->cursorX) & 7 || win->spacing <= 7)
    {
        sPrintGlyphFuncs[win->textMode](win, 0);
    }
    else
    {
        u32 val = sGlyphBuffer.background;
        u16 index = GetCursorTileNum(win, 0, 0);
        u32 *buffer = (u32 *)(win->tileData + 32 * index);
        buffer[0] = val;
        buffer[1] = val;
        buffer[2] = val;
        buffer[3] = val;
        buffer[4] = val;
        buffer[5] = val;
        buffer[6] = val;
        buffer[7] = val;
        index = GetCursorTileNum(win, 0, 1);
        buffer = (u32 *)(win->tileData + 32 * index);
        buffer[0] = val;
        buffer[1] = val;
        buffer[2] = val;
        buffer[3] = val;
        buffer[4] = val;
        buffer[5] = val;
        buffer[6] = val;
        buffer[7] = val;
        UpdateTilemap(win, 1);
        AddToCursorX(win, 8);
    }
}

static void sub_8003344(struct Window *win, u8 newX)
{
    u8 savedSpacing = win->spacing;

    if (newX - win->cursorX <= 8)
        win->spacing = newX - win->cursorX;
    else
        win->spacing = 8 - ((win->left + win->cursorX) & 7);

    while (win->cursorX < newX)
    {
        s32 spacing;
        DrawSpace(win);
        spacing = newX - win->cursorX;
        if (spacing >= 8)
            spacing = 8;
        win->spacing = spacing;
    }

    win->spacing = savedSpacing;
}

static u8 ExtCtrlCode_Skip(struct Window *win)
{
    sub_8003344(win, win->cursorX + win->text[win->textIndex++]);
    return 1;
}

static u8 ExtCtrlCode_SetCursorX(struct Window *win)
{
    ClipRight(win);
    SetCursorX(win, win->text[win->textIndex++]);
    return 1;
}

static u8 ExtCtrlCode_SkipTo(struct Window *win)
{
    sub_8003344(win, win->text[win->textIndex++]);
    return 1;
}

static u8 ExtCtrlCode_Spacing(struct Window *win)
{
    win->spacing = win->text[win->textIndex++];
    return 2;
}

static u8 ExtCtrlCode_Japanese(struct Window *win)
{
    win->language = LANGUAGE_JAPANESE;
    return 2;
}

static u8 ExtCtrlCode_Latin(struct Window *win)
{
    win->language = GAME_LANGUAGE;
    return 2;
}

// Prints the window text without expanding any placeholders
u8 Text_PrintWindowSimple(struct Window *win)
{
    u8 retVal = 1;
    while (win->state != WIN_STATE_END)
    {
        if (win->state == WIN_STATE_NEWLINE)
        {
            AddToCursorY(win, 16);
            win->cursorX = 0;
            if (win->win_field_F)
                ClipLeft(win);
            win->state = WIN_STATE_NORMAL;
        }
        if (PrintNextChar(win) == 1)
        {
            retVal = 0;
            break;
        }
    }
    return retVal;
}

u8 Text_InitWindowAndPrintText(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
{
    Text_InitWindow(win, text, tileDataStartOffset, left, top);
    return Text_PrintWindow8002F44(win);
}

u8 sub_8003490(struct Window *win, u8 c, u16 tileDataStartOffset, u8 left, u8 top)
{
    u8 retVal;
    u8 text[2];
    text[0] = c;
    text[1] = EOS;
    Text_InitWindow(win, text, tileDataStartOffset, left, top);
    retVal = PrintNextChar(win);
    ClipRight(win);
    return retVal;
}

void RenderTextHandleBold(u8 *tileData, const u8 *text)
{
    Text_InitWindow8004E3C(&gWindowTemplate_81E6C74, tileData, text);
}

u8 sub_80034EC(u8 *str)
{
    return Text_GetStringWidthFromWindowTemplate((struct WindowTemplate *)&gWindowTemplate_81E6C74, str);
}

u8 *sub_8003504(u8 *dest, s32 value, u8 alignAmount, u8 alignType)
{
    sTempWindow.template = (struct WindowTemplate *)&gWindowTemplate_81E6C74;
    Text_InitWindow(&sTempWindow, 0, 0, 0, 0);
    return AlignInt2(&sTempWindow, dest, value, alignAmount, alignType);
}

u8 *sub_8003558(u8 *dest, const u8 *src, u8 alignAmount, u8 alignType)
{
    sTempWindow.template = (struct WindowTemplate *)&gWindowTemplate_81E6C74;
    Text_InitWindow(&sTempWindow, src, 0, 0, 0);
    return AlignString(&sTempWindow, dest, src, alignAmount, alignType);
}

// Updates the window text
// The text delay can be accelerated by holding the A button
u8 Text_UpdateWindow(struct Window *win)
{
    sWaitType = WAIT_TYPE_NORMAL;
    return UpdateWindowText(win);
}

// Returns TRUE when all of the text has been printed.
static u8 UpdateWindowText(struct Window *win)
{
    switch (win->state)
    {
    case WIN_STATE_WAIT_BUTTON:
        if (PlayerCanInterruptDelay(win))
        {
            if (gMain.newKeys & (A_BUTTON | B_BUTTON))
            {
                PlaySE(SE_SELECT);
            }
            else
            {
                return FALSE;
            }
        }
        else
        {
            win->delayCounter--;
            if (win->delayCounter)
                return FALSE;
        }
        win->state = WIN_STATE_NORMAL;
        return FALSE;
    case WIN_STATE_CHAR_DELAY:
        // Allow the player to speed up text by holding a button
        if (PlayerCanInterruptDelay(win)
         && (gMain.heldKeys & (A_BUTTON | B_BUTTON))
         && gMain.watchedKeysPressed == TRUE)
        {
            win->delayCounter = 0;
            win->state = WIN_STATE_NORMAL;
            break;
        }
        // fall through
    case WIN_STATE_PAUSE:
        // Wait for timer to expire, then continue printing
        if (win->delayCounter)
        {
            win->delayCounter--;
            if (win->delayCounter)
                return FALSE;
        }
        win->state = WIN_STATE_NORMAL;
        break;
    case WIN_STATE_WAIT_CLEAR:
        // Erase the text once a button is pressed
        if (WaitWithDownArrow(win))
        {
            Text_ClearWindow(win);
            win->state = WIN_STATE_NORMAL;
            asm("");
        }
        return FALSE;
    case WIN_STATE_WAIT_SCROLL:
        // Scroll the text once a button is pressed
        if (WaitWithDownArrow(win))
        {
            ScrollWindowTextLines(win);
            win->state = WIN_STATE_NORMAL;
            asm("");
        }
        return FALSE;
    case WIN_STATE_PLACEHOLDER:
        win->textIndex++;
        win->state = WIN_STATE_NORMAL;
        break;
    case WIN_STATE_NEWLINE:
        ScrollWindowTextLines(win);
        win->state = WIN_STATE_NORMAL;
        asm("");
        return FALSE;
    case WIN_STATE_BEGIN:
        Text_ClearWindow(win);
        break;
    case WIN_STATE_WAIT_SOUND:
        if (IsSEPlaying())
            return FALSE;
        win->state = WIN_STATE_NORMAL;
        break;
    case WIN_STATE_END:
        return TRUE;  // done printing text
    case WIN_STATE_NORMAL:
        break;
    default:
        win->state = WIN_STATE_END;
        return TRUE;
    }

    PrintNextChar(win);

    switch (win->state)
    {
    case WIN_STATE_END:
        return TRUE;  // done printing text
    case WIN_STATE_WAIT_BUTTON:
    case WIN_STATE_WAIT_CLEAR:
    case WIN_STATE_WAIT_SCROLL:
        if (PlayerCanInterruptDelay(win))
            return 0;
        win->delayCounter = 60;
        break;
    case WIN_STATE_PAUSE:
    case WIN_STATE_NEWLINE:
    case WIN_STATE_WAIT_SOUND:
        break;
    default:
        win->state = WIN_STATE_CHAR_DELAY;
        win->delayCounter = GetTextDelay(win);
        break;
    }

    return 0;
}

#if defined(ENGLISH)
#define SUB_800374C_LINE_LENGTH 26
#elif defined(GERMAN)
#define SUB_800374C_LINE_LENGTH 27
#endif

u8 Text_UpdateWindowInBattle(struct Window *win)
{
    u8 retVal;

    sWaitType = WAIT_TYPE_BATTLE;
    sLineLength = SUB_800374C_LINE_LENGTH;
    retVal = UpdateWindowText(win);
    sLineLength = 26;
    sWaitType = WAIT_TYPE_NORMAL;
    return retVal;
}

u8 Text_UpdateWindowAutoscroll(struct Window *win)
{
    u8 retVal;

    sWaitType = WAIT_TYPE_AUTOSCROLL;
    sLineLength = 26;
    retVal = UpdateWindowText(win);
    sWaitType = WAIT_TYPE_NORMAL;
    return retVal;
}

u8 Contest_RunTextPrinter(struct Window *win)
{
    u8 retVal;

    sWaitType = WAIT_TYPE_CONTEST;
    sLineLength = 17;
    retVal = UpdateWindowText(win);
    sLineLength = 26;
    return retVal;
}

u32 Text_UpdateWindowOverrideLineLength(struct Window *win, u8 lineLength)
{
    u8 retVal;

    sWaitType = WAIT_TYPE_NORMAL;
    sLineLength = lineLength;
    retVal = UpdateWindowText(win);
    sLineLength = 26;
    return retVal;
}

static void PrintGlyph_TextMode0(struct Window *win, u32 glyph)
{
    AddToCursorX(win, DrawGlyph_TextMode0(win, glyph));
    if (win->win_field_B)
        ClipRight(win);
}

static u8 DrawGlyph_TextMode0(struct Window *win, u32 glyph)
{
    u8 pixelsWidth = GetGlyphWidth(win, glyph);
    u32 tilesWidth = DrawGlyphTiles(win, glyph, pixelsWidth);
    UpdateTilemap(win, tilesWidth);
    return pixelsWidth;
}

static void PrintGlyph_TextMode1(struct Window *win, u32 glyph)
{
    sWriteGlyphTilemapFuncs[win->fontNum](win, glyph);
    win->cursorX += 8;
}

static void WriteGlyphTilemap_Font0_Font3(struct Window *win, u32 glyph)
{
    s16 val = win->tileDataStartOffset + 2 * glyph;
    WriteGlyphTilemap(win, val, val + 1);
}

static void WriteGlyphTilemap_Font1_Font4(struct Window *win, u32 glyph)
{
    u32 val = glyph * 2;
    WriteGlyphTilemap(
        win,
        win->tileDataStartOffset + sFontType1Map[val],
        win->tileDataStartOffset + sFontType1Map[val + 1]);
}

static void WriteGlyphTilemap_Font2_Font5(struct Window *win, u32 glyph)
{
    WriteGlyphTilemap(
        win,
        win->tileDataStartOffset + 212,
        win->tileDataStartOffset + glyph);
}

static void WriteGlyphTilemap_Font6(struct Window *win, u32 glyph)
{
    u32 val = glyph * 2;
    WriteGlyphTilemap(
        win,
        win->tileDataStartOffset + sFontType3Map[val],
        win->tileDataStartOffset + sFontType3Map[val + 1]);
}

static void PrintGlyph_TextMode2(struct Window *win, u32 glyph)
{
    u8 width = GetGlyphWidth(win, glyph);
    DrawGlyph_TextMode2(win, glyph);
    AddToCursorX(win, width);
    if (win->win_field_B)
        ClipRight(win);
}

static void DrawGlyph_TextMode2(struct Window *win, u32 glyph)
{
    u8 pixelsWidth = GetGlyphWidth(win, glyph);
    u32 tilesWidth = DrawGlyphTiles(win, glyph, pixelsWidth);
    UpdateTilemap(win, tilesWidth);
}

static void LoadFixedWidthGlyph(struct Window *win, u32 glyph, u8 *dest)
{
    u8 *upperTile;
    u8 *lowerTile;

    GetGlyphTilePointers(win->fontNum, win->language, glyph, &upperTile, &lowerTile);

    switch (win->fontNum)
    {
    case 0:
    case 1:
    case 2:
    case 6:
        ApplyColors_UnshadowedFont(upperTile, (u32 *)dest, win->foregroundColor, win->backgroundColor);
        ApplyColors_UnshadowedFont(lowerTile, (u32 *)(dest + 32), win->foregroundColor, win->backgroundColor);
        break;
    case 3:
    case 4:
    case 5:
        ApplyColors_ShadowedFont(upperTile, dest, win->foregroundColor, win->shadowColor, win->backgroundColor);
        ApplyColors_ShadowedFont(lowerTile, dest + 32, win->foregroundColor, win->shadowColor, win->backgroundColor);
        break;
    }
}

static void WriteGlyphTilemap(struct Window *win, u16 upperTileNum, u16 lowerTileNum)
{
    u16 *buffer = GetCursorTilemapPointer(win);
    if (buffer)
    {
        u16 palette = (win->paletteNum << 12);
        buffer[0] = upperTileNum | palette;
        buffer[32] = lowerTileNum | palette;
    }
}

static void GetGlyphTilePointers(u8 fontNum, u8 language, u16 glyph, u8 **upperTilePtr, u8 **lowerTilePtr)
{
    u16 index;
    const struct Font *font;

    if (language == LANGUAGE_JAPANESE)
        language = 0;
    else
        language = 7;

    font = &sFonts[language + fontNum];

    switch (font->type)
    {
    case 0:
        *upperTilePtr = font->glyphs + glyph * font->glyphSize;
        *lowerTilePtr = *upperTilePtr + font->lowerTileOffset;
        break;
    case 1:
        index = 2 * glyph;
        *upperTilePtr = font->glyphs + sFontType1Map[index] * font->glyphSize;
        *lowerTilePtr = font->glyphs + sFontType1Map[index + 1] * font->glyphSize;
        break;
    case 2:
        *upperTilePtr = font->glyphs + 212 * font->glyphSize;
        *lowerTilePtr = font->glyphs + glyph * font->glyphSize;
        break;
    case 3:
        index = 2 * glyph;
        *upperTilePtr = font->glyphs + sFontType3Map[index] * font->glyphSize;
        *lowerTilePtr = font->glyphs + sFontType3Map[index + 1] * font->glyphSize;
        break;
    case 4:
        *upperTilePtr = font->glyphs
            + (glyph & 0xFFF0) * font->glyphSize
            + (((glyph &= 0xF) * font->glyphSize) >> 1);
        *lowerTilePtr = *upperTilePtr + font->lowerTileOffset;
        break;
    }
}

static u16 *GetCursorTilemapPointer(struct Window *win)
{
    u16 *ptr = NULL;
    if (win->tilemap)
    {
        u8 x = ((win->left + win->cursorX) >> 3) + win->tilemapLeft;
        u8 y = ((win->top + win->cursorY) >> 3) + win->tilemapTop;
        ptr = &win->tilemap[(y * 32) + x];
    }
    return ptr;
}

static void ApplyColors_UnshadowedFont(const u8 *src, u32 *dest, u8 foreground, u8 background)
{
    u32 a[2];
    s32 i;
    const u8 *srcRows = src;

    a[0] = background;
    a[1] = foreground;

    for (i = 0; i < 8; i++)
    {
        u32 destRow = a[srcRows[i] & 1]
                    | (a[(srcRows[i] >> 1) & 1] << 4)
                    | (a[(srcRows[i] >> 2) & 1] << 8)
                    | (a[(srcRows[i] >> 3) & 1] << 12)
                    | (a[(srcRows[i] >> 4) & 1] << 16)
                    | (a[(srcRows[i] >> 5) & 1] << 20)
                    | (a[(srcRows[i] >> 6) & 1] << 24)
                    | (a[(srcRows[i] >> 7)    ] << 28);
        dest[i] = destRow;
    }
}

static void ApplyColors_ShadowedFont(const void *src, void *dest, u8 foreground, u8 shadow, u8 background)
{
    u32 a[0x10];
    s32 i;
    const u32 *curSrc;
    u32 *curDest;
    u32 colorMask;

    a[0x1] = 0x1;
    a[0x2] = 0x2;
    a[0x3] = 0x3;
    a[0x4] = 0x4;
    a[0x5] = 0x5;
    a[0x6] = 0x6;
    a[0x7] = 0x7;
    a[0x8] = 0x8;
    a[0x9] = 0x9;
    a[0xA] = 0xA;
    a[0xB] = 0xB;
    a[0xC] = 0xC;
    a[0xD] = 0xD;
    a[0x0] = background;
    a[0xE] = shadow;
    a[0xF] = foreground;

    colorMask = 0xF;

    curSrc = src;
    curDest = dest;

    for (i = 7; i >= 0; i--)
    {
        u32 row = *curSrc++;
        u32 recoloredRow = a[row & colorMask]
                         | (a[(row >> 4) & colorMask] << 4)
                         | (a[(row >> 8) & colorMask] << 8)
                         | (a[(row >> 12) & colorMask] << 12)
                         | (a[(row >> 16) & colorMask] << 16)
                         | (a[(row >> 20) & colorMask] << 20)
                         | (a[(row >> 24) & colorMask] << 24)
                         | (a[(row >> 28)            ] << 28);
        *curDest++ = recoloredRow;
    }
}

static void SetCursorX(struct Window *win, u8 x)
{
    if (win->textMode == TEXT_MODE_UNKNOWN0 && ((win->left + win->cursorX) & 7))
        win->tileDataOffset += 2;
    win->cursorX = x;
}

static void AddToCursorX(struct Window *win, u8 deltaX)
{
    if (win->textMode == TEXT_MODE_UNKNOWN0)
    {
        u8 x = win->cursorX;
        win->cursorX += deltaX;
        if (((win->left + win->cursorX) & 0xF8) != ((win->left + x) & 0xF8))
            win->tileDataOffset += 2;
    }
    else
    {
        win->cursorX += deltaX;
    }
}

static void AddToCursorY(struct Window *win, u8 deltaY)
{
    if (win->textMode == TEXT_MODE_UNKNOWN0 && ((win->left + win->cursorX) & 7))
        win->tileDataOffset += 2;
    win->cursorY += deltaY;
}

static void EraseAtCursor(struct Window *win)
{
    switch (win->textMode)
    {
    case TEXT_MODE_UNKNOWN0:
    case TEXT_MODE_UNKNOWN2:
        DrawGlyphTiles(win, 0, 8);
        break;
    case TEXT_MODE_MONOSPACE:
        sWriteGlyphTilemapFuncs[win->fontNum](win, 0);
        break;
    }
}

static void ClipLeft(struct Window *win)
{
    u32 pixel = win->left & 7;

    if (win->textMode != TEXT_MODE_MONOSPACE && pixel)
    {
        const u32 *masks = sGlyphMasks[8][pixel];
        u32 outsideMask = masks[0];
        u32 insideMask = ~outsideMask;
        u32 outside = sGlyphBuffer.background & outsideMask;
        u16 tileNum = GetCursorTileNum(win, 0, 0);
        u32 *buffer = (u32 *)(win->tileData + 32 * tileNum);
        buffer[0] = (buffer[0] & insideMask) | outside;
        buffer[1] = (buffer[1] & insideMask) | outside;
        buffer[2] = (buffer[2] & insideMask) | outside;
        buffer[3] = (buffer[3] & insideMask) | outside;
        buffer[4] = (buffer[4] & insideMask) | outside;
        buffer[5] = (buffer[5] & insideMask) | outside;
        buffer[6] = (buffer[6] & insideMask) | outside;
        buffer[7] = (buffer[7] & insideMask) | outside;
        tileNum = GetCursorTileNum(win, 0, 1);
        buffer = (u32 *)(win->tileData + 32 * tileNum);
        buffer[0] = (buffer[0] & insideMask) | outside;
        buffer[1] = (buffer[1] & insideMask) | outside;
        buffer[2] = (buffer[2] & insideMask) | outside;
        buffer[3] = (buffer[3] & insideMask) | outside;
        buffer[4] = (buffer[4] & insideMask) | outside;
        buffer[5] = (buffer[5] & insideMask) | outside;
        buffer[6] = (buffer[6] & insideMask) | outside;
        buffer[7] = (buffer[7] & insideMask) | outside;
    }
}

static void ClipRight(struct Window *win)
{
    register u8 cursorX asm("r0") = win->cursorX;
    u8 left = win->left;
    u32 pixelX = (cursorX + left) & 7;

    if (win->textMode != TEXT_MODE_MONOSPACE && pixelX != 0)
    {
        const u32 *masks = sGlyphMasks[8 - pixelX][pixelX];
        u32 insideMask = masks[0];
        u32 outside = (sGlyphBuffer.background & ~insideMask);
        u16 tileNum; 
        u32 *tileData;

        tileNum = GetCursorTileNum(win, 0, 0);
        tileData = (u32 *)(win->tileData + 32 * tileNum);

        tileData[0] = (tileData[0] & insideMask) | outside;
        tileData[1] = (tileData[1] & insideMask) | outside;
        tileData[2] = (tileData[2] & insideMask) | outside;
        tileData[3] = (tileData[3] & insideMask) | outside;
        tileData[4] = (tileData[4] & insideMask) | outside;
        tileData[5] = (tileData[5] & insideMask) | outside;
        tileData[6] = (tileData[6] & insideMask) | outside;
        tileData[7] = (tileData[7] & insideMask) | outside;

        tileNum = GetCursorTileNum(win, 0, 1);
        tileData = (u32 *)(win->tileData + 32 * tileNum);

        tileData[0] = (tileData[0] & insideMask) | outside;
        tileData[1] = (tileData[1] & insideMask) | outside;
        tileData[2] = (tileData[2] & insideMask) | outside;
        tileData[3] = (tileData[3] & insideMask) | outside;
        tileData[4] = (tileData[4] & insideMask) | outside;
        tileData[5] = (tileData[5] & insideMask) | outside;
        tileData[6] = (tileData[6] & insideMask) | outside;
        tileData[7] = (tileData[7] & insideMask) | outside;

        UpdateTilemap(win, 1);
    }
}

static void SetWindowDefaultColors(struct Window *win)
{
    u32 i;

    win->backgroundColor = 0;
    win->shadowColor = 14;
    win->foregroundColor = 15;

    for (i = 0; i < 16; i++)
        sGlyphBuffer.colors[i] = i;
}

static void SetWindowBackgroundColor(struct Window *win, u8 color)
{
    u32 val1;
    u32 val2;
    u32 val3;
    win->backgroundColor = color;
    sGlyphBuffer.colors[0] = color;
    val1 = color | (color << 4);
    val2 = val1 | (val1 << 8);
    val3 = val2 | (val2 << 16);
    sGlyphBuffer.background = val3;
}

static void SetWindowShadowColor(struct Window *win, u8 color)
{
    win->shadowColor = color;
    sGlyphBuffer.colors[14] = color;
}

static void SetWindowForegroundColor(struct Window *win, u8 color)
{
    win->foregroundColor = color;
    sGlyphBuffer.colors[15] = color;
}

static u8 GetTextDelay(struct Window *win)
{
    if (!PlayerCanInterruptDelay(win))
        return 3;

    return sTextSpeedDelays[gSaveBlock2.optionsTextSpeed];
}

static bool8 PlayerCanInterruptDelay(struct Window *win)
{
    bool8 retVal = TRUE;

    switch (sWaitType)
    {
    case WAIT_TYPE_AUTOSCROLL:
        retVal = FALSE;
        break;
    case WAIT_TYPE_CONTEST:
        retVal = gIsLinkContest ? FALSE : TRUE;
        break;
    case WAIT_TYPE_BATTLE:
        retVal = (gBattleTypeFlags & BATTLE_TYPE_LINK) ? FALSE : TRUE;
        break;
    }

    return retVal;
}

static void ScrollWindowTextLines(struct Window *win)
{
    switch (win->textMode)
    {
    case TEXT_MODE_UNKNOWN0:
        ScrollWindowTextLines_TextMode0(win);
        break;
    case TEXT_MODE_MONOSPACE:
        ScrollWindowTextLines_TextModeMonospace(win);
        break;
    case TEXT_MODE_UNKNOWN2:
        ScrollWindowTextLines_TextMode2(win);
        break;
    }
}

static void ScrollWindowTextLines_TextMode0(struct Window *win)
{
    if (win->cursorY == 0)
    {
        // Advance to the next line
        win->tileDataOffset = 2 * sLineLength + 2;
        win->cursorX = 0;
        win->cursorY += 16;
    }
    else
    {
        if (win->win_field_C & 2)
            win->tileDataOffset = 2 * sLineLength + 2;
        else
            win->tileDataOffset = 2;
        win->win_field_C ^= 2;
        win->cursorX = 0;
        DoScroll_TextMode0(win, sLineLength);
    }
}

static void DoScroll_TextMode0(struct Window *win, u16 lineLength)
{
    u16 *buffer = win->tilemap;
    u32 val1 = 32 * (win->top >> 3);
    u32 val2 = (win->left >> 3);
    u16 fill;
    buffer += val1 + val2;
    fill = (win->paletteNum << 12) | GetBlankTileNum(win);
    
    // Move the bottom line up
    CpuCopy16(buffer + 64, buffer, lineLength * 2);
    CpuCopy16(buffer + 96, buffer + 32, lineLength * 2);
    
    // Clear the bottom line
    CpuFill16(fill, buffer + 64, lineLength * 2);
    CpuFill16(fill, buffer + 96, lineLength * 2);
}

static void ScrollWindowTextLines_TextModeMonospace(struct Window *win)
{
    if (win->cursorY == 0)
    {
        // Advance to the next line
        win->cursorX = 0;
        win->cursorY += 16;
    }
    else
    {
        win->win_field_C ^= 2;
        win->cursorX = 0;
        DoScroll_TextModeMonospace(win, sLineLength);
    }
}

static void DoScroll_TextModeMonospace(struct Window *win, u16 lineLength)
{
    u16 *buffer = GetCursorTilemapPointer(win);
    u16 *dest = buffer - 32;
    u16 fill = (win->paletteNum << 12) | GetBlankTileNum(win);
    
    // Move the bottom line up
    CpuCopy16(buffer + 32, dest, lineLength * 2);
    CpuCopy16(buffer + 64, buffer, lineLength * 2);
    
    // Clear the bottom line
    CpuFill16(fill, buffer + 32, lineLength * 2);
    CpuFill16(fill, buffer + 64, lineLength * 2);
}

static void ScrollWindowTextLines_TextMode2(struct Window *win)
{
    if (win->cursorY == 0)
    {
        // Advance to the next line
        win->cursorX = 0;
        win->cursorY += 16;
    }
    else
    {
        win->win_field_C ^= 2;
        win->cursorX = 0;
        DoScroll_TextMode2(win, sLineLength);
    }
}

static void DoScroll_TextMode2(struct Window *win, u8 lineLength)
{
    u8 i;
    u8 *buf1 = win->tileData + 32 * GetCursorTileNum(win, 0, -2);
    u8 *buf2 = win->tileData + 32 * GetCursorTileNum(win, 0, 0);
    u8 *buf4;
    u16 *buf3;
    u16 a[4];

    CpuFastCopy(buf2, buf1, 32 * lineLength);
    CpuFastFill(sGlyphBuffer.background, buf2, 32 * lineLength);
    buf4 = buf2 + 32 * win->width;
    CpuFastCopy(buf4, buf1 + 32 * win->width, 32 * lineLength);
    CpuFastFill(sGlyphBuffer.background, buf4, 32 * lineLength);

    buf3 = GetCursorTilemapPointer(win) - 64;

    a[0] = (win->tileDataStartOffset + win->tileDataOffset
          + ((win->top >> 3) * win->width)
          + (win->left >> 3))
         | (win->paletteNum << 12);
    a[1] = a[0] + win->width;
    a[2] = a[1] + win->width;
    a[3] = a[2] + win->width;

    for (i = 0; i < lineLength; i++)
    {
        buf3[0] = a[0]++;
        buf3[32] = a[1]++;
        buf3[64] = a[2]++;
        buf3[96] = a[3]++;
        buf3++;
    }
}

void Text_ClearWindow(struct Window *win)
{
    switch (win->textMode)
    {
    case TEXT_MODE_UNKNOWN0:
        ClearWindowTextLines_TextMode0_TextMode1(win, sLineLength);
        win->tileDataOffset = 2;
        break;
    case TEXT_MODE_MONOSPACE:
        ClearWindowTextLines_TextMode0_TextMode1(win, sLineLength);
        break;
    case TEXT_MODE_UNKNOWN2:
        ClearWindowTextLines_TextMode2(win, sLineLength);
        break;
    }
}

static void ClearWindowTextLines_TextMode0_TextMode1(struct Window *win, u8 lineLength)
{
    u8 x, y;
    u16 *buffer;
    u16 fill;

    win->cursorX = 0;
    win->cursorY = 0;
    win->win_field_C = 0;

    buffer = GetCursorTilemapPointer(win);
    fill = GetBlankTileNum(win) | (win->paletteNum << 12);

    for (y = 0; y < 4; y++)
    {
        for (x = 0; x < lineLength; x++)
            buffer[x] = fill;
        buffer += 32;
    }
}

static void ClearWindowTextLines_TextMode2(struct Window *win, u8 lineLength)
{
    u8 *buffer;

    win->cursorX = 0;
    win->cursorY = 0;
    win->win_field_C = 0;

    buffer = win->tileData + 32 * GetCursorTileNum(win, 0, 0);
    CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
    buffer += 32 * win->width;
    CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
    buffer += 32 * win->width;
    CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
    buffer += 32 * win->width;
    CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
}

static void DrawDownArrow(struct Window *win)
{
    if (PlayerCanInterruptDelay(win))
    {
        const u32 *downArrowTiles = &sDownArrowTiles[((win->downArrowCounter & 0x0F00) >> 8) * 16];

        switch (win->textMode)
        {
        case TEXT_MODE_MONOSPACE:
        {
            u8 *buffer;
            u16 tileNum = win->tileDataStartOffset + 254;
            if (win->fontNum == 0 || win->fontNum == 3)
                tileNum *= 2;
            buffer = win->tileData + 32 * tileNum;
            ApplyColors_ShadowedFont(downArrowTiles, buffer, win->foregroundColor, win->shadowColor, win->backgroundColor);
            ApplyColors_ShadowedFont(downArrowTiles + 8, buffer + 32, win->foregroundColor, win->shadowColor, win->backgroundColor);
            WriteGlyphTilemap(win, tileNum, tileNum + 1);
            break;
        }
        case TEXT_MODE_UNKNOWN0:
        case TEXT_MODE_UNKNOWN2:
        {
            struct GlyphTileInfo glyphTileInfo;
            glyphTileInfo.textMode = win->textMode;
            glyphTileInfo.startPixel = (win->left + win->cursorX) & 7;
            if (glyphTileInfo.startPixel != 0)
            {
                u8 *upperTile;
                u8 *lowerTile;
                GetGlyphTilePointers(win->fontNum, win->language, 0, &upperTile, &lowerTile);
                glyphTileInfo.width = 8 - glyphTileInfo.startPixel;
                glyphTileInfo.src = upperTile;
                glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 1, 0));
                glyphTileInfo.colors = sGlyphBuffer.colors;
                DrawGlyphTile_ShadowedFont(&glyphTileInfo);
                glyphTileInfo.src = lowerTile;
                glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 1, 1));
                DrawGlyphTile_ShadowedFont(&glyphTileInfo);
            }
            glyphTileInfo.width = 8;
            glyphTileInfo.src = (u8 *)downArrowTiles;
            glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 0));
            glyphTileInfo.colors = sGlyphBuffer.colors;
            DrawGlyphTile_ShadowedFont(&glyphTileInfo);
            glyphTileInfo.src += 32;
            glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
            DrawGlyphTile_ShadowedFont(&glyphTileInfo);
            if (glyphTileInfo.startPixel != 0)
                UpdateTilemap(win, 2);
            else
                UpdateTilemap(win, 1);
            break;
        }
        }
    }
}

static u8 WaitWithDownArrow(struct Window *win)
{
    u8 retVal = TRUE;

    if (!PlayerCanInterruptDelay(win))
    {
        win->delayCounter--;
        if (!win->delayCounter)
        {
            TryEraseDownArrow(win);
        }
        else
        {
            UpdateDownArrowAnimation(win);
            retVal = FALSE;
        }
    }
    else
    {
        if (gMain.newKeys & (A_BUTTON | B_BUTTON))
        {
            PlaySE(SE_SELECT);
            TryEraseDownArrow(win);
        }
        else
        {
            UpdateDownArrowAnimation(win);
            retVal = FALSE;
        }
    }

    return retVal;
}

static void DrawInitialDownArrow(struct Window *win)
{
    win->downArrowCounter = 0;
    DrawDownArrow(win);
}

static void UpdateDownArrowAnimation(struct Window *win)
{
    u16 downArrowPos = (win->downArrowCounter & 0x0F00) >> 8;
    u16 wait = win->downArrowCounter & 0x000F;
    u16 newVal;

    wait++;

    if (wait == 6)
    {
        wait = 0;
        downArrowPos++;
        if (downArrowPos > 3)
            downArrowPos = 0;
        win->downArrowCounter = downArrowPos << 8;
        DrawDownArrow(win);
    }

    newVal = downArrowPos << 8;
    newVal |= wait;

    win->downArrowCounter = newVal;
}

static void TryEraseDownArrow(struct Window *win)
{
    win->downArrowCounter = 0;
    if (PlayerCanInterruptDelay(win) == TRUE)
        EraseAtCursor(win);
}

// unused
u16 Text_GetWindowTilemapEntry(struct Window *win, u8 x, u8 y)
{
    u16 *tilemap = win->tilemap;
    return tilemap[32 * y + x];
}

// unused
void Text_FillWindowBorder(struct Window *win, u16 tilemapEntry, u8 left, u8 top, u8 right, u8 bottom)
{
    u8 i;
    u16 *buffer = &win->tilemap[top * 32];

    // Fill top border
    for (i = left; i <= right; i++)
        buffer[i] = tilemapEntry;

    for (i = top + 1; i < bottom - 1; i++)
    {
        buffer += 32;
        // left border
        buffer[left] = tilemapEntry;
        // right border
        buffer[right] = tilemapEntry;
    }

    // Fill bottom border
    if (top != bottom)
    {
        buffer += 32;
        for (i = left; i <= right; i++)
            buffer[i] = tilemapEntry;
    }
}

// unused
void DrawWindowRect_DefaultPalette(struct Window *win, u16 tileNum, u8 left, u8 top, u8 right, u8 bottom)
{
    Text_FillWindowBorder(win, (win->paletteNum << 12) | tileNum, left, top, right, bottom);
}

// Fills the whole window area with tilemapEntry
void Text_FillWindowRect(struct Window *win, u16 tilemapEntry, u8 left, u8 top, u8 right, u8 bottom)
{
    u16 *buffer = &win->tilemap[top * 32];
    while (top++ <= bottom)
    {
        u8 x;
        for (x = left; x <= right; x++)
            buffer[x] = tilemapEntry;
        buffer += 32;
    }
}

void Text_FillWindowRectDefPalette(struct Window *win, u16 tileNum, u8 left, u8 top, u8 right, u8 bottom)
{
    Text_FillWindowRect(win, (win->paletteNum << 12) | tileNum, left, top, right, bottom);
}

void Text_EraseWindowRect(struct Window *win, u8 left, u8 top, u8 right, u8 bottom)
{
    Text_FillWindowRectDefPalette(win, 0, left, top, right, bottom);
}

void Text_BlankWindowRect(struct Window *win, u8 left, u8 top, u8 right, u8 bottom)
{
    u16 tileNum = GetBlankTileNum(win);
    Text_FillWindowRectDefPalette(win, tileNum, left, top, right, bottom);
}

static u16 GetBlankTileNum(struct Window *win)
{
    u16 retVal = win->tileDataStartOffset;

    switch (win->textMode)
    {
    case TEXT_MODE_UNKNOWN0:
        break;
    case TEXT_MODE_UNKNOWN2:
        retVal++;
        break;
    case TEXT_MODE_MONOSPACE:
        switch (win->fontNum)
        {
        case 1:
        case 2:
        case 4:
        case 5:
            retVal += 212;
        case 0:
        case 3:
        case 6:
            break;
        default:
            retVal = 0;
        }
        break;
    }

    return retVal;
}

static s32 Window_MoveCursor(struct Window *win, u8 x, u8 y)
{
    win->cursorX = x;
    win->cursorY = y & 0xF8;
}

static u8 GetGlyphWidth(struct Window *win, u32 glyph)
{
    u8 width = 8;

#ifdef BUGFIX_GLYPHWIDTH
    if (win->language != LANGUAGE_JAPANESE)
#else
    if (win->language == LANGUAGE_ENGLISH)
#endif
    {
        width = win->spacing;
        if (!win->spacing)
        {
            switch (win->fontNum)
            {
            case 3:
                width = sFont3Widths[glyph];
                break;
            case 4:
            case 5:
                width = sFont4Widths[sFontType1Map[2 * glyph + 1]];
                break;
            case 0:
                width = sFont0Widths[glyph];
                break;
            case 1:
            case 2:
                width = sFont1Widths[sFontType1Map[2 * glyph + 1]];
                break;
            case 6:
                width = 8;
                break;
            default:
                width = 8;
            }
        }
    }

    return width;
}

u8 GetExtCtrlCodeLength(u8 code)
{
    u8 length = 0;
    if (code <= 0x16)
        length = sExtCtrlCodeLengths[code];
    return length;
}

u8 *AlignInt1(struct Window *win, u8 *dest, s32 value, u8 alignAmount, u8 alignType)
{
    u8 temp[16];
    u8 width;
    switch (alignType)
    {
    case 0:
        ConvertIntToDecimalString(temp, value);
        dest = StringCopy(dest, temp);
        dest[0] = EXT_CTRL_CODE_BEGIN;
        dest[1] = 19;
        dest[2] = alignAmount;
        dest += 3;
        *dest = 0xFF;
        break;
    case 1:
        ConvertIntToDecimalString(temp, value);
        width = GetStringWidth(win, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = alignAmount - width;
            dest += 3;
        }
        dest = StringCopy(dest, temp);
        break;
    case 2:
        ConvertIntToDecimalString(temp, value);
        width = GetStringWidth(win, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = (alignAmount - width) / 2;
            dest += 3;
        }
        dest = StringCopy(dest, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = alignAmount;
            dest += 3;
            *dest = 0xFF;
        }
        break;
    }
    return dest;
}

u8 *AlignInt2(struct Window *win, u8 *dest, s32 value, u8 alignAmount, u8 alignType)
{
    u8 temp[16];
    u8 width;
    switch (alignType)
    {
    case 0:
        ConvertIntToDecimalString(temp, value);
        width = GetStringWidth(win, temp);
        dest = StringCopy(dest, temp);
        dest[0] = EXT_CTRL_CODE_BEGIN;
        dest[1] = 17;
        dest[2] = alignAmount - width;
        dest += 3;
        *dest = 0xFF;
        break;
    case 1:
        ConvertIntToDecimalString(temp, value);
        width = GetStringWidth(win, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 17;
            dest[2] = alignAmount - width;
            dest += 3;
        }
        dest = StringCopy(dest, temp);
        break;
    case 2:
        ConvertIntToDecimalString(temp, value);
        width = GetStringWidth(win, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 17;
            dest[2] = (alignAmount - width) / 2;
            dest += 3;
        }
        dest = StringCopy(dest, temp);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 17;
            dest[2] = (alignAmount - width) / 2;
            dest += 3;
            *dest = 0xFF;
        }
        break;
    }
    return dest;
}

u8 *AlignString(struct Window *win, u8 *dest, const u8 *src, u8 alignAmount, u8 alignType)
{
    u8 width;
    switch (alignType)
    {
    case 0:
        dest = StringCopy(dest, src);
        dest[0] = EXT_CTRL_CODE_BEGIN;
        dest[1] = 19;
        dest[2] = alignAmount;
        dest += 3;
        *dest = 0xFF;
        break;
    case 1:
        width = GetStringWidth(win, src);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = alignAmount - width;
            dest += 3;
        }
        dest = StringCopy(dest, src);
        break;
    case 2:
        width = GetStringWidth(win, src);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = (alignAmount - width) / 2;
            dest += 3;
        }
        dest = StringCopy(dest, src);
        if (alignAmount > width)
        {
            dest[0] = EXT_CTRL_CODE_BEGIN;
            dest[1] = 19;
            dest[2] = alignAmount;
            dest += 3;
            *dest = 0xFF;
        }
        break;
    }
    return dest;
}

u8 GetStringWidth(struct Window *win, const u8 *s)
{
    u8 width = 0;
    u8 savedFontNum = win->fontNum;
    u8 savedLanguage = win->language;
    u8 savedSpacing = win->spacing;
    s32 i = 0;

    while (s[i] != EOS)
    {
        u8 c = s[i];
        switch (c)
        {
        case PLACEHOLDER_BEGIN:
        {
            u8 temp;
            i++;
            temp = win->language;
            width += GetStringWidth(win, GetExpandedPlaceholder(s[i]));
            win->language = temp;
            i++;
            break;
        }
        case EXT_CTRL_CODE_BEGIN:
            i++;
            switch (s[i])
            {
            case 6:
                win->fontNum = s[i + 1];
                break;
            case 7:
                win->fontNum = win->template->fontNum;
                break;
            case 0x11:
                width += s[i + 1];
                break;
            case 0x12:
            case 0x13:
                if (width < s[i + 1])
                    width = s[i + 1];
                break;
            case 0x14:
                win->spacing = s[i + 1];
                break;
            case 0x15:
                win->language = LANGUAGE_JAPANESE;
                break;
            case 0x16:
                win->language = GAME_LANGUAGE;
                break;
            }

            i += GetExtCtrlCodeLength(s[i]);
            break;
        default:
            i++;
            width += GetGlyphWidth(win, c);
        }
    }

    win->spacing = savedSpacing;
    win->language = savedLanguage;
    win->fontNum = savedFontNum;

    return width;
}

u8 Text_InitWindow8004D04(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u16 top, u32 a6)
{
    Text_InitWindow8002E4C(win, text, tileDataStartOffset, left, top, a6);
    return Text_PrintWindow8002F44(win);
}

u8 Text_InitWindow_RightAligned(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 right, u8 top)
{
    u8 width = GetStringWidth(win, text);
    Text_InitWindow(win, text, tileDataStartOffset, right - ((u32)(width + 7) >> 3), top);
    EraseAtCursor(win);
    width &= 7;
    if (width)
        width = 8 - width;
    Window_MoveCursor(win, width, 0);
    return Text_PrintWindow8002F44(win);
}

u8 Text_InitWindow_Centered(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top, u16 width)
{
    width = (u8)(width / 2 - GetStringWidth(win, text) / 2);
    left += (u8)width / 8;
    Text_InitWindow(win, text, tileDataStartOffset, left, top);
    EraseAtCursor(win);
    width &= 7;
    Window_MoveCursor(win, width, 0);
    return Text_PrintWindow8002F44(win);
}

u8 Text_GetWindowPaletteNum(struct Window *win)
{
    return win->paletteNum;
}

void Text_GetTextColors(struct Window *win, u8 *foreground, u8 *background, u8 *shadow)
{
    *foreground = win->foregroundColor;
    *background = win->backgroundColor;
    *shadow = win->shadowColor;
}

void Text_InitWindow8004E3C(const struct WindowTemplate *winTemplate, u8 *tileData, const u8 *text)
{
    sTempWindow.template = winTemplate;
    Text_InitWindow(&sTempWindow, text, 0, 0, 0);
    sTempWindow.tileData = tileData;
    Text_PrintWindow8002F44(&sTempWindow);
}

u8 Text_GetStringWidthFromWindowTemplate(const struct WindowTemplate *winTemplate, const u8 *s)
{
    sTempWindow.template = winTemplate;
    Text_InitWindow(&sTempWindow, s, 0, 0, 0);
    return GetStringWidth(&sTempWindow, s);
}

void ConvertInternationalString(u8 *s, u8 language)
{
    if (language == LANGUAGE_JAPANESE)
    {
        u8 i;

        Text_StripExtCtrlCodes(s);
        i = StringLength(s);
        s[i++] = EXT_CTRL_CODE_BEGIN;
        s[i++] = 22;
        s[i++] = EOS;

        i--;

        while (i != (u8)-1)
        {
            s[i + 2] = s[i];
            i--;
        }

        s[0] = EXT_CTRL_CODE_BEGIN;
        s[1] = 21;
    }
}

void Text_StripExtCtrlCodes(u8 *str)
{
    u16 srcIndex = 0;
    u16 destIndex = 0;
    while (str[srcIndex] != EOS)
    {
        if (str[srcIndex] == EXT_CTRL_CODE_BEGIN)
        {
            srcIndex++;
            srcIndex += GetExtCtrlCodeLength(str[srcIndex]);
        }
        else
        {
            str[destIndex++] = str[srcIndex++];
        }
    }
    str[destIndex] = EOS;
}

static const u8 *SkipExtCtrlCode(const u8 *s)
{
    while (*s == EXT_CTRL_CODE_BEGIN)
    {
        s++;
        s += GetExtCtrlCodeLength(*s);
    }

    return s;
}

s32 StringCompareWithoutExtCtrlCodes(const u8 *str1, const u8 *str2)
{
    s32 retVal = 0;

    while (1)
    {
        str1 = SkipExtCtrlCode(str1);
        str2 = SkipExtCtrlCode(str2);

        if (*str1 > *str2)
            break;

        if (*str1 < *str2)
        {
            retVal = -1;
            if (*str2 == 0xFF)
                retVal = 1;
        }

        if (*str1 == 0xFF)
            return retVal;

        str1++;
        str2++;
    }

    retVal = 1;

    if (*str1 == 0xFF)
        retVal = -1;

    return retVal;
}

u8 sub_8004FD0(struct Window *win, u8 *dest, const u8 *src, u16 tileDataStartOffset, u8 left, u16 top, u8 width, u32 a8)
{
    u8 newlineCount = 0;
    u8 extCtrlCodeLength;
    u8 *start;
    u32 endsWithoutNewline;

    if (dest == NULL)
        dest = gStringVar4;

    start = dest;
    endsWithoutNewline = FALSE;

    while (*src != 0xFF)
    {
        switch (*src)
        {
        default:
            *dest = *src;
            dest++;
            src++;
            endsWithoutNewline = TRUE;
            break;
        case 0xFC:
            extCtrlCodeLength = GetExtCtrlCodeLength(src[1]) + 1;
            memcpy(dest, src, extCtrlCodeLength);
            dest += extCtrlCodeLength;
            src += extCtrlCodeLength;
            break;
        case 0xFE:
            dest[0] = 0xFC;
            dest[1] = 19;
            dest[2] = width;
            dest[3] = 0xFE;
            dest += 4;
            src++;
            newlineCount++;
            endsWithoutNewline = FALSE;
            break;
        }
    }

    dest[0] = 0xFC;
    dest[1] = 19;
    dest[2] = width;
    dest[3] = 0xFF;

    if (endsWithoutNewline)
        newlineCount++;

    Text_InitWindow8002E4C(win, start, tileDataStartOffset, left, top, a8);
    Text_PrintWindow8002F44(win);

    return newlineCount;
}

static s32 DrawGlyphTile_UnshadowedFont(struct GlyphTileInfo *glyphTileInfo)
{
    struct GlyphBuffer *glyphBuffer = &sGlyphBuffer;
    u32 colors[2];
    u32 *buffer = glyphTileInfo->dest;
    const u32 *masks = sGlyphMasks[glyphTileInfo->width][glyphTileInfo->startPixel];
    u32 mask1 = masks[0] | masks[2];

    glyphBuffer->pixelRows[0] = buffer[0] & mask1;
    glyphBuffer->pixelRows[1] = buffer[1] & mask1;
    glyphBuffer->pixelRows[2] = buffer[2] & mask1;
    glyphBuffer->pixelRows[3] = buffer[3] & mask1;
    glyphBuffer->pixelRows[4] = buffer[4] & mask1;
    glyphBuffer->pixelRows[5] = buffer[5] & mask1;
    glyphBuffer->pixelRows[6] = buffer[6] & mask1;
    glyphBuffer->pixelRows[7] = buffer[7] & mask1;

    if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
    {
        u32 mask2 = masks[1];
        if (glyphTileInfo->textMode == TEXT_MODE_UNKNOWN2)
        {
            glyphBuffer->pixelRows[8] = buffer[8] & mask2;
            glyphBuffer->pixelRows[9] = buffer[9] & mask2;
            glyphBuffer->pixelRows[10] = buffer[10] & mask2;
            glyphBuffer->pixelRows[11] = buffer[11] & mask2;
            glyphBuffer->pixelRows[12] = buffer[12] & mask2;
            glyphBuffer->pixelRows[13] = buffer[13] & mask2;
            glyphBuffer->pixelRows[14] = buffer[14] & mask2;
            glyphBuffer->pixelRows[15] = buffer[15] & mask2;
        }
        else
        {
            glyphBuffer->pixelRows[8] = buffer[16] & mask2;
            glyphBuffer->pixelRows[9] = buffer[17] & mask2;
            glyphBuffer->pixelRows[10] = buffer[18] & mask2;
            glyphBuffer->pixelRows[11] = buffer[19] & mask2;
            glyphBuffer->pixelRows[12] = buffer[20] & mask2;
            glyphBuffer->pixelRows[13] = buffer[21] & mask2;
            glyphBuffer->pixelRows[14] = buffer[22] & mask2;
            glyphBuffer->pixelRows[15] = buffer[23] & mask2;
        }
    }

    colors[0] = glyphTileInfo->colors[0];
    colors[1] = glyphTileInfo->colors[15];

    sShiftGlyphTileUnshadowedFuncs[glyphTileInfo->width](glyphBuffer, glyphTileInfo->src, colors, glyphTileInfo->startPixel);

    buffer[0] = glyphBuffer->pixelRows[0];
    buffer[1] = glyphBuffer->pixelRows[1];
    buffer[2] = glyphBuffer->pixelRows[2];
    buffer[3] = glyphBuffer->pixelRows[3];
    buffer[4] = glyphBuffer->pixelRows[4];
    buffer[5] = glyphBuffer->pixelRows[5];
    buffer[6] = glyphBuffer->pixelRows[6];
    buffer[7] = glyphBuffer->pixelRows[7];

    if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
    {
        if (glyphTileInfo->textMode != TEXT_MODE_UNKNOWN2)
            buffer += 8;
        buffer[8] = glyphBuffer->pixelRows[8];
        buffer[9] = glyphBuffer->pixelRows[9];
        buffer[10] = glyphBuffer->pixelRows[10];
        buffer[11] = glyphBuffer->pixelRows[11];
        buffer[12] = glyphBuffer->pixelRows[12];
        buffer[13] = glyphBuffer->pixelRows[13];
        buffer[14] = glyphBuffer->pixelRows[14];
        buffer[15] = glyphBuffer->pixelRows[15];
    }

    return (glyphTileInfo->startPixel + glyphTileInfo->width) / 8;
}

static void ShiftGlyphTile_UnshadowedFont_Width0(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *a3, u8 startPixel)
{
}

static void ShiftGlyphTile_UnshadowedFont_Width1(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = colors[src[i] >> 7];
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width2(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] << 0)
                | (colors[(src[i] >> 6) & 1] << 4);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width3(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        // XXX: why 4?
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width4(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width5(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12)
                | (colors[(src[i] >> 3) & 1] << 16);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width6(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12)
                | (colors[(src[i] >> 3) & 1] << 16)
                | (colors[(src[i] >> 2) & 1] << 20);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width7(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12)
                | (colors[(src[i] >> 3) & 1] << 16)
                | (colors[(src[i] >> 2) & 1] << 20)
                | (colors[(src[i] >> 1) & 1] << 24);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_UnshadowedFont_Width8(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 7) & 1] <<  0)
                | (colors[(src[i] >> 6) & 1] <<  4)
                | (colors[(src[i] >> 5) & 1] <<  8)
                | (colors[(src[i] >> 4) & 1] << 12)
                | (colors[(src[i] >> 3) & 1] << 16)
                | (colors[(src[i] >> 2) & 1] << 20)
                | (colors[(src[i] >> 1) & 1] << 24)
                | (colors[(src[i] >> 0) & 1] << 28);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static s32 DrawGlyphTile_ShadowedFont(struct GlyphTileInfo *glyphTileInfo)
{
    struct GlyphBuffer *glyphBuffer = &sGlyphBuffer;
    u32 *buffer = glyphTileInfo->dest;
    const u32 *masks = sGlyphMasks[glyphTileInfo->width][glyphTileInfo->startPixel];
    u32 mask1 = masks[0] | masks[2];

    glyphBuffer->pixelRows[0] = buffer[0] & mask1;
    glyphBuffer->pixelRows[1] = buffer[1] & mask1;
    glyphBuffer->pixelRows[2] = buffer[2] & mask1;
    glyphBuffer->pixelRows[3] = buffer[3] & mask1;
    glyphBuffer->pixelRows[4] = buffer[4] & mask1;
    glyphBuffer->pixelRows[5] = buffer[5] & mask1;
    glyphBuffer->pixelRows[6] = buffer[6] & mask1;
    glyphBuffer->pixelRows[7] = buffer[7] & mask1;

    if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
    {
        u32 mask2 = masks[1];
        if (glyphTileInfo->textMode == TEXT_MODE_UNKNOWN2)
        {
            glyphBuffer->pixelRows[8] = buffer[8] & mask2;
            glyphBuffer->pixelRows[9] = buffer[9] & mask2;
            glyphBuffer->pixelRows[10] = buffer[10] & mask2;
            glyphBuffer->pixelRows[11] = buffer[11] & mask2;
            glyphBuffer->pixelRows[12] = buffer[12] & mask2;
            glyphBuffer->pixelRows[13] = buffer[13] & mask2;
            glyphBuffer->pixelRows[14] = buffer[14] & mask2;
            glyphBuffer->pixelRows[15] = buffer[15] & mask2;
        }
        else
        {
            glyphBuffer->pixelRows[8] = buffer[16] & mask2;
            glyphBuffer->pixelRows[9] = buffer[17] & mask2;
            glyphBuffer->pixelRows[10] = buffer[18] & mask2;
            glyphBuffer->pixelRows[11] = buffer[19] & mask2;
            glyphBuffer->pixelRows[12] = buffer[20] & mask2;
            glyphBuffer->pixelRows[13] = buffer[21] & mask2;
            glyphBuffer->pixelRows[14] = buffer[22] & mask2;
            glyphBuffer->pixelRows[15] = buffer[23] & mask2;
        }
    }

    sShiftGlyphTileShadowedFuncs[glyphTileInfo->width](glyphBuffer, (u32 *)glyphTileInfo->src, glyphTileInfo->colors, glyphTileInfo->startPixel);

    buffer[0] = glyphBuffer->pixelRows[0];
    buffer[1] = glyphBuffer->pixelRows[1];
    buffer[2] = glyphBuffer->pixelRows[2];
    buffer[3] = glyphBuffer->pixelRows[3];
    buffer[4] = glyphBuffer->pixelRows[4];
    buffer[5] = glyphBuffer->pixelRows[5];
    buffer[6] = glyphBuffer->pixelRows[6];
    buffer[7] = glyphBuffer->pixelRows[7];

    if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
    {
        if (glyphTileInfo->textMode != TEXT_MODE_UNKNOWN2)
            buffer += 8;
        buffer[8] = glyphBuffer->pixelRows[8];
        buffer[9] = glyphBuffer->pixelRows[9];
        buffer[10] = glyphBuffer->pixelRows[10];
        buffer[11] = glyphBuffer->pixelRows[11];
        buffer[12] = glyphBuffer->pixelRows[12];
        buffer[13] = glyphBuffer->pixelRows[13];
        buffer[14] = glyphBuffer->pixelRows[14];
        buffer[15] = glyphBuffer->pixelRows[15];
    }

    return (glyphTileInfo->startPixel + glyphTileInfo->width) / 8;
}

static void ShiftGlyphTile_ShadowedFont_Width0(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
}

static void ShiftGlyphTile_ShadowedFont_Width1(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = colors[src[i] & 0xF];
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_ShadowedFont_Width2(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 0) & 0xF] << 0)
                | (colors[(src[i] >> 4) & 0xF] << 4);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_ShadowedFont_Width3(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >> 0) & 0xF] << 0)
                | (colors[(src[i] >> 4) & 0xF] << 4)
                | (colors[(src[i] >> 8) & 0xF] << 8);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

static void ShiftGlyphTile_ShadowedFont_Width4(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u8 i;
    for (i = 0; i < 8; i++)
    {
        u32 val = (colors[(src[i] >>  0) & 0xF] <<  0)
                | (colors[(src[i] >>  4) & 0xF] <<  4)
                | (colors[(src[i] >>  8) & 0xF] <<  8)
                | (colors[(src[i] >> 12) & 0xF] << 12);
        u32 *dest = &glyphBuffer->pixelRows[i];
        dest[0] |= val << shiftAmount->left;
        dest[8] |= val >> shiftAmount->right;
    }
}

#define SHIFT_GLYPH_WIDTH5_STEP(i)                          \
val = (colors[(src[i] >>  0) & 0xF] <<  0)                  \
    | (colors[(src[i] >>  4) & 0xF] <<  4)                  \
    | (colors[(src[i] >>  8) & 0xF] <<  8)                  \
    | (colors[(src[i] >> 12) & 0xF] << 12)                  \
    | (colors[(src[i] >> 16) & 0xF] << 16);                 \
glyphBuffer->pixelRows[i]     |= val << shiftAmount->left;  \
glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \

static void ShiftGlyphTile_ShadowedFont_Width5(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u32 val;
    SHIFT_GLYPH_WIDTH5_STEP(0)
    SHIFT_GLYPH_WIDTH5_STEP(1)
    SHIFT_GLYPH_WIDTH5_STEP(2)
    SHIFT_GLYPH_WIDTH5_STEP(3)
    SHIFT_GLYPH_WIDTH5_STEP(4)
    SHIFT_GLYPH_WIDTH5_STEP(5)
    SHIFT_GLYPH_WIDTH5_STEP(6)
    SHIFT_GLYPH_WIDTH5_STEP(7)
}

#define SHIFT_GLYPH_WIDTH6_STEP(i)                          \
val = (colors[(src[i] >>  0) & 0xF] <<  0)                  \
    | (colors[(src[i] >>  4) & 0xF] <<  4)                  \
    | (colors[(src[i] >>  8) & 0xF] <<  8)                  \
    | (colors[(src[i] >> 12) & 0xF] << 12)                  \
    | (colors[(src[i] >> 16) & 0xF] << 16)                  \
    | (colors[(src[i] >> 20) & 0xF] << 20);                 \
glyphBuffer->pixelRows[i]     |= val << shiftAmount->left;  \
glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \

static void ShiftGlyphTile_ShadowedFont_Width6(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u32 val;
    SHIFT_GLYPH_WIDTH6_STEP(0)
    SHIFT_GLYPH_WIDTH6_STEP(1)
    SHIFT_GLYPH_WIDTH6_STEP(2)
    SHIFT_GLYPH_WIDTH6_STEP(3)
    SHIFT_GLYPH_WIDTH6_STEP(4)
    SHIFT_GLYPH_WIDTH6_STEP(5)
    SHIFT_GLYPH_WIDTH6_STEP(6)
    SHIFT_GLYPH_WIDTH6_STEP(7)
}

#define SHIFT_GLYPH_WIDTH7_STEP(i)                          \
val = (colors[(src[i] >>  0) & 0xF] <<  0)                  \
    | (colors[(src[i] >>  4) & 0xF] <<  4)                  \
    | (colors[(src[i] >>  8) & 0xF] <<  8)                  \
    | (colors[(src[i] >> 12) & 0xF] << 12)                  \
    | (colors[(src[i] >> 16) & 0xF] << 16)                  \
    | (colors[(src[i] >> 20) & 0xF] << 20)                  \
    | (colors[(src[i] >> 24) & 0xF] << 24);                 \
glyphBuffer->pixelRows[i]     |= val << shiftAmount->left;  \
glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \

static void ShiftGlyphTile_ShadowedFont_Width7(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u32 val;
    SHIFT_GLYPH_WIDTH7_STEP(0)
    SHIFT_GLYPH_WIDTH7_STEP(1)
    SHIFT_GLYPH_WIDTH7_STEP(2)
    SHIFT_GLYPH_WIDTH7_STEP(3)
    SHIFT_GLYPH_WIDTH7_STEP(4)
    SHIFT_GLYPH_WIDTH7_STEP(5)
    SHIFT_GLYPH_WIDTH7_STEP(6)
    SHIFT_GLYPH_WIDTH7_STEP(7)
}

#define SHIFT_GLYPH_WIDTH8_STEP(i)                          \
val = (colors[(src[i] >>  0) & 0xF] <<  0)                  \
    | (colors[(src[i] >>  4) & 0xF] <<  4)                  \
    | (colors[(src[i] >>  8) & 0xF] <<  8)                  \
    | (colors[(src[i] >> 12) & 0xF] << 12)                  \
    | (colors[(src[i] >> 16) & 0xF] << 16)                  \
    | (colors[(src[i] >> 20) & 0xF] << 20)                  \
    | (colors[(src[i] >> 24) & 0xF] << 24)                  \
    | (colors[(src[i] >> 28)      ] << 28);                 \
glyphBuffer->pixelRows[i]     |= val << shiftAmount->left;  \
glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \

static void ShiftGlyphTile_ShadowedFont_Width8(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
{
    const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
    u32 val;
    SHIFT_GLYPH_WIDTH8_STEP(0)
    SHIFT_GLYPH_WIDTH8_STEP(1)
    SHIFT_GLYPH_WIDTH8_STEP(2)
    SHIFT_GLYPH_WIDTH8_STEP(3)
    SHIFT_GLYPH_WIDTH8_STEP(4)
    SHIFT_GLYPH_WIDTH8_STEP(5)
    SHIFT_GLYPH_WIDTH8_STEP(6)
    SHIFT_GLYPH_WIDTH8_STEP(7)
}

static s32 DrawGlyphTiles(struct Window *win, u32 glyph, u32 glyphWidth)
{
    struct GlyphTileInfo glyphTileInfo;
    u8 *upperTile;
    u8 *lowerTile;
    s32 retVal = 0;

    GetGlyphTilePointers(win->fontNum, win->language, glyph, &upperTile, &lowerTile);
    glyphTileInfo.textMode = win->textMode;
    glyphTileInfo.startPixel = (win->left + win->cursorX) & 7;
    glyphTileInfo.width = glyphWidth;
    glyphTileInfo.src = upperTile;
    glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 0));
    glyphTileInfo.colors = sGlyphBuffer.colors;

    switch (win->fontNum)
    {
    case 0:
    case 1:
    case 2:
    case 6:
        DrawGlyphTile_UnshadowedFont(&glyphTileInfo);
        glyphTileInfo.src = lowerTile;
        glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
        retVal = DrawGlyphTile_UnshadowedFont(&glyphTileInfo);
        break;
    case 3:
    case 4:
    case 5:
        DrawGlyphTile_ShadowedFont(&glyphTileInfo);
        glyphTileInfo.src = lowerTile;
        glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
        retVal = DrawGlyphTile_ShadowedFont(&glyphTileInfo);
        break;
    }

    return retVal;
}

static void UpdateTilemap(struct Window *win, u32 tilesWidth)
{
    u16 *tilemap = GetCursorTilemapPointer(win);
    if (tilesWidth && tilemap)
    {
        u16 paletteNum = (win->paletteNum << 12);
        u16 upperLeftTileNum = GetCursorTileNum(win, 0, 0);
        u16 lowerLeftTileNum = GetCursorTileNum(win, 0, 1);
        tilemap[0] = upperLeftTileNum | paletteNum;
        tilemap[32] = lowerLeftTileNum | paletteNum;
        if (tilesWidth == 2)
        {
            u16 upperRightTileNum = GetCursorTileNum(win, 1, 0);
            u16 lowerRightTileNum = GetCursorTileNum(win, 1, 1);
            tilemap[1] = upperRightTileNum | paletteNum;
            tilemap[33] = lowerRightTileNum | paletteNum;
        }
    }
}

static u16 GetCursorTileNum(struct Window *win, u32 xOffset, u32 yOffset)
{
    u16 index;

    if (win->textMode == TEXT_MODE_UNKNOWN2)
        index = win->tileDataStartOffset
              + win->tileDataOffset
              + (((win->top + win->cursorY) >> 3) + yOffset) * win->width
              + (((win->left + win->cursorX) >> 3) + xOffset);
    else
        index = win->tileDataStartOffset + win->tileDataOffset + 2 * xOffset + yOffset;

    return index;
}
