#include "global.h"
#include "reset_rtc_screen.h"
#include "event_data.h"
#include "main.h"
#include "menu.h"
#include "palette.h"
#include "rtc.h"
#include "save.h"
#include "sprite.h"
#include "constants/songs.h"
#include "sound.h"
#include "string_util.h"
#include "strings.h"
#include "task.h"
#include "text.h"
#include "scanline_effect.h"
#include "bg.h"
#include "window.h"
#include "gpu_regs.h"

struct ResetRtcStruct
{
    /*0x0*/ u8 dataIndex;
    /*0x2*/ u16 minVal;
    /*0x4*/ u16 maxVal;
    /*0x6*/ u8 left;
    /*0x7*/ u8 right;
    /*0x8*/ u8 unk8;
};

// this file's functions
static void CB2_ResetRtcScreen(void);
static void VBlankCB(void);
static void Task_ResetRtcScreen(u8 taskId);
static void sub_809F048(void);

// const rom data
static const struct BgTemplate sBackgroundTemplates[] =
{
    {
        .bg = 0,
        .charBaseIndex = 0,
        .mapBaseIndex = 31,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 0,
        .baseTile = 0
    }
};

static const struct WindowTemplate sUnknown_08510408[] =
{
    {0x00, 0x01, 0x01, 0x13, 0x09, 0x0f, 0x0155},
    {0x00, 0x02, 0x0f, 0x1b, 0x04, 0x0f, 0x00e9},
    DUMMY_WIN_TEMPLATE
};

static const struct WindowTemplate sUnknown_08510420 = {0x00, 0x04, 0x09, 0x15, 0x02, 0x0f, 0x00bf};

static const struct ResetRtcStruct sUnknown_08510428[5] =
{
    {
        .dataIndex = 3,
        .minVal = 1,
        .maxVal = 9999,
        .left = 0,
        .right = 2,
        .unk8 = 0,
    },
    {
        .dataIndex = 4,
        .minVal = 0,
        .maxVal = 23,
        .left = 1,
        .right = 3,
        .unk8 = 0,
    },
    {
        .dataIndex = 5,
        .minVal = 0,
        .maxVal = 59,
        .left = 2,
        .right = 4,
        .unk8 = 0,
    },
    {
        .dataIndex = 6,
        .minVal = 0,
        .maxVal = 59,
        .left = 3,
        .right = 5,
        .unk8 = 0,
    },
    {
        .dataIndex = 7,
        .minVal = 0,
        .maxVal = 0,
        .left = 4,
        .right = 0,
        .unk8 = 6,
    },
};

static const struct OamData sOamData_08510464 =
{
    .y = 0,
    .affineMode = 0,
    .objMode = 0,
    .mosaic = 0,
    .bpp = 0,
    .shape = 0,
    .x = 0,
    .matrixNum = 0,
    .size = 0,
    .tileNum = 0,
    .priority = 0,
    .paletteNum = 0,
    .affineParam = 0,
};

static const u8 sResetRtcScreen_DownArrowGfx[] = INCBIN_U8("graphics/misc/reset_rtc_screen_downarrow.4bpp");
static const u8 sResetRtcScreen_RightArrowGfx[] = INCBIN_U8("graphics/misc/reset_rtc_screen_rightarrow.4bpp");
static const u16 sResetRtcScreen_ArrowPal[] = INCBIN_U16("graphics/misc/reset_rtc_screen_arrow.gbapal");

static const struct SpriteFrameImage sSpriteImageTable_85104B4[] =
{
    obj_frame_tiles(sResetRtcScreen_DownArrowGfx),
    obj_frame_tiles(sResetRtcScreen_RightArrowGfx)
};

static const struct SpritePalette sSpritePalette_Arrow =
{
    sResetRtcScreen_ArrowPal, 0x1000
};

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

static const union AnimCmd sSpriteAnim_85104D4[] =
{
    ANIMCMD_FRAME(0, 158, .vFlip = TRUE),
    ANIMCMD_JUMP(0),
};

static const union AnimCmd sSpriteAnim_85104DC[] =
{
    ANIMCMD_FRAME(1, 30),
    ANIMCMD_JUMP(0),
};

static const union AnimCmd *const sSpriteAnimTable_85104E4[] =
{
    sSpriteAnim_85104CC,
    sSpriteAnim_85104D4,
    sSpriteAnim_85104DC,
};

