#include "global.h"
#include "decompress.h"
#include "link.h"
#include "link_rfu.h"
#include "sound.h"
#include "task.h"
#include "trig.h"
#include "constants/songs.h"

extern const struct OamData gOamData_AffineOff_ObjNormal_32x32;

static void sub_802E6D0(u8 taskId);
static void sub_802E83C(u8 taskId);
static void sub_802E8C8(u8 taskId);
static void sub_802EA50(u8 taskId);
static void sub_802EAB0(u8 taskId);
static void sub_802EB98(u8 taskId);
static void Task_MinigameCountdown(u8 taskId);
static bool32 RunMinigameCountdownDigitsAnim(u8 spriteId);
static bool32 IsStartGraphicAnimRunning(u8 spriteId);
static void Load321StartGfx(u16 tileTag, u16 palTag);
static u8 CreateNumberSprite(u16 tileTag, u16 palTag, s16 x, s16 y, u8 subpriority);
static void CreateStartSprite(u16 tileTag, u16 palTag, s16 x, s16 y, u8 subpriority, s16 *spriteId1, s16 *spriteId2);
static void InitStartGraphic(u8 spriteId1, u8 spriteId2, u8 spriteId3);
static void SpriteCB_Start(struct Sprite *sprite);

static const u16 sSpritePal_321Start_2[] = INCBIN_U16("graphics/link_games/321start_2.gbapal");
static const u32 sSpriteSheet_321Start_2[] = INCBIN_U32("graphics/link_games/321start_2.4bpp.lz");

static const struct CompressedSpriteSheet gUnknown_082FE6C8[] =
{
    {sSpriteSheet_321Start_2, 0xC00, 0x2000},
    {},
};

static const struct SpritePalette gUnknown_082FE6D8[] =
{
    {sSpritePal_321Start_2, 0x2000},
    {},
};

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

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

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

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

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

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

static const union AnimCmd *const sSpriteAnimTable_82FE718[] =
{
    sSpriteAnim_82FE6E8,
    sSpriteAnim_82FE6F0,
    sSpriteAnim_82FE6F8,
    sSpriteAnim_82FE700,
    sSpriteAnim_82FE708,
    sSpriteAnim_82FE710
};

static const struct SpriteTemplate gUnknown_082FE730[] =
{
    {
        .tileTag = 0x2000,
        .paletteTag = 0x2000,
        .oam = &gOamData_AffineOff_ObjNormal_32x32,
        .anims = sSpriteAnimTable_82FE718,
        .images = NULL,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    },
};

static const TaskFunc gUnknown_082FE748[][4] =
{
    {
        sub_802E83C,
        sub_802E8C8,
        sub_802EA50,
        sub_802EAB0
    },
};

static const u16 sSpritePal_321Start[] = INCBIN_U16("graphics/link_games/321start.gbapal");
static const u32 sSpriteSheet_321Start[] = INCBIN_U32("graphics/link_games/321start.4bpp.lz");

// Unused
static u32 sub_802E63C(u8 funcSetId, u8 taskPriority)
{
    u8 taskId = CreateTask(sub_802E6D0, taskPriority);
    struct Task *task = &gTasks[taskId];

    task->data[0] = 1;
    task->data[1] = funcSetId;
    gUnknown_082FE748[funcSetId][0](taskId);
    return taskId;
}

static bool32 sub_802E688(void)
{
    u8 taskId = FindTaskIdByFunc(sub_802E6D0);
    if (taskId == 0xFF)
        return FALSE;

    gTasks[taskId].data[0] = 2;
    return TRUE;
}

static bool32 sub_802E6BC(void)
{
    return FuncIsActiveTask(sub_802E6D0);
}

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

    switch (data[0])
    {
    case 2:
        gUnknown_082FE748[data[1]][2](taskId);
        data[0] = 3;
        break;
    case 3:
        gUnknown_082FE748[data[1]][3](taskId);
        break;
    case 4:
        gUnknown_082FE748[data[1]][1](taskId);
        DestroyTask(taskId);
        break;
    }
}

