#include "global.h"
#include "gba/flash_internal.h"
#include "save_failed_screen.h"
#include "m4a.h"
#include "main.h"
#include "menu.h"
#include "palette.h"
#include "save.h"
#include "sprite.h"
#include "starter_choose.h"
#include "strings.h"
#include "task.h"
#include "text.h"
#include "ewram.h"

// In English 1.0, the text window is too small, causing text to overflow.

#ifdef BUGFIX_SAVEFAILEDSCREEN1
#define MSG_WIN_TOP 10
#else
#define MSG_WIN_TOP 12
#endif

#define CLOCK_WIN_TOP (MSG_WIN_TOP - 4)

static EWRAM_DATA u16 gSaveFailedType = 0;
static EWRAM_DATA u16 gSaveFailedClockInfo[9] = {0};

extern u32 gUnknown_Debug_03004BD0;
extern u32 gDamagedSaveSectors;
extern u32 gGameContinueCallback;

static const struct OamData sClockOamData =
{
    160, // Y
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    1,
    0,
    0,
    0,
    0
};

static const u8 sClockFrames[8][3] =
{
    { 1, 0, 0 },
    { 5, 0, 0 },
    { 9, 0, 0 },
    { 5, 0, 1 },
    { 1, 0, 1 },
    { 5, 1, 1 },
    { 9, 1, 0 },
    { 5, 1, 0 },
};

static const u8 gSaveFailedClockPal[] = INCBIN_U8("graphics/misc/clock_small.gbapal");
static const u8 gSaveFailedClockGfx[] = INCBIN_U8("graphics/misc/clock_small.4bpp.lz");

static void VBlankCB(void);
static void CB2_SaveFailedScreen(void);
static void CB2_WipeSave(void);
static void CB2_GameplayCannotBeContinued(void);
static void CB2_FadeAndReturnToTitleScreen(void);
static void CB2_ReturnToTitleScreen(void);
static void VBlankCB_UpdateClockGraphics(void);
static bool8 IsSectorNonEmpty(u16 sector);
static bool8 WipeSector(u16 sector);
static bool8 WipeSectors(u32 sectorBits);

void DoSaveFailedScreen(u8 saveType)
{
    SetMainCallback2(CB2_SaveFailedScreen);
    gSaveFailedType = saveType;
    gSaveFailedClockInfo[0] = FALSE;
}

static void VBlankCB(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
}

static void CB2_SaveFailedScreen(void)
{
    u16 ime;

    switch (gMain.state)
    {
    case 0:
    default:
        SetVBlankCallback(0);
        REG_DISPCNT = 0;
        REG_BG3CNT = 0;
        REG_BG2CNT = 0;
        REG_BG1CNT = 0;
        REG_BG0CNT = 0;
        REG_BG3HOFS = 0;
        REG_BG3VOFS = 0;
        REG_BG2HOFS = 0;
        REG_BG2VOFS = 0;
        REG_BG1HOFS = 0;
        REG_BG1VOFS = 0;
        REG_BG0HOFS = 0;
        REG_BG0VOFS = 0;
        DmaFill16(3, 0, VRAM, VRAM_SIZE);
        DmaFill32(3, 0, OAM, OAM_SIZE);
        DmaFill16(3, 0, PLTT, PLTT_SIZE);
        LZ77UnCompVram(&gBirchHelpGfx, (void *)VRAM);
        LZ77UnCompVram(&gBirchBagTilemap, (void *)(VRAM + 0x3000));
        LZ77UnCompVram(&gBirchGrassTilemap, (void *)(VRAM + 0x3800));
        LZ77UnCompVram(&gSaveFailedClockGfx, (void *)(VRAM + 0x10020));
        ResetSpriteData();
        ResetTasks();
        ResetPaletteFade();
        LoadPalette(&gBirchBagGrassPal, 0, sizeof(gBirchBagGrassPal));
        LoadPalette(&gSaveFailedClockPal, 0x100, sizeof(gSaveFailedClockPal));
        Text_LoadWindowTemplate(&gWindowTemplate_81E6C3C);
        InitMenuWindow(&gMenuTextWindowTemplate);
        Menu_DrawStdWindowFrame(13, CLOCK_WIN_TOP, 16, CLOCK_WIN_TOP + 3); // clock window
        Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19); // message window
        Menu_PrintText(gSystemText_SaveFailedBackupCheck, 2, MSG_WIN_TOP + 1);
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, RGB(0, 0, 0));
        ime = REG_IME;
        REG_IME = 0;
        REG_IE |= INTR_FLAG_VBLANK;
        REG_IME = ime;
        REG_DISPSTAT |= DISPSTAT_VBLANK_INTR;
        SetVBlankCallback(VBlankCB);
        REG_BG3CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(7)  | BGCNT_16COLOR | BGCNT_TXT256x256;
        REG_BG2CNT = BGCNT_PRIORITY(2) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(6)  | BGCNT_16COLOR | BGCNT_TXT256x256;
        REG_BG0CNT = BGCNT_PRIORITY(0) | BGCNT_CHARBASE(2) | BGCNT_SCREENBASE(31) | BGCNT_16COLOR | BGCNT_TXT256x256;
        REG_DISPCNT = DISPCNT_OBJ_ON | DISPCNT_BG3_ON | DISPCNT_BG2_ON | DISPCNT_BG0_ON | DISPCNT_OBJ_1D_MAP | DISPCNT_MODE_0;
        gMain.state++;
        break;
    case 1:
        if (!UpdatePaletteFade())
        {
            SetMainCallback2(CB2_WipeSave);
            SetVBlankCallback(VBlankCB_UpdateClockGraphics);
        }
        break;
    }
}

