#include "global.h"
#include "sprite.h"
#include "decompress.h"
#include "battle_transition_frontier.h"
#include "battle_transition.h"
#include "task.h"
#include "palette.h"
#include "trig.h"
#include "bg.h"
#include "gpu_regs.h"
#include "constants/rgb.h"

/*
    There are 3 "categories" of Battle Frontier transition
    1. The full logo is used (B_TRANSITION_FRONTIER_LOGO_*)
    2. Small squares with the logo on it are used (B_TRANSITION_FRONTIER_SQUARES_*)
    3. The balls that make up the logo come together to form the full logo (B_TRANSITION_FRONTIER_CIRCLES_*)

    This file handles category 3. Functions for the other two are handled in battle_transition.c
*/

typedef bool8 (*TransitionStateFunc)(struct Task *task);

static void SpriteCB_LogoCircleSlide(struct Sprite *sprite);
static void SpriteCB_LogoCircleSpiral(struct Sprite *sprite);
static bool8 WaitForLogoCirclesAnim(struct Task *task);
static bool8 FadeInCenterLogoCircle(struct Task *task);
static bool8 Circles_Init(struct Task *task);
static bool8 CirclesMeet_CreateSprites(struct Task *task);
static bool8 CirclesMeet_End(struct Task *task);
static bool8 CirclesCross_CreateSprites(struct Task *task);
static bool8 CirclesCross_End(struct Task *task);
static bool8 CirclesAsymmetricSpiral_CreateSprites(struct Task *task);
static bool8 CirclesAsymmetricSpiral_End(struct Task *task);
static bool8 CirclesSymmetricSpiral_CreateSprites(struct Task *task);
static bool8 CirclesSymmetricSpiral_End(struct Task *task);
static bool8 CirclesMeetInSeq_CreateSprites(struct Task *task);
static bool8 CirclesMeetInSeq_End(struct Task *task);
static bool8 CirclesCrossInSeq_CreateSprites(struct Task *task);
static bool8 CirclesCrossInSeq_End(struct Task *task);
static bool8 CirclesAsymmetricSpiralInSeq_CreateSprites(struct Task *task);
static bool8 CirclesAsymmetricSpiralInSeq_End(struct Task *task);
static bool8 CirclesSymmetricSpiralInSeq_CreateSprites(struct Task *task);
static bool8 CirclesSymmetricSpiralInSeq_End(struct Task *task);

#define PALTAG_LOGO_CIRCLES 0x2E90

static const u32 sLogoCenter_Gfx[] = INCBIN_U32("graphics/battle_transitions/frontier_logo_center.4bpp.lz");
static const u32 sLogoCenter_Tilemap[] = INCBIN_U32("graphics/battle_transitions/frontier_logo_center.bin");
static const u32 sLogoCircles_Gfx[] = INCBIN_U32("graphics/battle_transitions/frontier_logo_circles.4bpp.lz");
static const u16 sLogo_Pal[] = INCBIN_U16("graphics/battle_transitions/frontier_logo_circles.gbapal");

// Unused Empty data.
static const u8 sFiller[0x1C0] = {0};

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

static const struct CompressedSpriteSheet sSpriteSheet_LogoCircles =
{
    .data = sLogoCircles_Gfx,
    .size = 0x1800,
    .tag = PALTAG_LOGO_CIRCLES
};

static const struct SpritePalette sSpritePalette_LogoCircles =
{
    .data = sLogo_Pal,
    .tag = PALTAG_LOGO_CIRCLES
};

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

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

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

static const union AnimCmd *const sAnimTable_LogoCircles[] =
{
    sAnim_LogoCircle_Top,
    sAnim_LogoCircle_Left,
    sAnim_LogoCircle_Right
};

static const struct SpriteTemplate sSpriteTemplate_LogoCircles =
{
    .tileTag = PALTAG_LOGO_CIRCLES,
    .paletteTag = PALTAG_LOGO_CIRCLES,
    .oam = &sOamData_LogoCircles,
    .anims = sAnimTable_LogoCircles,
    .images = NULL,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy,
};