static void sub_802E75C(u8 taskId, s16 *data)
{
    u8 i;
    struct Sprite *sprite;

    LoadCompressedSpriteSheet(&gUnknown_082FE6C8[data[3]]);
    LoadSpritePalette(&gUnknown_082FE6D8[data[4]]);
    for (i = 0; i < data[8]; i++)
        data[13 + i] = CreateSprite(&gUnknown_082FE730[data[2]], data[9], data[10], data[7]);
    for (i = 0; i < data[8]; i++)
    {
        sprite = &gSprites[data[13 + i]];
        sprite->oam.priority = data[6];
        sprite->invisible = TRUE;
        sprite->data[1] = data[5];
        sprite->data[3] = taskId;
        sprite->data[4] = i;
        sprite->data[5] = data[13];
    }
}

static void sub_802E83C(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    data[2] = 0;
    data[3] = 0;
    data[4] = 0;
    data[5] = 60;
    data[6] = 0;
    data[7] = 0;
    data[8] = 3;
    data[9] = 120;
    data[10] = 88;
    sub_802E75C(taskId, data);

    StartSpriteAnim(&gSprites[data[14]], 4);
    gSprites[data[14]].pos2.x = -32;

    StartSpriteAnim(&gSprites[data[15]], 5);
    gSprites[data[15]].pos2.x = 32;
}

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

    for (i = 0; i < data[8]; i++)
        DestroySprite(&gSprites[data[13 + i]]);
    FreeSpriteTilesByTag(gUnknown_082FE6C8[data[3]].tag);
    FreeSpritePaletteByTag(gUnknown_082FE6D8[data[4]].tag);
}

static void sub_802E938(struct Sprite *sprite)
{
    s16 *data = gTasks[sprite->data[3]].data;

    if (data[11] % data[5] != 0)
        return;
    if (data[11] == data[10])
        return;

    data[10] = data[11];
    switch (sprite->data[2])
    {
    case 0:
        sprite->invisible = FALSE;
    case 1:
    case 2:
        PlaySE(SE_BALL_BOUNCE_1);
        StartSpriteAnim(sprite, sprite->data[2]);
        break;
    case 3:
        PlaySE(SE_PIN);
        StartSpriteAnim(sprite, sprite->data[2]);
        gSprites[data[14]].invisible = FALSE;
        gSprites[data[15]].invisible = FALSE;
        break;
    case 4:
        sprite->invisible = TRUE;
        gSprites[data[14]].invisible = TRUE;
        gSprites[data[15]].invisible = TRUE;
        data[0] = 4;
        return;
    }
    sprite->data[2]++;
}

static void sub_802EA50(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    PlaySE(SE_BALL_BOUNCE_1);
    gSprites[data[13]].callback = sub_802E938;
    gSprites[data[13]].invisible = FALSE;
    gTasks[taskId].data[0] = 3;
}

static void sub_802EAB0(u8 taskId)
{
    u16 packet[6];
    s16 *data = gTasks[taskId].data;

    if (gReceivedRemoteLinkPlayers != 0)
    {
        if (gRecvCmds[0][1] == LINKCMD_0x7FFF)
            data[11] = gRecvCmds[0][2];
        if (GetMultiplayerId() == 0)
        {
            data[12]++;
            memset(packet, 0, sizeof(packet));
            packet[0] = LINKCMD_0x7FFF;
            packet[1] = data[12];
            Rfu_SendPacket(packet);
        }
    }
    else
    {
        data[11]++;
    }
}

#define tState       data[0]
#define tTilesTag    data[2]
#define tPalTag      data[3]
#define tX           data[4]
#define tY           data[5]
#define tSubpriority data[6]
#define tSpriteId1   data[7]
#define tSpriteId2   data[8]
#define tSpriteId3   data[9]

void StartMinigameCountdown(s16 tilesTag, s16 palTag, s16 x, s16 y, u8 subpriority)
{
    u8 taskId = CreateTask(Task_MinigameCountdown, 80);
    gTasks[taskId].tTilesTag = tilesTag;
    gTasks[taskId].tPalTag = palTag;
    gTasks[taskId].tX = x;
    gTasks[taskId].tY = y;
    gTasks[taskId].tSubpriority = subpriority;
}