static const struct SpriteTemplate sSpriteTemplate_85104F0 =
{
    .tileTag = 0xFFFF,
    .paletteTag = 0x1000,
    .oam = &sOamData_08510464,
    .anims = sSpriteAnimTable_85104E4,
    .images = sSpriteImageTable_85104B4,
    .affineAnims = gDummySpriteAffineAnimTable,
    .callback = SpriteCallbackDummy,
};

// code
static void SpriteCB_ResetRtcCursor0(struct Sprite *sprite)
{
    int state = gTasks[sprite->data[0]].data[2];
    if (state != sprite->data[1])
    {
        sprite->data[1] = state;
        switch (state)
        {
        case 1:
            sprite->invisible = FALSE;
            sprite->animNum = 1;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 53;
            sprite->pos1.y = 68;
            break;
        case 2:
            sprite->invisible = FALSE;
            sprite->animNum = 1;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 86;
            sprite->pos1.y = 68;
            break;
        case 3:
            sprite->invisible = FALSE;
            sprite->animNum = 1;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 101;
            sprite->pos1.y = 68;
            break;
        case 4:
            sprite->invisible = FALSE;
            sprite->animNum = 1;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 116;
            sprite->pos1.y = 68;
            break;
        case 5:
            sprite->invisible = FALSE;
            sprite->animNum = 2;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 153;
            sprite->pos1.y = 80;
            break;
        case 6:
            DestroySprite(sprite);
            break;
        }
    }
}

static void SpriteCB_ResetRtcCursor1(struct Sprite *sprite)
{
    int state = gTasks[sprite->data[0]].data[2];
    if (state != sprite->data[1])
    {
        sprite->data[1] = state;
        switch (state)
        {
        case 1:
            sprite->invisible = FALSE;
            sprite->animNum = 0;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 53;
            sprite->pos1.y = 92;
            break;
        case 2:
            sprite->invisible = FALSE;
            sprite->animNum = 0;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 86;
            sprite->pos1.y = 92;
            break;
        case 3:
            sprite->invisible = FALSE;
            sprite->animNum = 0;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 101;
            sprite->pos1.y = 92;
            break;
        case 4:
            sprite->invisible = FALSE;
            sprite->animNum = 0;
            sprite->animDelayCounter = 0;
            sprite->pos1.x = 116;
            sprite->pos1.y = 92;
            break;
        case 5:
            sprite->invisible = TRUE;
            break;
        case 6:
            DestroySprite(sprite);
            break;
        }
    }
}

static void CreateCursor(u8 taskId)
{
    u32 spriteId;

    LoadSpritePalette(&sSpritePalette_Arrow);

    spriteId = CreateSpriteAtEnd(&sSpriteTemplate_85104F0, 53, 68, 0);
    gSprites[spriteId].callback = SpriteCB_ResetRtcCursor0;
    gSprites[spriteId].data[0] = taskId;
    gSprites[spriteId].data[1] = -1;

    spriteId = CreateSpriteAtEnd(&sSpriteTemplate_85104F0, 53, 68, 0);
    gSprites[spriteId].callback = SpriteCB_ResetRtcCursor1;
    gSprites[spriteId].data[0] = taskId;
    gSprites[spriteId].data[1] = -1;
}

static void FreeCursorPalette(void)
{
    FreeSpritePaletteByTag(sSpritePalette_Arrow.tag);
}

static void HideChooseTimeWindow(u8 windowId)
{
    sub_8198070(windowId, FALSE);
    RemoveWindow(windowId);
    schedule_bg_copy_tilemap_to_vram(0);
}

static void PrintTime(u8 windowId, u8 x, u8 y, u16 days, u8 hours, u8 minutes, u8 seconds)
{
    u8 *dest = gStringVar4;

    ConvertIntToDecimalStringN(gStringVar1, days, 1, 4);
    dest = StringCopy(dest, gStringVar1);
    dest = StringCopy(dest, gText_Day);

    ConvertIntToDecimalStringN(gStringVar1, hours, 1, 3);
    dest = StringCopy(dest, gStringVar1);
    dest = StringCopy(dest, gText_Colon3);

    ConvertIntToDecimalStringN(gStringVar1, minutes, 2, 2);
    dest = StringCopy(dest, gStringVar1);
    dest = StringCopy(dest, gText_Colon3);

    ConvertIntToDecimalStringN(gStringVar1, seconds, 2, 2);
    dest = StringCopy(dest, gStringVar1);

    AddTextPrinterParameterized(windowId, 1, gStringVar4, x, y, TEXT_SPEED_FF, NULL);
}