static const TransitionStateFunc sFrontierCirclesMeet_Funcs[] =
{
    Circles_Init,
    CirclesMeet_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesMeet_End
};

static const TransitionStateFunc sFrontierCirclesCross_Funcs[] =
{
    Circles_Init,
    CirclesCross_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesCross_End
};

static const TransitionStateFunc sFrontierCirclesAsymmetricSpiral_Funcs[] =
{
    Circles_Init,
    CirclesAsymmetricSpiral_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesAsymmetricSpiral_End
};

static const TransitionStateFunc sFrontierCirclesSymmetricSpiral_Funcs[] =
{
    Circles_Init,
    CirclesSymmetricSpiral_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesSymmetricSpiral_End
};

static const TransitionStateFunc sFrontierCirclesMeetInSeq_Funcs[] =
{
    Circles_Init,
    CirclesMeetInSeq_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesMeetInSeq_End
};

static const TransitionStateFunc sFrontierCirclesCrossInSeq_Funcs[] =
{
    Circles_Init,
    CirclesCrossInSeq_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesCrossInSeq_End
};

static const TransitionStateFunc sFrontierCirclesAsymmetricSpiralInSeq_Funcs[] =
{
    Circles_Init,
    CirclesAsymmetricSpiralInSeq_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesAsymmetricSpiralInSeq_End
};

static const TransitionStateFunc sFrontierCirclesSymmetricSpiralInSeq_Funcs[] =
{
    Circles_Init,
    CirclesSymmetricSpiralInSeq_CreateSprites,
    WaitForLogoCirclesAnim,
    FadeInCenterLogoCircle,
    CirclesSymmetricSpiralInSeq_End
};

// Task data
#define tState           data[0]
#define tTimer           data[1]
#define tBlend           data[2]
#define tFadeTimer       data[3]
#define tCircle1SpriteId data[4]
#define tCircle2SpriteId data[5]
#define tCircle3SpriteId data[6]

#define sTargetX data[0]
#define sTargetY data[1]

// Sprite data for CreateSlidingLogoCircleSprite
#define sSpeedX  data[2]
#define sSpeedY  data[3]
#define sTimerX  data[4]
#define sTimerY  data[5]
#define sDelayX  data[6]
#define sDelayY  data[7]

// Sprite data for CreateSpiralingLogoCircleSprite
#define sAngle        data[2]
#define sRotateSpeed  data[3]
#define sRadius       data[4]
#define sTargetRadius data[5]
#define sRadiusDelta  data[6]


static void LoadLogoGfx(void)
{
    u16 *tilemap, *tileset;

    GetBg0TilesDst(&tilemap, &tileset);
    LZ77UnCompVram(sLogoCenter_Gfx, tileset);
    LZ77UnCompVram(sLogoCenter_Tilemap, tilemap);
    LoadPalette(sLogo_Pal, 0xF0, sizeof(sLogo_Pal));
    LoadCompressedSpriteSheet(&sSpriteSheet_LogoCircles);
    LoadSpritePalette(&sSpritePalette_LogoCircles);
}

static u8 CreateSlidingLogoCircleSprite(s16 x, s16 y, u8 delayX, u8 delayY, s8 speedX, s8 speedY, u8 spriteAnimNum)
{
    u8 spriteId = CreateSprite(&sSpriteTemplate_LogoCircles, x, y, 0);

    switch (spriteAnimNum)
    {
    case 0:
        gSprites[spriteId].sTargetX = 120;
        gSprites[spriteId].sTargetY = 45;
        break;
    case 1:
        gSprites[spriteId].sTargetX = 89;
        gSprites[spriteId].sTargetY = 97;
        break;
    case 2:
        gSprites[spriteId].sTargetX = 151;
        gSprites[spriteId].sTargetY = 97;
        break;
    }

    gSprites[spriteId].sSpeedX = speedX;
    gSprites[spriteId].sSpeedY = speedY;
    gSprites[spriteId].sDelayX = delayX;
    gSprites[spriteId].sDelayY = delayY;
    gSprites[spriteId].sTimerX = 0;
    gSprites[spriteId].sTimerY = 0;

    StartSpriteAnim(&gSprites[spriteId], spriteAnimNum);
    gSprites[spriteId].callback = SpriteCB_LogoCircleSlide;

    return spriteId;
}