bool32 IsMinigameCountdownRunning(void)
{
    return FuncIsActiveTask(Task_MinigameCountdown);
}

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

    switch (tState)
    {
    case 0:
        Load321StartGfx(tTilesTag, tPalTag);
        tSpriteId1 = CreateNumberSprite(tTilesTag, tPalTag, tX, tY, tSubpriority);
        CreateStartSprite(tTilesTag, tPalTag, tX, tY, tSubpriority, &tSpriteId2, &tSpriteId3);
        tState++;
        break;
    case 1:
        if (!RunMinigameCountdownDigitsAnim(tSpriteId1))
        {
            InitStartGraphic(tSpriteId1, tSpriteId2, tSpriteId3);
            FreeSpriteOamMatrix(&gSprites[tSpriteId1]);
            DestroySprite(&gSprites[tSpriteId1]);
            tState++;
        }
        break;
    case 2:
        if (!IsStartGraphicAnimRunning(tSpriteId2))
        {
            DestroySprite(&gSprites[tSpriteId2]);
            DestroySprite(&gSprites[tSpriteId3]);
            FreeSpriteTilesByTag(tTilesTag);
            FreeSpritePaletteByTag(tPalTag);
            DestroyTask(taskId);
        }
        break;
    }
}

static bool32 RunMinigameCountdownDigitsAnim(u8 spriteId)
{
    struct Sprite *sprite = &gSprites[spriteId];

    switch (sprite->data[0])
    {
    case 0:
        sub_8007E18(sprite, 0x800, 0x1A);
        sprite->data[0]++;
        // fallthrough
    case 1:
        if (sprite->data[2] == 0)
            PlaySE(SE_BALL_BOUNCE_2);
        if (++sprite->data[2] >= 20)
        {
            sprite->data[2] = 0;
            StartSpriteAffineAnim(sprite, 1);
            sprite->data[0]++;
        }
        break;
    case 2:
        if (sprite->affineAnimEnded)
            sprite->data[0]++;
        break;
    case 3:
        if (++sprite->data[2] >= 4)
        {
            sprite->data[2] = 0;
            sprite->data[0]++;
            StartSpriteAffineAnim(sprite, 2);
        }
        break;
    case 4:
        sprite->pos1.y -= 4;
        if (++sprite->data[2] >= 8)
        {
            if (sprite->data[4] <= 1)
            {
                StartSpriteAnim(sprite, sprite->data[4] + 1);
                sprite->data[2] = 0;
                sprite->data[0]++;
            }
            else
            {
                sprite->data[0] = 7;
                return FALSE;
            }
        }
        break;
    case 5:
        sprite->pos1.y += 4;
        if (++sprite->data[2] >= 8)
        {
            sprite->data[2] = 0;
            StartSpriteAffineAnim(sprite, 3);
            sprite->data[0]++;
        }
        break;
    case 6:
        if (sprite->affineAnimEnded)
        {
            sprite->data[4]++;
            sprite->data[0] = 1;
        }
        break;
    case 7:
        return FALSE;
    }

    return TRUE;
}

// First argument is unused.
static void InitStartGraphic(u8 spriteId1, u8 spriteId2, u8 spriteId3)
{
    gSprites[spriteId2].pos2.y = -40;
    gSprites[spriteId3].pos2.y = -40;
    gSprites[spriteId2].invisible = FALSE;
    gSprites[spriteId3].invisible = FALSE;
    gSprites[spriteId2].callback = SpriteCB_Start;
    gSprites[spriteId3].callback = SpriteCB_Start;
}

static bool32 IsStartGraphicAnimRunning(u8 spriteId)
{
    return gSprites[spriteId].callback == SpriteCB_Start;
}

static void SpriteCB_Start(struct Sprite *sprite)
{
    int y;
    s16 *data = sprite->data;

    switch (data[0])
    {
    case 0:
        data[4] = 64;
        data[5] = sprite->pos2.y << 4;
        data[0]++;
    case 1:
        data[5] += data[4];
        data[4]++;
        sprite->pos2.y = data[5] >> 4;
        if (sprite->pos2.y >= 0)
        {
            PlaySE(SE_BALL_BOUNCE_2);
            sprite->pos2.y = 0;
            data[0]++;
        }
        break;
    case 2:
        data[1] += 12;
        if (data[1] >= 128)
        {
            PlaySE(SE_BALL_BOUNCE_2);
            data[1] = 0;
            data[0]++;
        }
        y = gSineTable[data[1]];
        sprite->pos2.y = -(y >> 4);
        break;
    case 3:
        data[1] += 16;
        if (data[1] >= 128)
        {
            PlaySE(SE_BALL_BOUNCE_2);
            data[1] = 0;
            data[0]++;
        }
        sprite->pos2.y = -(gSineTable[data[1]] >> 5);
        break;
    case 4:
        if (++data[1] > 40)
            sprite->callback = SpriteCallbackDummy;
        break;
    }
}