static void ShowChooseTimeWindow(u8 windowId, u16 days, u8 hours, u8 minutes, u8 seconds)
{
    SetWindowBorderStyle(windowId, FALSE, 0x214, 0xE);
    PrintTime(windowId, 0, 1, days, hours, minutes, seconds);
    AddTextPrinterParameterized(windowId, 1, gText_Confirm2, 126, 1, 0, NULL);
    schedule_bg_copy_tilemap_to_vram(0);
}

static bool32 MoveTimeUpDown(s16 *val, int minVal, int maxVal, u16 keys)
{
    if (keys & DPAD_DOWN)
    {
        *val -= 1;
        if (*val < minVal)
            *val = maxVal;
    }
    else if (keys & DPAD_UP)
    {
        *val += 1;
        if (*val > maxVal)
            *val = minVal;
    }
    else if (keys & DPAD_LEFT)
    {
        *val -= 10;
        if (*val < minVal)
            *val = maxVal;
    }
    else if (keys & DPAD_RIGHT)
    {
        *val += 10;
        if (*val > maxVal)
            *val = minVal;
    }
    else
    {
        return FALSE;
    }

    return TRUE;
}

static void Task_ResetRtc_3(u8 taskId)
{
    gTasks[taskId].data[0] = 1;
}

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

    HideChooseTimeWindow(data[8]);
    FreeCursorPalette();
    gTasks[taskId].func = Task_ResetRtc_3;
}

static void Task_ResetRtc_1(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    u8 selection = data[2];
    const struct ResetRtcStruct *selectionInfo = &sUnknown_08510428[selection - 1];

    if (gMain.newKeys & B_BUTTON)
    {
        gTasks[taskId].func = Task_ResetRtc_2;
        data[1] = 0;
        data[2] = 6;
        PlaySE(SE_SELECT);
        return;
    }

    if (gMain.newKeys & DPAD_RIGHT)
    {
        if (selectionInfo->right)
        {
            data[2] = selectionInfo->right;
            PlaySE(SE_SELECT);
            return;
        }
    }

    if (gMain.newKeys & DPAD_LEFT)
    {
        if (selectionInfo->left)
        {
            data[2] = selectionInfo->left;
            PlaySE(SE_SELECT);
            return;
        }
    }

    if (selection == 5)
    {
        if (gMain.newKeys & A_BUTTON)
        {
            gLocalTime.days = data[3];
            gLocalTime.hours = data[4];
            gLocalTime.minutes = data[5];
            gLocalTime.seconds = data[6];
            PlaySE(SE_SELECT);
            gTasks[taskId].func = Task_ResetRtc_2;
            data[1] = 1;
            data[2] = 6;
        }
    }
    else if (MoveTimeUpDown(&data[selectionInfo->dataIndex], selectionInfo->minVal, selectionInfo->maxVal, gMain.newAndRepeatedKeys & (DPAD_UP | DPAD_DOWN)))
    {
        PlaySE(SE_SELECT);
        PrintTime(data[8], 0, 1, data[3], data[4], data[5], data[6]);
        CopyWindowToVram(data[8], 2);
    }
}

static void Task_ResetRtc_0(u8 taskId)
{
    s16 *data = gTasks[taskId].data;
    data[0] = 0;
    data[3] = gLocalTime.days;
    data[4] = gLocalTime.hours;
    data[5] = gLocalTime.minutes;
    data[6] = gLocalTime.seconds;
    data[8] = AddWindow(&sUnknown_08510420);
    ShowChooseTimeWindow(data[8], data[3], data[4], data[5], data[6]);
    CreateCursor(taskId);
    data[2] = 2;
    gTasks[taskId].func = Task_ResetRtc_1;
}

void CB2_InitResetRtcScreen(void)
{
    SetGpuReg(REG_OFFSET_DISPCNT, 0);
    SetVBlankCallback(NULL);
    DmaClear16(3, PLTT, PLTT_SIZE);
    DmaFillLarge16(3, 0, (u8 *)VRAM, 0x18000, 0x1000);
    ResetOamRange(0, 128);
    LoadOam();
    ScanlineEffect_Stop();
    ScanlineEffect_Clear();
    ResetSpriteData();
    ResetTasks();
    ResetPaletteFade();
    sub_809F048();
    SetVBlankCallback(VBlankCB);
    SetMainCallback2(CB2_ResetRtcScreen);
    CreateTask(Task_ResetRtcScreen, 80);
}

