/**
 * @file title.c
 * @ingroup Tasks
 *
 * @brief Title task
 */

#include "sound.h"
#include "main.h"
#include "entity.h"
#include "common.h"
#include "screen.h"
#include "object.h"
#include "message.h"
#include "functions.h"
#include "save.h"
#include "area.h"
#include "item.h"
#include "game.h"

typedef struct {
    u8 filler0[0x4];
    u8 language;
    u8 state;
    u8 subState;
    u8 filler7[0x1];
    u16 timer;
    u8 fillerA[0x6];
    u8 lightRaysPaletteGroup;
    u8 lightRaysAlphaBlendIndex;
    u8 counter;
    u8 filler13[0x19];
    int swordBgScaleRatio;
} IntroState;

// TODO: This occupies the same memory region as gMenu
extern IntroState gIntroState;

enum {
    ADVANCE_NONE,
    ADVANCE_TIMER_EXPIRED,
    ADVANCE_KEY_PRESSED,
};

static void HandleNintendoCapcomLogos(void);
static void HandleTitlescreen(void);
static void UpdateSwordBgAffineData(void);
static void ExitTitlescreen(void);
static void HandleJapaneseTitlescreenAnimationIntro(void);
static void HandleTitlescreenAnimationIntro(void);
static u32 GetAdvanceState(void);
static void UpdateLightRays(void);
static void UpdatePressStartIcon(void);

static void (*const sIntroSequenceHandlers[])(void) = {
    HandleNintendoCapcomLogos,
    HandleTitlescreen,
    ExitTitlescreen,
};

static const u16 sLightRaysAlphaBlends[] = {
    BLDALPHA_BLEND(9, 9),  BLDALPHA_BLEND(8, 10), BLDALPHA_BLEND(7, 11), BLDALPHA_BLEND(6, 12),
    BLDALPHA_BLEND(5, 13), BLDALPHA_BLEND(6, 12), BLDALPHA_BLEND(7, 11), BLDALPHA_BLEND(8, 10),
};

#define FLAG_BYTE(bank, flag) (((bank) + (flag)) >> 3)
#define FLAG_X(flag) (1 << ((flag)&7))

#ifdef DEMO_JP
static const SaveFile gDemoSave = {
    .initialized = 1,
    .msg_speed = 1,
    .brightness = 1,
    .global_progress = 1,
    .field_0x9 = { [23] = 0x1F },
    .windcrests = 0x00013780,
    .unk50 = 7,
    .areaVisitFlags = { 0x0114C300 },
    .name = "\x97\x7f\xdd",
    .saved_status = {
        .area_next = AREA_DEEPWOOD_SHRINE,
        .room_next = 0xb,
        .start_pos_x = 0xa8,
        .start_pos_y = 0xc8,
        .layer = 1,
        .overworld_map_x = 0xd66,
        .overworld_map_y = 0xae0,
    },
    .stats = {
        .health = 40,
        .maxHealth = 40,
        .itemButtons = { ITEM_SHIELD, ITEM_SMITH_SWORD },
        .rupees = 5,
    },
    .fillerD0 = {
        [34] = 5,
        [37] = 4,
        [47] = 6,
        [51] = 0x40,
        [56] = 0x40,
    },
    .flags = {
        [FLAG_BYTE(FLAG_BANK_G, START)] = FLAG_X(START) | FLAG_X(EZERO_1ST) | FLAG_X(TABIDACHI),
        [FLAG_BYTE(FLAG_BANK_G, OUTDOOR)] = FLAG_X(OUTDOOR),
        [FLAG_BYTE(FLAG_BANK_G, ENTRANCE_0)] = FLAG_X(ENTRANCE_0),
        [FLAG_BYTE(FLAG_BANK_1, MORI_00_KOBITO)] = FLAG_X(MORI_00_KOBITO) | FLAG_X(MORI_ENTRANCE_1ST),
        [FLAG_BYTE(FLAG_BANK_1, SOUGEN_01_ZELDA)] = FLAG_X(SOUGEN_01_ZELDA),
        [FLAG_BYTE(FLAG_BANK_1, SOUGEN_06_WAKAGI_1)] = FLAG_X(SOUGEN_06_WAKAGI_1) | FLAG_X(SOUGEN_06_WAKAGI_2) | FLAG_X(SOUGEN_06_WAKAGI_3),
        [FLAG_BYTE(FLAG_BANK_1, SOUGEN_06_AKINDO)] = FLAG_X(SOUGEN_06_AKINDO),
        [FLAG_BYTE(FLAG_BANK_1, CASTLE_04_MEZAME)] = FLAG_X(CASTLE_04_MEZAME),
        [FLAG_BYTE(FLAG_BANK_1, MACHI_01_DEMO)] = FLAG_X(MACHI_01_DEMO),
        [FLAG_BYTE(FLAG_BANK_2, MHOUSE15_OP1ST)] = FLAG_X(MHOUSE15_OP1ST),
        [FLAG_BYTE(FLAG_BANK_2, M_PRIEST_TALK)] = FLAG_X(M_PRIEST_TALK) | FLAG_X(M_ELDER_TALK1ST) | FLAG_X(M_PRIEST_MOVE),
        [FLAG_BYTE(FLAG_BANK_2, KOBITO_MORI_1ST)] = FLAG_X(KOBITO_MORI_1ST),
        [FLAG_BYTE(FLAG_BANK_5, LV1_0B_WALK)] = FLAG_X(LV1_0B_WALK),
    },
};
static const u8 unk[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0,
                          0xc2, 0xd1, 0xc2, 0xd2, 0xc2, 0xd3, 0xc2, 0xd4, 0xc2, 0xd5, 0xc2, 0xd6, 0xc2,
                          0xd7, 0xc2, 0xd8, 0xc2, 0x00, 0xf0, 0x11, 0x90, 0x11, 0x90, 0x05, 0xd3, 0x49,
                          0xd3, 0x26, 0xd3, 0x65, 0xdf, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90,
                          0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11, 0x90, 0x11,
                          0x90, 0x11, 0x90, 0x8a, 0xc2, 0x8b, 0xc2, 0x8c, 0xc2, 0x8d, 0xc2 };
