#include "global.h"
#include "intro_credits_graphics.h"
#include "palette.h"
#include "decompress.h"
#include "gpu_regs.h"
#include "task.h"
#include "main.h"
#include "graphics.h"
#include "constants/rgb.h"

/*
    The graphics here are used by both the second scene of the intro
    and the credit sequence, where the player bikes along a grassy path.

    These graphics are placed in graphics/intro/scene_2 to keep
    all of the intro graphics files together, though it includes
    the related graphics that are used only by the credits.
*/

#define TAG_BICYCLE 1001
#define TAG_BRENDAN 1002
#define TAG_MAY     1003
#define TAG_FLYGON_LATIOS  1004
#define TAG_FLYGON_LATIAS  1005

// Used for the Clouds/Trees/Houses sprites that pass by in the background
#define TAG_MOVING_SCENERY 2000

struct IntroCreditsSpriteMetadata
{
    u8 animNum:4;
    u8 shape:2;
    u8 size:2;
    u8 x;
    u8 y;
    u8 subpriority;
    u16 xOff;
};

static const u16 sGrass_Pal[]             = INCBIN_U16("graphics/intro/scene_2/grass.gbapal");
static const u16 sGrassSunset_Pal[]       = INCBIN_U16("graphics/intro/scene_2/grass_sunset.gbapal");
static const u16 sGrassNight_Pal[]        = INCBIN_U16("graphics/intro/scene_2/grass_night.gbapal");
static const u32 sGrass_Gfx[]             = INCBIN_U32("graphics/intro/scene_2/grass.4bpp.lz");
static const u32 sGrass_Tilemap[]         = INCBIN_U32("graphics/intro/scene_2/grass_map.bin.lz");
static const u16 sCloudsBg_Pal[]          = INCBIN_U16("graphics/intro/scene_2/clouds_bg.gbapal");
static const u16 sCloudsBgSunset_Pal[]    = INCBIN_U16("graphics/intro/scene_2/clouds_bg_sunset.gbapal");
static const u32 sCloudsBg_Gfx[]          = INCBIN_U32("graphics/intro/scene_2/clouds_bg.4bpp.lz");
static const u32 sCloudsBg_Tilemap[]      = INCBIN_U32("graphics/intro/scene_2/clouds_bg_map.bin.lz");
static const u16 sClouds_Pal[]            = INCBIN_U16("graphics/intro/scene_2/clouds.gbapal");
static const u16 sCloudsSunset_Pal[]      = INCBIN_U16("graphics/intro/scene_2/clouds_sunset.gbapal");
static const u32 sClouds_Gfx[]            = INCBIN_U32("graphics/intro/scene_2/clouds.4bpp.lz");
static const u16 sTrees_Pal[]             = INCBIN_U16("graphics/intro/scene_2/trees.gbapal");
static const u16 sTreesSunset_Pal[]       = INCBIN_U16("graphics/intro/scene_2/trees_sunset.gbapal");
static const u32 sTrees_Gfx[]             = INCBIN_U32("graphics/intro/scene_2/trees.4bpp.lz");
static const u32 sTrees_Tilemap[]         = INCBIN_U32("graphics/intro/scene_2/trees_map.bin.lz");
static const u16 sTreesSmall_Pal[]        = INCBIN_U16("graphics/intro/scene_2/trees_small.gbapal");
static const u32 sTreesSmall_Gfx[]        = INCBIN_U32("graphics/intro/scene_2/trees_small.4bpp.lz");
static const u16 sHouses_Pal[]            = INCBIN_U16("graphics/intro/scene_2/houses.gbapal");
static const u32 sHouses_Gfx[]            = INCBIN_U32("graphics/intro/scene_2/houses.4bpp.lz");
static const u16 sHouseSilhouette_Pal[]   = INCBIN_U16("graphics/intro/scene_2/house_silhouette.gbapal");
static const u32 sHouses_Tilemap[]        = INCBIN_U32("graphics/intro/scene_2/houses_map.bin.lz");
static const u32 sHouseSilhouette_Gfx[]   = INCBIN_U32("graphics/intro/scene_2/house_silhouette.4bpp.lz");
static const u16 sBrendanCredits_Pal[]    = INCBIN_U16("graphics/intro/scene_2/brendan_credits.gbapal");
static const u32 sBrendanCredits_Gfx[]    = INCBIN_U32("graphics/intro/scene_2/brendan_credits.4bpp.lz");
static const u16 sMayCredits_Pal[]        = INCBIN_U16("graphics/intro/scene_2/may_credits.gbapal");
static const u16 sUnused[0xF0]            = {0};
static const u32 sMayCredits_Gfx[]        = INCBIN_U32("graphics/intro/scene_2/may_credits.4bpp.lz");
static const u32 sBicycle_Gfx[]           = INCBIN_U32("graphics/intro/scene_2/bicycle.4bpp.lz");
static const u16 sLatios_Pal[]            = INCBIN_U16("graphics/intro/scene_2/latios.gbapal");
static const u32 sLatios_Gfx[]            = INCBIN_U32("graphics/intro/scene_2/latios.4bpp.lz");
static const u16 sLatias_Pal[]            = INCBIN_U16("graphics/intro/scene_2/latias.gbapal");
static const u32 sLatias_Gfx[]            = INCBIN_U32("graphics/intro/scene_2/latias.4bpp.lz");