static void Load321StartGfx(u16 tileTag, u16 palTag)
{
    struct CompressedSpriteSheet spriteSheet = {sSpriteSheet_321Start, 0xE00, 0};
    struct SpritePalette spritePalette = {sSpritePal_321Start, 0};

    spriteSheet.tag = tileTag;
    spritePalette.tag = palTag;

    LoadCompressedSpriteSheet(&spriteSheet);
    LoadSpritePalette(&spritePalette);
}

static const struct OamData sOamData_Numbers =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_DOUBLE,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = 0,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(32x32),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(32x32),
    .tileNum = 0,
    .priority = 0,
    .paletteNum = 0,
    .affineParam = 0
};

static const struct OamData sOamData_Start =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = 0,
    .bpp = ST_OAM_4BPP,
    .shape = SPRITE_SHAPE(64x32),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(64x32),
    .tileNum = 0,
    .priority = 0,
    .paletteNum = 0,
    .affineParam = 0
};

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

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

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

static const union AnimCmd *const sAnimTable_Numbers[] =
{
    sAnim_Numbers_Three,
    sAnim_Numbers_Two,
    sAnim_Numbers_One
};

static const union AnimCmd sAnim_StartLeft[] =
{
    ANIMCMD_FRAME(48, 1),
    ANIMCMD_END
};

static const union AnimCmd sAnim_StartRight[] =
{
    ANIMCMD_FRAME(80, 1),
    ANIMCMD_END
};

static const union AnimCmd *const sAnimTable_Start[] =
{
    sAnim_StartLeft,
    sAnim_StartRight
};

static const union AffineAnimCmd sAffineAnim_Numbers_0[] =
{
    AFFINEANIMCMD_FRAME(256, 256, 0, 0),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sAffineAnim_Numbers_1[] =
{
    AFFINEANIMCMD_FRAME(256, 256, 0, 0),
    AFFINEANIMCMD_FRAME(16, -16, 0, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sAffineAnim_Numbers_2[] =
{
    AFFINEANIMCMD_FRAME(-18, 18, 0, 8),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd sAffineAnim_Numbers_3[] =
{
    AFFINEANIMCMD_FRAME(  6,  -6, 0, 8),
    AFFINEANIMCMD_FRAME( -4,   4, 0, 8),
    AFFINEANIMCMD_FRAME(256, 256, 0, 0),
    AFFINEANIMCMD_END
};

static const union AffineAnimCmd *const sAffineAnimTable_Numbers[] =
{
    sAffineAnim_Numbers_0,
    sAffineAnim_Numbers_1,
    sAffineAnim_Numbers_2,
    sAffineAnim_Numbers_3
};

static u8 CreateNumberSprite(u16 tileTag, u16 palTag, s16 x, s16 y, u8 subpriority)
{
    struct SpriteTemplate spriteTemplate =
    {
        .oam = &sOamData_Numbers,
        .anims = sAnimTable_Numbers,
        .affineAnims = sAffineAnimTable_Numbers,
        .callback = SpriteCallbackDummy,
    };

    spriteTemplate.tileTag = tileTag;
    spriteTemplate.paletteTag = palTag;
    return CreateSprite(&spriteTemplate, x, y, subpriority);
}

static void CreateStartSprite(u16 tileTag, u16 palTag, s16 x, s16 y, u8 subpriority, s16 *spriteId1, s16 *spriteId2)
{
    struct SpriteTemplate spriteTemplate =
    {
        .oam = &sOamData_Start,
        .anims = sAnimTable_Start,
        .affineAnims = gDummySpriteAffineAnimTable,
        .callback = SpriteCallbackDummy,
    };

    spriteTemplate.tileTag = tileTag;
    spriteTemplate.paletteTag = palTag;
    *spriteId1 = CreateSprite(&spriteTemplate, x - 32, y, subpriority);
    *spriteId2 = CreateSprite(&spriteTemplate, x + 32, y, subpriority);

    gSprites[*spriteId1].invisible = TRUE;
    gSprites[*spriteId2].invisible = TRUE;
    StartSpriteAnim(&gSprites[*spriteId2], 1);
}