static void CB2_WipeSave(void)
{
    u8 wipeTries = 0;

    gSaveFailedClockInfo[0] = TRUE;

#if (DEBUG && !(ENGLISH && REVISION == 0))
	if (gUnknown_Debug_03004BD0 != 0)
		gDamagedSaveSectors = 1;
#endif

    while (gDamagedSaveSectors != 0 && wipeTries < 3) // while there are still attempts left, keep trying to fix the save sectors.
    {
        if (WipeSectors(gDamagedSaveSectors) != FALSE)
        {
            Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);
            Menu_PrintText(gSystemText_BackupDamagedGameContinue, 2, MSG_WIN_TOP + 1);
            SetMainCallback2(CB2_GameplayCannotBeContinued);
            return;
        }

        Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);
        Menu_PrintText(gSystemText_CheckCompleteSaveAttempt, 2, MSG_WIN_TOP + 1);
        Save_WriteDataInternal(gSaveFailedType);

        if (gDamagedSaveSectors != 0)
        {
#ifdef BUGFIX_SAVEFAILEDSCREEN2
            Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);
#endif
            Menu_PrintText(gSystemText_SaveFailedBackupCheck, 2, MSG_WIN_TOP + 1);
        }

        wipeTries++;
    }

    if (wipeTries == 3)
    {
        Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);
        Menu_PrintText(gSystemText_BackupDamagedGameContinue, 2, MSG_WIN_TOP + 1);
        SetMainCallback2(CB2_FadeAndReturnToTitleScreen); // called again below
    }
    else
    {
        Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);

        // no callback exists, so the game cannot continue.
        if (gGameContinueCallback == 0)
            Menu_PrintText(gSystemText_SaveCompletedGameEnd, 2, MSG_WIN_TOP + 1);
        else // callback exists, so continue
            Menu_PrintText(gSystemText_SaveCompletedPressA, 2, MSG_WIN_TOP + 1);
    }

    SetMainCallback2(CB2_FadeAndReturnToTitleScreen);
}

static void CB2_GameplayCannotBeContinued(void)
{
    gSaveFailedClockInfo[0] = FALSE;

    if (gMain.newKeys & A_BUTTON)
    {
        Menu_DrawStdWindowFrame(1, MSG_WIN_TOP, 28, 19);
        Menu_PrintText(gSystemText_GameplayEnded, 2, MSG_WIN_TOP + 1);
        SetVBlankCallback(VBlankCB);
        SetMainCallback2(CB2_FadeAndReturnToTitleScreen);
    }
}

static void CB2_FadeAndReturnToTitleScreen(void)
{
    gSaveFailedClockInfo[0] = FALSE;

    if (gMain.newKeys & A_BUTTON)
    {
        BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB(0, 0, 0));
        SetVBlankCallback(VBlankCB);
        SetMainCallback2(CB2_ReturnToTitleScreen);
    }
}

static void CB2_ReturnToTitleScreen(void)
{
    if (!UpdatePaletteFade())
    {
        if (gGameContinueCallback == 0) // no callback exists, so do a soft reset.
        {
            DoSoftReset();
        }
        else
        {
            SetMainCallback2((MainCallback)gGameContinueCallback);
            gGameContinueCallback = 0;
        }
    }
}

static void VBlankCB_UpdateClockGraphics(void)
{
    unsigned int n = (gMain.vblankCounter2 >> 3) & 7;

    gMain.oamBuffer[0] = sClockOamData;
    gMain.oamBuffer[0].x = 112;
    gMain.oamBuffer[0].y = (CLOCK_WIN_TOP + 1) * 8;

    if (gSaveFailedClockInfo[0] != FALSE)
    {
        gMain.oamBuffer[0].tileNum = sClockFrames[n][0];
        gMain.oamBuffer[0].matrixNum = (sClockFrames[n][2] << 4) | (sClockFrames[n][1] << 3);
    }
    else
    {
        gMain.oamBuffer[0].tileNum = 1;
    }

    CpuFastCopy(gMain.oamBuffer, (void *)OAM, 4);

    if (gSaveFailedClockInfo[1]) // maybe was used for debugging?
        gSaveFailedClockInfo[1]--;
}

static bool8 IsSectorNonEmpty(u16 sector)
{
    u32 *ptr = (u32 *)&gSharedMem;
    u16 i;

    ReadFlash(sector, 0, ptr, 4096);

#if (DEBUG && !(ENGLISH && REVISION == 0))  // Don't verify the sector wipe?
    for (i = 0; i < 0x400; i++, ptr++)
		;
    return gUnknown_Debug_03004BD0;
#else
	for (i = 0; i < 0x400; i++, ptr++)
        if (*ptr != 0)
            return TRUE;
    return FALSE;
#endif
}

static bool8 WipeSector(u16 sector)
{
    u16 i, j;
    bool8 failed = TRUE;

    for (i = 0; failed && i < 130; i++)
    {
        for (j = 0; j < 0x1000; j++)
            ProgramFlashByte(sector, j, 0);

        failed = IsSectorNonEmpty(sector);
    }

    return failed;
}

static bool8 WipeSectors(u32 sectorBits)
{
    u16 i;

    for (i = 0; i < 0x20; i++)
        if ((sectorBits & (1 << i)) && !WipeSector(i))
            sectorBits &= ~(1 << i);

    if (sectorBits == 0)
        return FALSE;
    else
        return TRUE;
}