static void SpriteCB_MovingScenery(struct Sprite *sprite);
static void SpriteCB_Player(struct Sprite *sprite);
static void SpriteCB_Bicycle(struct Sprite *sprite);
static void SpriteCB_FlygonLeftHalf(struct Sprite *sprite);

static const struct SpriteTemplate sSpriteTemplate_MovingScenery =
{
    .tileTag = TAG_MOVING_SCENERY,
    .paletteTag = TAG_NONE,
    .oam = &gDummyOamData,
    .anims = gDummySpriteAnimTable,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_MovingScenery
};

static const struct CompressedSpriteSheet sSpriteSheet_Clouds[] =
{
    {
        .data = sClouds_Gfx,
        .size = 0x400,
        .tag = TAG_MOVING_SCENERY
    },
    {}
};

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

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

static const union AnimCmd sAnim_Cloud_Small[] =
{
    ANIMCMD_FRAME(20, 30),
    ANIMCMD_END
};

static const union AnimCmd sAnim_Cloud_Smallest[] =
{
    ANIMCMD_FRAME(22, 30),
    ANIMCMD_END
};

static const union AnimCmd *const sAnims_Clouds[] =
{
    sAnim_Cloud_Largest,
    sAnim_Cloud_Large,
    sAnim_Cloud_Small,
    sAnim_Cloud_Smallest
};

static const struct IntroCreditsSpriteMetadata sSpriteMetadata_Clouds[] =
{
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 72,
        .y = 32,
        .subpriority = 100,
        .xOff = 0xc00
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 158,
        .y = 32,
        .subpriority = 100,
        .xOff = 0xc00
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x16),
        .size = SPRITE_SIZE(16x16),
        .x = 192,
        .y = 40,
        .subpriority = 101,
        .xOff = 0x800
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x16),
        .size = SPRITE_SIZE(16x16),
        .x = 56,
        .y = 40,
        .subpriority = 101,
        .xOff = 0x800
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x8),
        .size = SPRITE_SIZE(16x8),
        .x = 100,
        .y = 44,
        .subpriority = 102,
        .xOff = 0x400
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x8),
        .size = SPRITE_SIZE(16x8),
        .x = 152,
        .y = 44,
        .subpriority = 102,
        .xOff = 0x400
    },
    {
        .animNum = 3,
        .shape = SPRITE_SHAPE(16x8),
        .size = SPRITE_SIZE(16x8),
        .x = 8,
        .y = 46,
        .subpriority = 103,
        .xOff = 0x100
    },
    {
        .animNum = 3,
        .shape = SPRITE_SHAPE(16x8),
        .size = SPRITE_SIZE(16x8),
        .x = 56,
        .y = 46,
        .subpriority = 103,
        .xOff = 0x100
    },
    {
        .animNum = 3,
        .shape = SPRITE_SHAPE(16x8),
        .size = SPRITE_SIZE(16x8),
        .x = 240,
        .y = 46,
        .subpriority = 103,
        .xOff = 0x100
    },
};

static const struct CompressedSpriteSheet sSpriteSheet_TreesSmall[] =
{
    {
        .data = sTreesSmall_Gfx,
        .size = 0x400,
        .tag = TAG_MOVING_SCENERY
    },
    {}
};

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

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

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

static const union AnimCmd *const sAnims_Trees[] =
{
    sAnim_Trees_0,
    sAnim_Trees_1,
    sAnim_Trees_2
};