#endif

static u32 AdvanceIntroSequence(u32 transition) {
    gUI.lastState = transition;
    gMain.state = GAMETASK_MAIN;
    MemClear(&gIntroState, sizeof(gIntroState));
    SetFade(7, 8);
}

void TitleTask(void) {
    FlushSprites();
    switch (gMain.state) {
        case 0:
            MessageInitialize();
            MemClear(&gUI, sizeof(gUI));
            AdvanceIntroSequence(0);
            break;
        case 1:
            sIntroSequenceHandlers[gUI.lastState]();
            break;
        case 2:
            if (gFadeControl.active) {
                return;
            }
            DispReset(1);
            gMain.state = GAMETASK_INIT;
            break;
    }
    CopyOAM();
}

static void HandleNintendoCapcomLogos(void) {
    u32 advance;
    u32 paletteGroup;

    advance = GetAdvanceState();
    if (gIntroState.state == 0) {
        DispReset(1);
        gIntroState.state = 1;
        gIntroState.timer = 120;
        LoadGfxGroup(16);
        LoadGfxGroup(1);
        if (gSaveHeader->language == 0) {
            paletteGroup = 1;
        } else {
            paletteGroup = 2;
        }
        LoadPaletteGroup(paletteGroup);
        gScreen.lcd.displayControl |= DISPCNT_BG2_ON;
        gScreen.bg1.updated = 1;
        SetFade(6, 8);
        advance = ADVANCE_NONE;
#if defined(DEMO_USA)
        if (gUnk_02000010.listenForKeyPresses == 0) {
            if ((gInput.heldKeys & 0x204) == 0x204) { // TODO
                gUnk_02000010.field_0x7 = 1;
                SoundReq(SFX_SECRET_BIG);
            } else {
                if ((gInput.heldKeys & 0x104) == 0x104) { // TODO
                    gUnk_02000010.field_0x7 = 2;
                    SoundReq(SFX_TASK_COMPLETE);
                } else {
                    gUnk_02000010.field_0x7 = 0;
                }
            }
        }
#endif

    } else {
        if (advance == ADVANCE_TIMER_EXPIRED) {
            advance = ADVANCE_KEY_PRESSED;
        }
    }

    if (advance == ADVANCE_KEY_PRESSED) {
        gUnk_02000010.listenForKeyPresses = 1;
        AdvanceIntroSequence(1);
    }
}