static void SpriteCB_LogoCircleSlide(struct Sprite *sprite)
{
    s16 *data = sprite->data;

    if (sprite->x == sTargetX && sprite->y == sTargetY)
    {
        sprite->callback = SpriteCallbackDummy;
    }
    else
    {
        if (sTimerX == sDelayX)
        {
            sprite->x += sSpeedX;
            sTimerX = 0;
        }
        else
        {
            sTimerX++;
        }

        if (sTimerY == sDelayY)
        {
            sprite->y += sSpeedY;
            sTimerY = 0;
        }
        else
        {
            sTimerY++;
        }
    }
}

static u8 CreateSpiralingLogoCircleSprite(s16 x, s16 y, s16 angle, s16 rotateSpeed, s16 radiusStart, s16 radiusEnd, s16 radiusDelta, u8 spriteAnimNum)
{
    u8 spriteId = CreateSprite(&sSpriteTemplate_LogoCircles, x, y, 0);

    // Target coords are set but irrelevant
    switch (spriteAnimNum)
    {
    case 0:
        gSprites[spriteId].sTargetX = 120;
        gSprites[spriteId].sTargetY = 45;
        break;
    case 1:
        gSprites[spriteId].sTargetX = 89;
        gSprites[spriteId].sTargetY = 97;
        break;
    case 2:
        gSprites[spriteId].sTargetX = 151;
        gSprites[spriteId].sTargetY = 97;
        break;
    }

    gSprites[spriteId].sAngle = angle;
    gSprites[spriteId].sRotateSpeed = rotateSpeed;
    gSprites[spriteId].sRadius = radiusStart;
    gSprites[spriteId].sTargetRadius = radiusEnd;
    gSprites[spriteId].sRadiusDelta = radiusDelta;

    StartSpriteAnim(&gSprites[spriteId], spriteAnimNum);
    gSprites[spriteId].callback = SpriteCB_LogoCircleSpiral;

    return spriteId;
}

static void SpriteCB_LogoCircleSpiral(struct Sprite *sprite)
{
    sprite->x2 = (Sin2(sprite->sAngle) * sprite->sRadius) >> 12; // div by 4096
    sprite->y2 = (Cos2(sprite->sAngle) * sprite->sRadius) >> 12; // div by 4096

    sprite->sAngle = (sprite->sAngle + sprite->sRotateSpeed) % 360;

    if (sprite->sRadius != sprite->sTargetRadius)
        sprite->sRadius += sprite->sRadiusDelta;
    else
        sprite->callback = SpriteCallbackDummy;
}

static void DestroyLogoCirclesGfx(struct Task *task)
{
    FreeSpriteTilesByTag(PALTAG_LOGO_CIRCLES);
    FreeSpritePaletteByTag(PALTAG_LOGO_CIRCLES);

    DestroySprite(&gSprites[task->tCircle1SpriteId]);
    DestroySprite(&gSprites[task->tCircle2SpriteId]);
    DestroySprite(&gSprites[task->tCircle3SpriteId]);
}

static bool8 IsLogoCirclesAnimFinished(struct Task *task)
{
    if (gSprites[task->tCircle1SpriteId].callback == SpriteCallbackDummy
     && gSprites[task->tCircle2SpriteId].callback == SpriteCallbackDummy
     && gSprites[task->tCircle3SpriteId].callback == SpriteCallbackDummy)
        return TRUE;
    else
        return FALSE;
}

static bool8 Circles_Init(struct Task *task)
{
    if (task->tTimer == 0)
    {
        ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN0_ON);
        ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_WIN1_ON);
        ClearGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG0_ON);

        task->tTimer++;
        return FALSE;
    }
    else
    {
        LoadLogoGfx();
        SetGpuReg(REG_OFFSET_BLDCNT, BLDCNT_TGT1_BG0 | BLDCNT_EFFECT_BLEND | BLDCNT_TGT2_ALL);
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(0, 16));
        ChangeBgX(0, 0, BG_COORD_SET);
        ChangeBgY(0, 0, BG_COORD_SET);
        ChangeBgY(0, 0x500, BG_COORD_SUB);

        task->tTimer = 0;
        task->tState++;
        return TRUE;
    }
}