static const struct IntroCreditsSpriteMetadata sSpriteMetadata_Trees[] =
{
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 16,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x2000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 80,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x2000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 144,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x2000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 208,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x2000,
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 40,
        .y = 88,
        .subpriority = 101,
        .xOff = 0x1000,
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 104,
        .y = 88,
        .subpriority = 101,
        .xOff = 0x1000,
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 168,
        .y = 88,
        .subpriority = 101,
        .xOff = 0x1000,
    },
    {
        .animNum = 1,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 232,
        .y = 88,
        .subpriority = 101,
        .xOff = 0x1000,
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 56,
        .y = 88,
        .subpriority = 102,
        .xOff = 0x800,
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 120,
        .y = 88,
        .subpriority = 102,
        .xOff = 0x800,
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 184,
        .y = 88,
        .subpriority = 102,
        .xOff = 0x800,
    },
    {
        .animNum = 2,
        .shape = SPRITE_SHAPE(16x32),
        .size = SPRITE_SIZE(16x32),
        .x = 248,
        .y = 88,
        .subpriority = 102,
        .xOff = 0x800,
    }
};

static const struct CompressedSpriteSheet sSpriteSheet_HouseSilhouette[] =
{
    {
        .data = sHouseSilhouette_Gfx,
        .size = 0x400,
        .tag = TAG_MOVING_SCENERY
    },
    {}
};

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

static const union AnimCmd *const sAnims_HouseSilhouette[] =
{
    sAnim_HouseSilhouette
};

static const struct IntroCreditsSpriteMetadata sSpriteMetadata_HouseSilhouette[] =
{
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 24,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 64,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 104,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 144,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 184,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    },
    {
        .animNum = 0,
        .shape = SPRITE_SHAPE(32x32),
        .size = SPRITE_SIZE(32x32),
        .x = 224,
        .y = 88,
        .subpriority = 100,
        .xOff = 0x1000,
    }
};

static const struct OamData sOamData_Player =
{
    .y = DISPLAY_HEIGHT,
    .shape = SPRITE_SHAPE(64x64),
    .size = SPRITE_SIZE(64x64),
    .priority = 1
};

static const union AnimCmd sAnim_Player[] =
{
    ANIMCMD_FRAME(  0, 8),
    ANIMCMD_FRAME( 64, 8),
    ANIMCMD_FRAME(128, 8),
    ANIMCMD_FRAME(192, 8),
    ANIMCMD_JUMP(0)
};

static const union AnimCmd *const sAnims_Player[] =
{
    sAnim_Player
};

static const struct SpriteTemplate sSpriteTemplate_Brendan =
{
    .tileTag = TAG_BRENDAN,
    .paletteTag = TAG_BRENDAN,
    .oam = &sOamData_Player,
    .anims = sAnims_Player,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_Player
};

static const struct SpriteTemplate sSpriteTemplate_May =
{
    .tileTag = TAG_MAY,
    .paletteTag = TAG_MAY,
    .oam = &sOamData_Player,
    .anims = sAnims_Player,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_Player
};

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

static const union AnimCmd sAnim_Bicycle[] =
{
    ANIMCMD_FRAME(  0, 8),
    ANIMCMD_FRAME( 32, 8),
    ANIMCMD_FRAME( 64, 8),
    ANIMCMD_FRAME( 96, 8),
    ANIMCMD_JUMP(0)
};

static const union AnimCmd *const sAnims_Bicycle[] =
{
    sAnim_Bicycle
};

static const struct SpriteTemplate sSpriteTemplate_BrendanBicycle =
{
    .tileTag = TAG_BICYCLE,
    .paletteTag = TAG_BRENDAN,
    .oam = &sOamData_Bicycle,
    .anims = sAnims_Bicycle,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_Bicycle
};

static const struct SpriteTemplate sSpriteTemplate_MayBicycle =
{
    .tileTag = TAG_BICYCLE,
    .paletteTag = TAG_MAY,
    .oam = &sOamData_Bicycle,
    .anims = sAnims_Bicycle,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_Bicycle
};

static const struct OamData sOamData_Flygon =
{
    .y = DISPLAY_HEIGHT,
    .shape = SPRITE_SHAPE(64x64),
    .size = SPRITE_SIZE(64x64),
    .priority = 1
};

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

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

static const union AnimCmd *const sAnims_Flygon[] =
{
    sAnim_FlygonLeft,
    sAnim_FlygonRight
};

static const struct SpriteTemplate sSpriteTemplate_FlygonLatios =
{
    .tileTag = TAG_FLYGON_LATIOS,
    .paletteTag = TAG_FLYGON_LATIOS,
    .oam = &sOamData_Flygon,
    .anims = sAnims_Flygon,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_FlygonLeftHalf
};

static const struct SpriteTemplate sSpriteTemplate_FlygonLatias =
{
    .tileTag = TAG_FLYGON_LATIAS,
    .paletteTag = TAG_FLYGON_LATIAS,
    .oam = &sOamData_Flygon,
    .anims = sAnims_Flygon,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCB_FlygonLeftHalf
};