static void HandleTitlescreen(void) {
    int advance;
    u32 paletteGroup;

    gIntroState.counter++;
    switch (gIntroState.state) {
        case 0:
            gIntroState.state = 1;
            gIntroState.subState = 0;
            gIntroState.timer = 30;
            gIntroState.language = 7;
            EraseAllEntities();
            ResetPaletteTable(0);
            ResetPalettes();
            gGFXSlots.unk0 = 1;
            LoadGfxGroup(2);
            if (gSaveHeader->language == 0) {
                paletteGroup = 3;
            } else {
                paletteGroup = 4;
            }
            LoadPaletteGroup(paletteGroup);
            if (gSaveHeader->language == 0) {
                // Blend first and second layer
                gScreen.controls.layerFXControl = BLDCNT_TGT1_BG2 | BLDCNT_TGT2_BG3 | BLDCNT_EFFECT_BLEND;
                gScreen.controls.alphaBlend = BLDALPHA_BLEND(9, 9);
                gScreen.bg1.control = BGCNT_SCREENBASE(28) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2);
                gScreen.bg2.control = BGCNT_SCREENBASE(29) | BGCNT_PRIORITY(2);
                gScreen.bg3.control = BGCNT_SCREENBASE(30) | BGCNT_PRIORITY(3);
                gScreen.lcd.displayControl |= DISPCNT_BG1_ON | DISPCNT_BG2_ON | DISPCNT_BG3_ON | DISPCNT_OBJ_ON;
                gScreen.bg1.yOffset = -160;
            } else {
                gScreen.controls.layerFXControl = BLDCNT_TGT1_BG0 | BLDCNT_TGT2_BG1 | BLDCNT_EFFECT_BLEND;
                gScreen.controls.alphaBlend = BLDALPHA_BLEND(9, 9);
                gScreen.bg0.control = BGCNT_SCREENBASE(29) | BGCNT_PRIORITY(2);
                gScreen.bg1.control = BGCNT_SCREENBASE(30) | BGCNT_PRIORITY(3);
                gScreen.bg2.control = BGCNT_SCREENBASE(28) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2) | BGCNT_256COLOR |
                                      BGCNT_WRAP | BGCNT_TXT512x256;
                gScreen.lcd.displayControl |= DISPCNT_MODE_1;
                gScreen.lcd.displayControl |= DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_OBJ_ON;
                gIntroState.swordBgScaleRatio = 0x10;
                UpdateSwordBgAffineData();
            }
            InitSoundPlayingInfo();
            SoundReq(BGM_TITLE_SCREEN);
            SetFade(6, 8);
            break;
        case 1:
            if (gFadeControl.active) {
                return;
            }
            if (gSaveHeader->language == 0) {
                HandleJapaneseTitlescreenAnimationIntro();
            } else {
                HandleTitlescreenAnimationIntro();
            }
            break;
        case 2:
#if defined(JP) || defined(DEMO_JP) || defined(EU)
            if (GetAdvanceState()) {
#else
            if (--gIntroState.timer == 0) {
#endif
                gIntroState.timer = 3600;
                gIntroState.state++;
            }
#if defined(USA) || defined(DEMO_USA)
            UpdatePressStartIcon();
#endif
            break;
        default:
            advance = GetAdvanceState();
            if (advance != ADVANCE_NONE) {
                if (advance == ADVANCE_KEY_PRESSED) {
                    SoundReq(SFX_TEXTBOX_SELECT);
                } else {
                    advance = ADVANCE_NONE;
                }
                AdvanceIntroSequence(advance);
                SoundReq(SONG_VOL_FADE_OUT);
            }
#if defined(JP) || defined(DEMO_JP) || defined(DEMO_JP)
            gOamCmd._4 = 0;
            gOamCmd._6 = 0;
            gOamCmd._8 = 0xE020;
            gOamCmd.x = 120;
            gOamCmd.y = 152;
            DrawDirect(511, 1);
#elif defined(EU)
            gOamCmd._4 = 0;
            gOamCmd._6 = 0;
            gOamCmd._8 = 0xE020;
            gOamCmd.x = 120;
            gOamCmd.y = 152;
            DrawDirect(510, 1);
#else
        UpdatePressStartIcon();
#endif
            if ((gIntroState.timer & 0x20) == 0) {
                gOamCmd._8 = 0xe000;
                gOamCmd.y = 0x84;
#ifdef EU
                DrawDirect(0x1fe, 0);
#else
                DrawDirect(0x1ff, 0);
#endif
            }
    }
    if (gIntroState.language != gSaveHeader->language) {
        gIntroState.language = gSaveHeader->language;
        LoadGfxGroup(3);
    }
    UpdateLightRays();
    UpdateEntities();
    DrawEntities();
}

#if defined(USA) || defined(DEMO_USA)
static void UpdatePressStartIcon(void) {
    gOamCmd._4 = 0;
    gOamCmd._6 = 0;
    gOamCmd._8 = 0xE020;
    gOamCmd.x = 120;
    gOamCmd.y = 152;
    DrawDirect(511, 1);
}
#endif

static void UpdateSwordBgAffineData(void) {
    struct BgAffineSrcData aff;
    aff.texY = 0x8000;
    aff.texX = 0x8000;
    aff.scrX = DISPLAY_WIDTH / 2;
    aff.scrY = DISPLAY_HEIGHT / 2 - 8;
    aff.alpha = 0;
    aff.sy = aff.sx = gIntroState.swordBgScaleRatio;
    BgAffineSet(&aff, (struct BgAffineDstData*)&gScreen.controls, 1);
}