static bool8 FadeInCenterLogoCircle(struct Task *task)
{
    if (task->tBlend == 0)
        SetGpuRegBits(REG_OFFSET_DISPCNT, DISPCNT_BG0_ON);

    if (task->tBlend == 16)
    {
        if (task->tFadeTimer == 31)
        {
            BeginNormalPaletteFade(PALETTES_ALL, -1, 0, 0x10, RGB_BLACK);
            task->tState++;
        }
        else
        {
            task->tFadeTimer++;
        }
    }
    else
    {
        u16 blnd;

        task->tBlend++;
        blnd = task->tBlend;
        SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(blnd, 16 - blnd));
    }

    return FALSE;
}

static bool8 WaitForLogoCirclesAnim(struct Task *task)
{
    if (IsLogoCirclesAnimFinished(task) == TRUE)
        task->tState++;

    return FALSE;
}

void Task_FrontierCirclesMeet(u8 taskId)
{
    while (sFrontierCirclesMeet_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesMeet_CreateSprites(struct Task *task)
{
    task->tCircle1SpriteId = CreateSlidingLogoCircleSprite(120, -51, 0, 0, 0,   2, 0);
    task->tCircle2SpriteId = CreateSlidingLogoCircleSprite(-7,  193, 0, 0, 2,  -2, 1);
    task->tCircle3SpriteId = CreateSlidingLogoCircleSprite(247, 193, 0, 0, -2, -2, 2);

    task->tState++;
    return FALSE;
}

static bool8 CirclesMeet_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesMeet));
    }

    return FALSE;
}

void Task_FrontierCirclesCross(u8 taskId)
{
    while (sFrontierCirclesCross_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesCross_CreateSprites(struct Task *task)
{
    task->tCircle1SpriteId = CreateSlidingLogoCircleSprite(120, 197, 0, 0, 0, -4, 0);
    task->tCircle2SpriteId = CreateSlidingLogoCircleSprite(241, 59,  0, 1, -4, 2, 1);
    task->tCircle3SpriteId = CreateSlidingLogoCircleSprite(-1,  59,  0, 1, 4,  2, 2);

    task->tState++;
    return FALSE;
}

static bool8 CirclesCross_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesCross));
    }

    return FALSE;
}

void Task_FrontierCirclesAsymmetricSpiral(u8 taskId)
{
    while (sFrontierCirclesAsymmetricSpiral_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesAsymmetricSpiral_CreateSprites(struct Task *task)
{
    task->tCircle1SpriteId = CreateSpiralingLogoCircleSprite(120, 45, 12,  4, 128, 0, -4, 0);
    task->tCircle2SpriteId = CreateSpiralingLogoCircleSprite(89,  97, 252, 4, 128, 0, -4, 1);
    task->tCircle3SpriteId = CreateSpiralingLogoCircleSprite(151, 97, 132, 4, 128, 0, -4, 2);

    task->tState++;
    return FALSE;
}

static bool8 CirclesAsymmetricSpiral_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesAsymmetricSpiral));
    }

    return FALSE;
}

void Task_FrontierCirclesSymmetricSpiral(u8 taskId)
{
    while (sFrontierCirclesSymmetricSpiral_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesSymmetricSpiral_CreateSprites(struct Task *task)
{
    task->tCircle1SpriteId = CreateSpiralingLogoCircleSprite(120, 80, 284, 8, 131, 35, -3, 0);
    task->tCircle2SpriteId = CreateSpiralingLogoCircleSprite(120, 80, 44,  8, 131, 35, -3, 1);
    task->tCircle3SpriteId = CreateSpiralingLogoCircleSprite(121, 80, 164, 8, 131, 35, -3, 2);

    task->tState++;
    return FALSE;
}

static bool8 CirclesSymmetricSpiral_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesSymmetricSpiral));
    }

    return FALSE;
}