const struct CompressedSpriteSheet gSpriteSheet_IntroBrendan[] =
{
    {
        .data = gIntroBrendan_Gfx,
        .size = 0x2000,
        .tag = TAG_BRENDAN
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_IntroMay[] =
{
    {
        .data = gIntroMay_Gfx,
        .size = 0x2000,
        .tag = TAG_MAY
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_IntroBicycle[] =
{
    {
        .data = sBicycle_Gfx,
        .size = 0x1000,
        .tag = TAG_BICYCLE
    },
    {}
};

// In RS these were Latios/Latias. In Emerald both are replaced with Flygon and now only 1 is used
static const struct CompressedSpriteSheet sSpriteSheet_IntroFlygon_Unused[] =
{
    {
        .data = gIntroFlygon_Gfx,
        .size = 0x1000,
        .tag = TAG_FLYGON_LATIOS
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_IntroFlygon[] =
{
    {
        .data = gIntroFlygon_Gfx,
        .size = 0x1000,
        .tag = TAG_FLYGON_LATIAS
    },
    {}
};

const struct SpritePalette gSpritePalettes_IntroPlayerFlygon[] =
{
    { .data = gIntroPlayer_Pal, .tag = TAG_BRENDAN },
    { .data = gIntroPlayer_Pal, .tag = TAG_MAY },
    { .data = gIntroFlygon_Pal, .tag = TAG_FLYGON_LATIOS },
    { .data = gIntroFlygon_Pal, .tag = TAG_FLYGON_LATIAS },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_CreditsBrendan[] =
{
    {
        .data = sBrendanCredits_Gfx,
        .size = 0x3800,
        .tag = TAG_BRENDAN
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_CreditsMay[] =
{
    {
        .data = sMayCredits_Gfx,
        .size = 0x3800,
        .tag = TAG_MAY
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_CreditsBicycle[] =
{
    {
        .data = sBicycle_Gfx,
        .size = 0x1000,
        .tag = TAG_BICYCLE
    },
    {}
};

// Unused
static const struct CompressedSpriteSheet sSpriteSheet_Latios[] =
{
    {
        .data = sLatios_Gfx,
        .size = 0x1000,
        .tag = TAG_FLYGON_LATIOS
    },
    {}
};

// Unused
static const struct CompressedSpriteSheet sSpriteSheet_Latias[] =
{
    {
        .data = sLatias_Gfx,
        .size = 0x1000,
        .tag = TAG_FLYGON_LATIAS
    },
    {}
};

const struct SpritePalette gSpritePalettes_Credits[] =
{
    { .data = sBrendanCredits_Pal, .tag = TAG_BRENDAN },
    { .data = sMayCredits_Pal,     .tag = TAG_MAY },
    { .data = sLatios_Pal,         .tag = TAG_FLYGON_LATIOS },
    { .data = sLatias_Pal,         .tag = TAG_FLYGON_LATIAS },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_CreditsRivalBrendan[] =
{
    {
        .data = sBrendanCredits_Gfx,
        .size = 0x2000,
        .tag = TAG_BRENDAN
    },
    {}
};

const struct CompressedSpriteSheet gSpriteSheet_CreditsRivalMay[] =
{
    {
        .data = sMayCredits_Gfx,
        .size = 0x2000,
        .tag = TAG_MAY
    },
    {}
};

EWRAM_DATA u16 gIntroCredits_MovingSceneryVBase = 0;
EWRAM_DATA s16 gIntroCredits_MovingSceneryVOffset = 0;
EWRAM_DATA s16 gIntroCredits_MovingSceneryState = 0;

static void CreateCloudSprites(void);
static void CreateTreeSprites(void);
static void CreateHouseSprites(void);
static void Task_BicycleBgAnimation(u8);

void LoadIntroPart2Graphics(u8 scenery)
{
    LZ77UnCompVram(sGrass_Gfx, (void *)(BG_CHAR_ADDR(1)));
    LZ77UnCompVram(sGrass_Tilemap, (void *)(BG_SCREEN_ADDR(15)));
    LoadPalette(&sGrass_Pal, BG_PLTT_ID(15), sizeof(sGrass_Pal));
    switch (scenery)
    {
    case 0:
    default:
        // Never reached, only called with an argument of 1
        // Clouds are never used in this part of the intro
        LZ77UnCompVram(sCloudsBg_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sCloudsBg_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sCloudsBg_Pal, BG_PLTT_ID(0), sizeof(sCloudsBg_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_Clouds);
        LoadPalette(&sClouds_Pal, OBJ_PLTT_ID(0), sizeof(sClouds_Pal));
        CreateCloudSprites();
        break;
    case 1:
        LZ77UnCompVram(sTrees_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sTrees_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sTrees_Pal, BG_PLTT_ID(0), sizeof(sTrees_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_TreesSmall);
        LoadPalette(&sTreesSmall_Pal, OBJ_PLTT_ID(0), sizeof(sTreesSmall_Pal));
        CreateTreeSprites();
        break;
    }
    gIntroCredits_MovingSceneryState = INTROCRED_SCENERY_NORMAL;
    gReservedSpritePaletteCount = 8;
}

// Note: This is only called with scenery=1.
void SetIntroPart2BgCnt(u8 scenery)
{
    switch (scenery)
    {
    default:
    case 0:
        SetGpuReg(REG_OFFSET_BG3CNT, BGCNT_PRIORITY(3)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(6)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG2CNT, BGCNT_PRIORITY(2)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(7)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG1CNT, BGCNT_PRIORITY(1)
                                   | BGCNT_CHARBASE(1)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(15)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0
                                    | DISPCNT_OBJ_1D_MAP
                                    | DISPCNT_BG1_ON
                                    | DISPCNT_BG2_ON
                                    | DISPCNT_BG3_ON
                                    | DISPCNT_OBJ_ON);
        break;
    case 1:
        SetGpuReg(REG_OFFSET_BG3CNT, BGCNT_PRIORITY(3)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(6)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG2CNT, BGCNT_PRIORITY(2)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(7)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG1CNT, BGCNT_PRIORITY(1)
                                   | BGCNT_CHARBASE(1)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(15)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0
                                    | DISPCNT_OBJ_1D_MAP
                                    | DISPCNT_BG1_ON
                                    | DISPCNT_BG2_ON
                                    | DISPCNT_BG3_ON
                                    | DISPCNT_OBJ_ON);
        break;
    case 2:
        SetGpuReg(REG_OFFSET_BG3CNT, BGCNT_PRIORITY(3)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(6)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG2CNT, BGCNT_PRIORITY(2)
                                   | BGCNT_CHARBASE(0)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(7)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_BG1CNT, BGCNT_PRIORITY(1)
                                   | BGCNT_CHARBASE(1)
                                   | BGCNT_16COLOR
                                   | BGCNT_SCREENBASE(15)
                                   | BGCNT_TXT256x256);
        SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0
                                    | DISPCNT_OBJ_1D_MAP
                                    | DISPCNT_BG1_ON
                                    | DISPCNT_BG2_ON
                                    | DISPCNT_BG3_ON
                                    | DISPCNT_OBJ_ON);
        break;
    }
}

void LoadCreditsSceneGraphics(u8 scene)
{
    LZ77UnCompVram(sGrass_Gfx, (void *)(BG_CHAR_ADDR(1)));
    LZ77UnCompVram(sGrass_Tilemap, (void *)(BG_SCREEN_ADDR(15)));
    switch (scene)
    {
    case SCENE_OCEAN_MORNING:
    default:
        LoadPalette(&sGrass_Pal, BG_PLTT_ID(15), sizeof(sGrass_Pal));
        LZ77UnCompVram(sCloudsBg_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sCloudsBg_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sCloudsBg_Pal, BG_PLTT_ID(0), sizeof(sCloudsBg_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_Clouds);
        LZ77UnCompVram(sClouds_Gfx, (void *)(OBJ_VRAM0));
        LoadPalette(&sClouds_Pal, OBJ_PLTT_ID(0), sizeof(sClouds_Pal));
        CreateCloudSprites();
        break;
    case SCENE_OCEAN_SUNSET:
        LoadPalette(&sGrassSunset_Pal, BG_PLTT_ID(15), sizeof(sGrassSunset_Pal));
        LZ77UnCompVram(sCloudsBg_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sCloudsBg_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sCloudsBgSunset_Pal, BG_PLTT_ID(0), sizeof(sCloudsBgSunset_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_Clouds);
        LZ77UnCompVram(sClouds_Gfx, (void *)(OBJ_VRAM0));
        LoadPalette(&sCloudsSunset_Pal, OBJ_PLTT_ID(0), sizeof(sCloudsSunset_Pal));
        CreateCloudSprites();
        break;
    case SCENE_FOREST_RIVAL_ARRIVE:
    case SCENE_FOREST_CATCH_RIVAL:
        LoadPalette(&sGrassSunset_Pal, BG_PLTT_ID(15), sizeof(sGrassSunset_Pal));
        LZ77UnCompVram(sTrees_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sTrees_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sTreesSunset_Pal, BG_PLTT_ID(0), sizeof(sTreesSunset_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_TreesSmall);
        LoadPalette(&sTreesSunset_Pal, OBJ_PLTT_ID(0), sizeof(sTreesSunset_Pal));
        CreateTreeSprites();
        break;
    case SCENE_CITY_NIGHT:
        LoadPalette(&sGrassNight_Pal, BG_PLTT_ID(15), sizeof(sGrassNight_Pal));
        LZ77UnCompVram(sHouses_Gfx, (void *)(VRAM));
        LZ77UnCompVram(sHouses_Tilemap, (void *)(BG_SCREEN_ADDR(6)));
        LoadPalette(&sHouses_Pal, BG_PLTT_ID(0), sizeof(sHouses_Pal));
        LoadCompressedSpriteSheet(sSpriteSheet_HouseSilhouette);
        LoadPalette(&sHouseSilhouette_Pal, OBJ_PLTT_ID(0), sizeof(sHouseSilhouette_Pal));
        CreateHouseSprites();
        break;
    }
    gReservedSpritePaletteCount = 8;
    gIntroCredits_MovingSceneryState = INTROCRED_SCENERY_NORMAL;
}

void SetCreditsSceneBgCnt(u8 scene)
{
    SetGpuReg(REG_OFFSET_BG3CNT, BGCNT_PRIORITY(3)
                               | BGCNT_CHARBASE(0)
                               | BGCNT_16COLOR
                               | BGCNT_SCREENBASE(6)
                               | BGCNT_TXT256x256);
    SetGpuReg(REG_OFFSET_BG2CNT, BGCNT_PRIORITY(2)
                               | BGCNT_CHARBASE(0)
                               | BGCNT_16COLOR
                               | BGCNT_SCREENBASE(7)
                               | BGCNT_TXT256x256);
    SetGpuReg(REG_OFFSET_BG1CNT, BGCNT_PRIORITY(1)
                               | BGCNT_CHARBASE(1)
                               | BGCNT_16COLOR
                               | BGCNT_SCREENBASE(15)
                               | BGCNT_TXT256x256);
    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_MODE_0
                                | DISPCNT_OBJ_1D_MAP
                                | DISPCNT_BG_ALL_ON
                                | DISPCNT_OBJ_ON);
}

#define tMode     data[0] // Can be 0-2, but only != 0 matters. Determines vertical movement for bg2
#define tBg1Speed data[1]
#define tBg1PosHi data[2]
#define tBg1PosLo data[3]
#define tBg2Speed data[4]
#define tBg2PosHi data[5]
#define tBg2PosLo data[6]
#define tBg3Speed data[7]
#define tBg3PosHi data[8]
#define tBg3PosLo data[9]

// Create task that manages the moving backgrounds during the bicycle ride
u8 CreateBicycleBgAnimationTask(u8 mode, u16 bg1Speed, u16 bg2Speed, u16 bg3Speed)
{
    u8 taskId = CreateTask(Task_BicycleBgAnimation, 0);

    gTasks[taskId].tMode = mode;
    gTasks[taskId].tBg1Speed = bg1Speed;
    gTasks[taskId].tBg1PosHi = 0;
    gTasks[taskId].tBg1PosLo = 0;
    gTasks[taskId].tBg2Speed = bg2Speed;
    gTasks[taskId].tBg2PosHi = 0;
    gTasks[taskId].tBg2PosLo = 0;
    gTasks[taskId].tBg3Speed = bg3Speed;
    gTasks[taskId].tBg3PosHi = 8;
    gTasks[taskId].tBg3PosLo = 0;
    Task_BicycleBgAnimation(taskId);
    return taskId;
}

static void Task_BicycleBgAnimation(u8 taskId)
{
    s16 bg1Speed;
    s16 bg2Speed;
    s16 bg3Speed;
    s32 offset;

    // Move BG1
    bg1Speed = gTasks[taskId].tBg1Speed;
    if (bg1Speed != 0)
    {
        offset = (gTasks[taskId].tBg1PosHi << 16) + (u16)gTasks[taskId].tBg1PosLo;
        offset -= (u16)bg1Speed << 4;
        gTasks[taskId].tBg1PosHi = offset >> 16;
        gTasks[taskId].tBg1PosLo = offset;
        SetGpuReg(REG_OFFSET_BG1HOFS, gTasks[taskId].tBg1PosHi);
        SetGpuReg(REG_OFFSET_BG1VOFS, gIntroCredits_MovingSceneryVBase + gIntroCredits_MovingSceneryVOffset);
    }

    // Move BG2
    bg2Speed = gTasks[taskId].tBg2Speed;
    if (bg2Speed != 0)
    {
        offset = (gTasks[taskId].tBg2PosHi << 16) + (u16)gTasks[taskId].tBg2PosLo;
        offset -= (u16)bg2Speed << 4;
        gTasks[taskId].tBg2PosHi = offset >> 16;
        gTasks[taskId].tBg2PosLo = offset;
        SetGpuReg(REG_OFFSET_BG2HOFS, gTasks[taskId].tBg2PosHi);
        if (gTasks[taskId].tMode != 0)
            SetGpuReg(REG_OFFSET_BG2VOFS, gIntroCredits_MovingSceneryVBase + gIntroCredits_MovingSceneryVOffset);
        else
            SetGpuReg(REG_OFFSET_BG2VOFS, gIntroCredits_MovingSceneryVBase);
    }

    // Move BG3
    bg3Speed = gTasks[taskId].tBg3Speed;
    if (bg3Speed != 0)
    {
        offset = (gTasks[taskId].tBg3PosHi << 16) + (u16)gTasks[taskId].tBg3PosLo;
        offset -= (u16)bg3Speed << 4;
        gTasks[taskId].tBg3PosHi = offset >> 16;
        gTasks[taskId].tBg3PosLo = offset;
        SetGpuReg(REG_OFFSET_BG3HOFS, gTasks[taskId].tBg3PosHi);
        SetGpuReg(REG_OFFSET_BG3VOFS, gIntroCredits_MovingSceneryVBase);
    }
}

void CycleSceneryPalette(u8 mode)
{
    u16 x;
    u16 y;
    switch (mode)
    {
        case 0:
        default:
            if (gMain.vblankCounter1 & 3 || gPaletteFade.active)
                break;
            if (gMain.vblankCounter1 & 4)
            {
                x = gPlttBufferUnfaded[BG_PLTT_ID(0) + 9];
                y = gPlttBufferUnfaded[BG_PLTT_ID(0) + 10];
            }
            else
            {
                x = gPlttBufferUnfaded[BG_PLTT_ID(0) + 10];
                y = gPlttBufferUnfaded[BG_PLTT_ID(0) + 9];
            }
            LoadPalette(&x, BG_PLTT_ID(0) + 9, sizeof(x));
            LoadPalette(&y, BG_PLTT_ID(0) + 10, sizeof(y));
            break;
        case 2:
            if (gMain.vblankCounter1 & 3 || gPaletteFade.active)
                break;
            if (gMain.vblankCounter1 & 4)
            {
                x = RGB(7, 9, 15);
                y = RGB(21, 20, 0);
            }
            else
            {
                x = RGB(28, 24, 0);
                y = RGB(7, 9, 15);
            }
            LoadPalette(&x, BG_PLTT_ID(0) + 12, sizeof(x));
            LoadPalette(&y, BG_PLTT_ID(0) + 13, sizeof(y));
            break;
        case 1:
            break;
    }
}

#define tHasVerticalMove data[0]
#define tXOffset         data[1]
#define tXPos            data[2]

static void SpriteCB_MovingScenery(struct Sprite *sprite)
{
    s32 x;
    s16 state = gIntroCredits_MovingSceneryState;

    if (state != INTROCRED_SCENERY_FROZEN)
    {
        switch (state)
        {
        default: // INTROCRED_SCENERY_DESTROY
            DestroySprite(sprite);
            break;
        case INTROCRED_SCENERY_NORMAL:
            x = ((sprite->x << 16) | (u16)sprite->tXPos) + (u16)sprite->tXOffset;
            sprite->x = x >> 16;
            sprite->tXPos = x;
            if (sprite->x > 255)
                sprite->x = -32;
            if (sprite->tHasVerticalMove)
                sprite->y2 = -(gIntroCredits_MovingSceneryVBase + gIntroCredits_MovingSceneryVOffset);
            else
                sprite->y2 = -gIntroCredits_MovingSceneryVBase;
            break;
        }
    }
}

static void CreateMovingScenerySprites(bool8 hasVerticalMove, const struct IntroCreditsSpriteMetadata *metadata, const union AnimCmd *const *anims, u8 numSprites)
{
    u8 i;

    for(i = 0; i < numSprites; i++)
    {
        u8 sprite = CreateSprite(&sSpriteTemplate_MovingScenery, metadata[i].x, metadata[i].y, metadata[i].subpriority);
        CalcCenterToCornerVec(&gSprites[sprite], metadata[i].shape, metadata[i].size, ST_OAM_AFFINE_OFF);
        gSprites[sprite].oam.priority = 3;
        gSprites[sprite].oam.shape = metadata[i].shape;
        gSprites[sprite].oam.size = metadata[i].size;
        gSprites[sprite].oam.paletteNum = 0;
        gSprites[sprite].anims = anims;
        StartSpriteAnim(&gSprites[sprite], metadata[i].animNum);
        gSprites[sprite].tHasVerticalMove = hasVerticalMove;
        gSprites[sprite].tXOffset = metadata[i].xOff;
        gSprites[sprite].tXPos = 0;
    }
}

#undef tHasVerticalMove
#undef tXOffset
#undef tXPos

static void CreateCloudSprites(void)
{
    CreateMovingScenerySprites(FALSE, sSpriteMetadata_Clouds, sAnims_Clouds, 9);
}

static void CreateTreeSprites(void)
{
    CreateMovingScenerySprites(TRUE, sSpriteMetadata_Trees, sAnims_Trees, 12);
}

static void CreateHouseSprites(void)
{
    CreateMovingScenerySprites(TRUE, sSpriteMetadata_HouseSilhouette, sAnims_HouseSilhouette, 6);
}

static void SpriteCB_Player(struct Sprite *sprite)
{
}

#define sPlayerSpriteId data[0]

static void SpriteCB_Bicycle(struct Sprite *sprite)
{
    sprite->invisible = gSprites[sprite->sPlayerSpriteId].invisible;
    sprite->x = gSprites[sprite->sPlayerSpriteId].x;
    sprite->y = gSprites[sprite->sPlayerSpriteId].y + 8;
    sprite->x2 = gSprites[sprite->sPlayerSpriteId].x2;
    sprite->y2 = gSprites[sprite->sPlayerSpriteId].y2;
}

u8 CreateIntroBrendanSprite(s16 x, s16 y)
{
    u8 playerSpriteId = CreateSprite(&sSpriteTemplate_Brendan, x, y, 2);
    u8 bicycleSpriteId = CreateSprite(&sSpriteTemplate_BrendanBicycle, x, y + 8, 3);
    gSprites[bicycleSpriteId].sPlayerSpriteId = playerSpriteId;
    return playerSpriteId;
}

u8 CreateIntroMaySprite(s16 x, s16 y)
{
    u8 playerSpriteId = CreateSprite(&sSpriteTemplate_May, x, y, 2);
    u8 bicycleSpriteId = CreateSprite(&sSpriteTemplate_MayBicycle, x, y + 8, 3);
    gSprites[bicycleSpriteId].sPlayerSpriteId = playerSpriteId;
    return playerSpriteId;
}

#undef sPlayerSpriteId

static void SpriteCB_FlygonLeftHalf(struct Sprite *sprite)
{
}

#define sLeftSpriteId data[0]

static void SpriteCB_FlygonRightHalf(struct Sprite *sprite)
{
    sprite->invisible = gSprites[sprite->sLeftSpriteId].invisible;
    sprite->y = gSprites[sprite->sLeftSpriteId].y;
    sprite->x2 = gSprites[sprite->sLeftSpriteId].x2;
    sprite->y2 = gSprites[sprite->sLeftSpriteId].y2;
}

// In RS these were for Latios/Latias. In Emerald both are replaced with Flygon and now only 1 is used
static u8 UNUSED CreateIntroFlygonSprite_Unused(s16 x, s16 y)
{
    u8 leftSpriteId = CreateSprite(&sSpriteTemplate_FlygonLatios, x - 32, y, 5);
    u8 rightSpriteId = CreateSprite(&sSpriteTemplate_FlygonLatios, x + 32, y, 6);
    gSprites[rightSpriteId].sLeftSpriteId = leftSpriteId;
    StartSpriteAnim(&gSprites[rightSpriteId], 1);
    gSprites[rightSpriteId].callback = &SpriteCB_FlygonRightHalf;
    return leftSpriteId;
}


u8 CreateIntroFlygonSprite(s16 x, s16 y)
{
    u8 leftSpriteId = CreateSprite(&sSpriteTemplate_FlygonLatias, x - 32, y, 5);
    u8 rightSpriteId = CreateSprite(&sSpriteTemplate_FlygonLatias, x + 32, y, 6);
    gSprites[rightSpriteId].sLeftSpriteId = leftSpriteId;
    StartSpriteAnim(&gSprites[rightSpriteId], 1);
    gSprites[rightSpriteId].callback = &SpriteCB_FlygonRightHalf;
    return leftSpriteId;
}

#undef sLeftSpriteId