static void sub_809F048(void)
{
    clear_scheduled_bg_copies_to_vram();
    ResetBgsAndClearDma3BusyFlags(0);
    InitBgsFromTemplates(0, sBackgroundTemplates, ARRAY_COUNT(sBackgroundTemplates));
    schedule_bg_copy_tilemap_to_vram(0);
    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP);
    ShowBg(0);
    InitWindows(sUnknown_08510408);
    DeactivateAllTextPrinters();
    sub_81973A4();
}

static void CB2_ResetRtcScreen(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    do_scheduled_bg_tilemap_copies_to_vram();
    UpdatePaletteFade();
}

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

static void ShowMessage(const u8 *str)
{
    sub_8197B1C(1, FALSE, 0x200, 0xF);
    AddTextPrinterParameterized(1, 1, str, 0, 1, 0, NULL);
    schedule_bg_copy_tilemap_to_vram(0);
}

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

    switch (data[0])
    {
    case 0:
        SetWindowBorderStyle(0, FALSE, 0x214, 0xE);
        AddTextPrinterParameterized(0, 1, gText_PresentTime, 0, 1, TEXT_SPEED_FF, 0);
        PrintTime(
            0,
            0,
            17,
            gLocalTime.days,
            gLocalTime.hours,
            gLocalTime.minutes,
            gLocalTime.seconds);
        AddTextPrinterParameterized(0, 1, gText_PreviousTime, 0, 33, TEXT_SPEED_FF, 0);
        PrintTime(
            0,
            0,
            49,
            gSaveBlock2Ptr->lastBerryTreeUpdate.days,
            gSaveBlock2Ptr->lastBerryTreeUpdate.hours,
            gSaveBlock2Ptr->lastBerryTreeUpdate.minutes,
            gSaveBlock2Ptr->lastBerryTreeUpdate.seconds);
        ShowMessage(gText_ResetRTCConfirmCancel);
        CopyWindowToVram(0, 2);
        schedule_bg_copy_tilemap_to_vram(0);
        data[0]++;
    case 1:
        if (gMain.newKeys & B_BUTTON)
        {
            DestroyTask(taskId);
            DoSoftReset();
        }
        else if (gMain.newKeys & A_BUTTON)
        {
            PlaySE(SE_SELECT);
            DestroyTask(taskId);
        }
        break;
    }
}

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

    switch (data[0])
    {
    case 0:
        BeginNormalPaletteFade(0xFFFFFFFF, 1, 0x10, 0, 0xFFFF);
        data[0] = 1;
        break;
    case 1:
        if (!gPaletteFade.active)
        {
            if (gSaveFileStatus == 0 || gSaveFileStatus == 2)
            {
                ShowMessage(gText_NoSaveFileCantSetTime);
                data[0] = 5;
            }
            else
            {
                RtcCalcLocalTime();
                data[1] = CreateTask(Task_ShowResetRtcPrompt, 80);
                data[0] = 2;
            }
        }
        break;
    case 2:
        if (gTasks[data[1]].isActive != TRUE)
        {
            sub_8198070(0, FALSE);
            ShowMessage(gText_PleaseResetTime);
            gLocalTime = gSaveBlock2Ptr->lastBerryTreeUpdate;
            data[1] = CreateTask(Task_ResetRtc_0, 80);
            data[0] = 3;
        }
        break;
    case 3:
        if (gTasks[data[1]].data[0])
        {
            if (!gTasks[data[1]].data[1])
            {
                DestroyTask(data[1]);
                data[0] = 2;
            }
            else
            {
                DestroyTask(data[1]);
                RtcReset();
                RtcCalcLocalTimeOffset(
                    gLocalTime.days,
                    gLocalTime.hours,
                    gLocalTime.minutes,
                    gLocalTime.seconds);
                gSaveBlock2Ptr->lastBerryTreeUpdate = gLocalTime;
                VarSet(VAR_DAYS, gLocalTime.days);
                DisableResetRTC();
                ShowMessage(gText_ClockHasBeenReset);
                data[0] = 4;
            }
        }
        break;
    case 4:
        if (TrySavingData(0) == 1)
        {
            ShowMessage(gText_SaveCompleted);
            PlaySE(SE_PINPON);
        }
        else
        {
            ShowMessage(gText_SaveFailed);
            PlaySE(SE_BOO);
        }
        data[0] = 5;
    case 5:
        if (gMain.newKeys & A_BUTTON)
        {
            BeginNormalPaletteFade(0xFFFFFFFF, 1, 0, 0x10, 0xFFFF);
            data[0] = 6;
        }
        else
        {
            break;
        }
    case 6:
        if (!gPaletteFade.active)
        {
            DestroyTask(taskId);
            FreeAllWindowBuffers();
            DoSoftReset();
        }
    }
}