void Task_FrontierCirclesMeetInSeq(u8 taskId)
{
    while (sFrontierCirclesMeetInSeq_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesMeetInSeq_CreateSprites(struct Task *task)
{
    if (task->tTimer == 0)
    {
        task->tCircle1SpriteId = CreateSlidingLogoCircleSprite(120, -51, 0, 0, 0, 4,  0);
    }
    else if (task->tTimer == 16)
    {
        task->tCircle2SpriteId = CreateSlidingLogoCircleSprite(-7,  193, 0, 0, 4, -4, 1);
    }
    else if (task->tTimer == 32)
    {
        task->tCircle3SpriteId = CreateSlidingLogoCircleSprite(247, 193, 0, 0, -4, -4, 2);
        task->tState++;
    }

    task->tTimer++;
    return FALSE;
}

static bool8 CirclesMeetInSeq_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesMeetInSeq));
    }

    return FALSE;
}

void Task_FrontierCirclesCrossInSeq(u8 taskId)
{
    while (sFrontierCirclesCrossInSeq_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesCrossInSeq_CreateSprites(struct Task *task)
{
    if (task->tTimer == 0)
    {
        task->tCircle1SpriteId = CreateSlidingLogoCircleSprite(120, 197, 0, 0, 0, -8,  0);
    }
    else if (task->tTimer == 16)
    {
        task->tCircle2SpriteId = CreateSlidingLogoCircleSprite(241, 78,  0, 0, -8, 1,  1);
    }
    else if (task->tTimer == 32)
    {
        task->tCircle3SpriteId = CreateSlidingLogoCircleSprite(-1,  78,  0, 0, 8,  1,  2);
        task->tState++;
    }

    task->tTimer++;
    return FALSE;
}

static bool8 CirclesCrossInSeq_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesCrossInSeq));
    }

    return FALSE;
}

void Task_FrontierCirclesAsymmetricSpiralInSeq(u8 taskId)
{
    while (sFrontierCirclesAsymmetricSpiralInSeq_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesAsymmetricSpiralInSeq_CreateSprites(struct Task *task)
{
    if (task->tTimer == 0)
    {
        task->tCircle1SpriteId = CreateSpiralingLogoCircleSprite(120, 45, 12,  4, 128, 0, -4, 0);
    }
    else if (task->tTimer == 16)
    {
        task->tCircle2SpriteId = CreateSpiralingLogoCircleSprite(89,  97, 252, 4, 128, 0, -4, 1);
    }
    else if (task->tTimer == 32)
    {
        task->tCircle3SpriteId = CreateSpiralingLogoCircleSprite(151, 97, 132, 4, 128, 0, -4, 2);
        task->tState++;
    }

    task->tTimer++;
    return FALSE;
}

static bool8 CirclesAsymmetricSpiralInSeq_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesAsymmetricSpiralInSeq));
    }

    return FALSE;
}

void Task_FrontierCirclesSymmetricSpiralInSeq(u8 taskId)
{
    while (sFrontierCirclesSymmetricSpiralInSeq_Funcs[gTasks[taskId].tState](&gTasks[taskId]));
}

static bool8 CirclesSymmetricSpiralInSeq_CreateSprites(struct Task *task)
{
    if (task->tTimer == 0)
    {
        task->tCircle1SpriteId = CreateSpiralingLogoCircleSprite(120, 80, 284, 8, 131, 35, -3, 0);
    }
    else if (task->tTimer == 16)
    {
        task->tCircle2SpriteId = CreateSpiralingLogoCircleSprite(120, 80, 44,  8, 131, 35, -3, 1);
    }
    else if (task->tTimer == 32)
    {
        task->tCircle3SpriteId = CreateSpiralingLogoCircleSprite(121, 80, 164, 8, 131, 35, -3, 2);
        task->tState++;
    }

    task->tTimer++;
    return FALSE;
}

static bool8 CirclesSymmetricSpiralInSeq_End(struct Task *task)
{
    if (!gPaletteFade.active)
    {
        DestroyLogoCirclesGfx(task);
        DestroyTask(FindTaskIdByFunc(Task_FrontierCirclesSymmetricSpiralInSeq));
    }

    return FALSE;
}