static void HandleJapaneseTitlescreenAnimationIntro(void) {
    Entity* pEVar2;

    switch (gIntroState.subState) {
        case 0:
            if (!gFadeControl.active) {
                if ((gIntroState.counter & 1) == 0) {
                    gScreen.bg1.yOffset++;
                }

                if (GetAdvanceState() == ADVANCE_KEY_PRESSED || gScreen.bg1.yOffset == 0) {
                    gIntroState.subState++;
                    gScreen.bg1.yOffset = 0;
                    gScreen.bg1.control = BGCNT_SCREENBASE(12) | BGCNT_PRIORITY(1) | BGCNT_CHARBASE(2);
                    gFadeControl.mask = 0x00000040;
                    SetFade(6, 0x10);
                    SoundReq(SFX_F8);
                }
            }
            break;
        case 1:
            if (!gFadeControl.active) {
                gFadeControl.mask = 0xFFFFFFFF;
                gIntroState.subState++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
                gIntroState.timer = 120;
#else
                gIntroState.timer = 90;
#endif
                pEVar2 = CreateObject(OBJECT_B4, 0, 0);
                if (pEVar2 != NULL) {
                    pEVar2->x.HALF.HI = 0;
                    pEVar2->y.HALF.HI = DISPLAY_HEIGHT / 2 - 8;
                }
            }
            break;
        case 2:
            if (GetAdvanceState() != ADVANCE_NONE) {
                gIntroState.state++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
                gIntroState.timer = 30;
#else
                gIntroState.timer = 60;
#endif
            }
    }
}

static void HandleTitlescreenAnimationIntro(void) {
    switch (gIntroState.subState) {
        case 0:
            if (!gFadeControl.active) {
                gIntroState.subState = 1;
                gScreen.lcd.displayControl |= DISPCNT_BG2_ON;
                SoundReq(SFX_EVAPORATE);
            }
            break;
        case 1:
            gIntroState.swordBgScaleRatio += 0x10;
            if (gIntroState.swordBgScaleRatio > 0x100) {
                gIntroState.swordBgScaleRatio = 0x100;
                gIntroState.timer = 40;
                gIntroState.subState++;
                SetFade(6, 16);
            }
            UpdateSwordBgAffineData();
            break;
        case 2:
            if (--gIntroState.timer == 0) {
#if defined(JP) || defined(EU) || defined(DEMO_JP)
                gIntroState.timer = 360;
#else
                gIntroState.timer = 300;
#endif
                gIntroState.subState++;
                CreateObject(OBJECT_BD, 0, 0);
                SetFade(6, 16);
                SoundReq(SFX_F8);
            }
            break;
        default:
            if (!gFadeControl.active && GetAdvanceState() != ADVANCE_NONE) {
                gIntroState.state++;
#if defined(JP) || defined(EU) || defined(DEMO_JP)
                gIntroState.timer = 30;
#else
                gIntroState.timer = 60;
#endif
            }
            break;
    }
}
static void ExitTitlescreen(void) {
    if (!gFadeControl.active) {
#ifdef DEMO_JP
        MemCopy(&gDemoSave, &gSave, sizeof(gSave));
        SetTask(TASK_GAME);
#else
        SetTask(TASK_FILE_SELECT);
#endif
    }
}

static u32 GetAdvanceState(void) {
    u32 newKeys;

    if (gFadeControl.active) {
        return ADVANCE_NONE;
    }

    if (!gUnk_02000010.listenForKeyPresses) {
        newKeys = 0;
    } else {
        newKeys = gInput.newKeys & (A_BUTTON | START_BUTTON);
    }

    if (--gIntroState.timer == 0) {
        return ADVANCE_TIMER_EXPIRED;
    }

    if (newKeys) {
        return ADVANCE_KEY_PRESSED;
    }

    return ADVANCE_NONE;
}

static void UpdateLightRays(void) {
    // Periodically rotate the palette to give a shimmering effect.
    if ((gIntroState.counter & 0x7) == 0) {
        gIntroState.lightRaysPaletteGroup++;
        gIntroState.lightRaysPaletteGroup &= 0x3;
        LoadPaletteGroup(5 + gIntroState.lightRaysPaletteGroup);
    }

    // Periodically update the transparency of the light rays.
    if ((gIntroState.counter & 0x1F) == 0) {
        gIntroState.lightRaysAlphaBlendIndex = (gIntroState.lightRaysAlphaBlendIndex + 1) & 0x7;
        gScreen.controls.alphaBlend = sLightRaysAlphaBlends[gIntroState.lightRaysAlphaBlendIndex];
    }
}
