#include "global.h"
#include "gpu_regs.h"
#include "multiboot.h"
#include "malloc.h"
#include "bg.h"
#include "graphics.h"
#include "main.h"
#include "sprite.h"
#include "task.h"
#include "scanline_effect.h"
#include "window.h"
#include "text.h"
#include "menu.h"
#include "m4a.h"
#include "constants/rgb.h"

struct {
    u8 state;
    u8 curScene;
    u16 timer;
    struct MultiBootParam mb;
} static * sBerryFix;

static void BerryFix_Main(void);
static void BerryFix_GpuSet(void);
static int BerryFix_TrySetScene(int);
static void BerryFix_SetScene(int);
static void BerryFix_HideScene(void);

static const u8 sText_BerryProgramUpdate[] = _("Berry Program Update");
static const u8 sText_RubySapphire[] = _("Ruby/Sapphire");
static const u8 sText_Emerald[] = _("Emerald");
static const u8 sText_BerryProgramWillBeUpdatedPressA[] = _("The Berry Program on your POKéMON\n"
                                                            "Ruby/Sapphire Game Pak will be updated.\n"
                                                            "{COLOR RED}{SHADOW LIGHT_RED}Press the A Button.");
static const u8 sText_EnsureGBAConnectionMatches[] = _("Please ensure the connection of your\n"
                                                       "Game Boy Advance system matches this.\n"
                                                       "{COLOR RED}{SHADOW LIGHT_RED}YES: Press the A Button.\n"
                                                       "NO: Turn off the power and try again.");
static const u8 sText_TurnOffPowerHoldingStartSelect[] = _("Please turn on the power of POKéMON\n"
                                                           "Ruby/Sapphire while holding START and\n"
                                                           "SELECT simultaneously. Then, ensure\n"
                                                           "the picture above appears.");
static const u8 sText_TransmittingPleaseWait[] = _("Transmitting. Please wait.\n"
                                                   "{COLOR RED}{SHADOW LIGHT_RED}Please do not turn off the power or\n"
                                                   "unplug the Game Boy Advance Game\nLink Cable.");
static const u8 sText_PleaseFollowInstructionsOnScreen[] = _("Please follow the instructions on your\n"
                                                             "POKéMON Ruby/Sapphire screen.");
static const u8 sText_TransmissionFailureTryAgain[] = _("Transmission failure.\n"
                                                        "{COLOR RED}{SHADOW LIGHT_RED}Please try again.");

static const struct BgTemplate sBerryFixBgTemplates[] = {
    {
        .bg = 0,
        .charBaseIndex = 0,
        .mapBaseIndex = 30,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 0,
        .baseTile = 0
    },
    {
        .bg = 1,
        .charBaseIndex = 1,
        .mapBaseIndex = 31,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 1,
        .baseTile = 0
    }
};

static const struct WindowTemplate sBerryFixWindowTemplates[] = {
    {
        .bg = 0,
        .tilemapLeft = 2,
        .tilemapTop = 4,
        .width = 26,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 1
    },
    {
        .bg = 0,
        .tilemapLeft = 1,
        .tilemapTop = 11,
        .width = 28,
        .height = 8,
        .paletteNum = 15,
        .baseBlock = 53
    },
    {
        .bg = 0,
        .tilemapLeft = 0,
        .tilemapTop = 8,
        .width = 30,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 277
    },
    {
        .bg = 0,
        .tilemapLeft = 8,
        .tilemapTop = 0,
        .width = 14,
        .height = 2,
        .paletteNum = 15,
        .baseBlock = 337
    },
    DUMMY_WIN_TEMPLATE
};

static const u16 sBerryFixPalColors[] = {
    RGB_WHITE,       RGB_WHITE,       RGB(12, 12, 12), RGB(26, 26, 25),
    RGB(28, 1, 1),   RGB(31, 23, 14), RGB(4, 19, 1),   RGB(18, 30, 18),
    RGB(6, 10, 25),  RGB(20, 24, 30), RGB_WHITE,       RGB(12, 12, 12),
    RGB(26, 26, 25), RGB_BLACK,       RGB_BLACK,       RGB_BLACK
};

static const u8 sBerryProgramTextColors[] = {TEXT_DYNAMIC_COLOR_1, TEXT_DYNAMIC_COLOR_2, TEXT_DYNAMIC_COLOR_3};
static const u8 sGameTitleTextColors[] = { TEXT_COLOR_TRANSPARENT, TEXT_DYNAMIC_COLOR_1, TEXT_DYNAMIC_COLOR_4};

enum {
    SCENE_ENSURE_CONNECT,
    SCENE_TURN_OFF_POWER,
    SCENE_TRANSMITTING,
    SCENE_FOLLOW_INSTRUCT,
    SCENE_TRANSMIT_FAILED,
    SCENE_BEGIN,
    SCENE_NONE
};

static const u8 *const sBerryProgramTexts[] = {
    [SCENE_ENSURE_CONNECT]  = sText_EnsureGBAConnectionMatches,
    [SCENE_TURN_OFF_POWER]  = sText_TurnOffPowerHoldingStartSelect,
    [SCENE_TRANSMITTING]    = sText_TransmittingPleaseWait,
    [SCENE_FOLLOW_INSTRUCT] = sText_PleaseFollowInstructionsOnScreen,
    [SCENE_TRANSMIT_FAILED] = sText_TransmissionFailureTryAgain,
    [SCENE_BEGIN]           = sText_BerryProgramWillBeUpdatedPressA
};

static const struct {
    const u32 *gfx;
    const u32 *tilemap;
    const u16 *palette;
} sBerryFixGraphics[] = {
    [SCENE_ENSURE_CONNECT] = {
        gBerryFixGameboy_Gfx,
        gBerryFixGameboy_Tilemap,
        gBerryFixGameboy_Pal
    },
    [SCENE_TURN_OFF_POWER] = {
        gBerryFixGameboyLogo_Gfx,
        gBerryFixGameboyLogo_Tilemap,
        gBerryFixGameboyLogo_Pal
    },
    [SCENE_TRANSMITTING] = {
        gBerryFixGbaTransfer_Gfx,
        gBerryFixGbaTransfer_Tilemap,
        gBerryFixGbaTransfer_Pal
    },
    [SCENE_FOLLOW_INSTRUCT] = {
        gBerryFixGbaTransferHighlight_Gfx,
        gBerryFixGbaTransferHighlight_Tilemap,
        gBerryFixGbaTransferHighlight_Pal
    },
    [SCENE_TRANSMIT_FAILED] = {
        gBerryFixGbaTransferError_Gfx,
        gBerryFixGbaTransferError_Tilemap,
        gBerryFixGbaTransferError_Pal
    },
    [SCENE_BEGIN] = {
        gBerryFixWindow_Gfx,
        gBerryFixWindow_Tilemap,
        gBerryFixWindow_Pal
    },
};

extern const u8 gMultiBootProgram_BerryGlitchFix_Start[0x3BF4];
extern const u8 gMultiBootProgram_BerryGlitchFix_End[];

enum {
    MAINSTATE_INIT,
    MAINSTATE_BEGIN,
    MAINSTATE_CONNECT,
    MAINSTATE_INIT_MULTIBOOT,
    MAINSTATE_MULTIBOOT,
    MAINSTATE_TRANSMIT,
    MAINSTATE_EXIT,
    MAINSTATE_FAILED,
};

void CB2_InitBerryFixProgram(void)
{
    DisableInterrupts(0xFFFF); // all
    EnableInterrupts(INTR_FLAG_VBLANK);
    m4aSoundVSyncOff();
    SetVBlankCallback(NULL);
    ResetSpriteData();
    ResetTasks();
    ScanlineEffect_Stop();
    SetGpuReg(REG_OFFSET_DISPCNT, 0);
    sBerryFix = AllocZeroed(sizeof(*sBerryFix));
    sBerryFix->state = MAINSTATE_INIT;
    sBerryFix->curScene = SCENE_NONE;
    SetMainCallback2(BerryFix_Main);
}

#define TryScene(sceneNum) BerryFix_TrySetScene(sceneNum) == (sceneNum)

static void BerryFix_Main(void)
{
    switch (sBerryFix->state)
    {
        case MAINSTATE_INIT:
            BerryFix_GpuSet();
            sBerryFix->state = MAINSTATE_BEGIN;
            break;
        case MAINSTATE_BEGIN:
            if (TryScene(SCENE_BEGIN) && (JOY_NEW(A_BUTTON)))
                sBerryFix->state = MAINSTATE_CONNECT;
            break;
        case MAINSTATE_CONNECT:
            if (TryScene(SCENE_ENSURE_CONNECT) && (JOY_NEW(A_BUTTON)))
                sBerryFix->state = MAINSTATE_INIT_MULTIBOOT;
            break;
        case MAINSTATE_INIT_MULTIBOOT:
            if (TryScene(SCENE_TURN_OFF_POWER))
            {
                sBerryFix->mb.masterp = gMultiBootProgram_BerryGlitchFix_Start;
                sBerryFix->mb.server_type = 0;
                MultiBootInit(&sBerryFix->mb);
                sBerryFix->timer = 0;
                sBerryFix->state = MAINSTATE_MULTIBOOT;
            }
            break;
        case MAINSTATE_MULTIBOOT:
            MultiBootMain(&sBerryFix->mb);
            if (sBerryFix->mb.probe_count != 0 || (!(sBerryFix->mb.response_bit & 2) || !(sBerryFix->mb.client_bit & 2)))
            {
                sBerryFix->timer = 0;
            }
            else if (++sBerryFix->timer > 180)
            {
                MultiBootStartMaster(&sBerryFix->mb,
                                     gMultiBootProgram_BerryGlitchFix_Start + ROM_HEADER_SIZE,
                                     (u32)(gMultiBootProgram_BerryGlitchFix_End - (gMultiBootProgram_BerryGlitchFix_Start + ROM_HEADER_SIZE)),
                                     4,
                                     1);
                sBerryFix->state = MAINSTATE_TRANSMIT;
            }
            break;
        case MAINSTATE_TRANSMIT:
            if (TryScene(SCENE_TRANSMITTING))
            {
                MultiBootMain(&sBerryFix->mb);

                if (MultiBootCheckComplete(&sBerryFix->mb))
                    sBerryFix->state = MAINSTATE_EXIT;
                else if (!(sBerryFix->mb.client_bit & 2))
                    sBerryFix->state = MAINSTATE_FAILED;
            }
            break;
        case MAINSTATE_EXIT:
            if (TryScene(SCENE_FOLLOW_INSTRUCT) && JOY_NEW(A_BUTTON))
                DoSoftReset();
            break;
        case MAINSTATE_FAILED:
            if (TryScene(SCENE_TRANSMIT_FAILED) && JOY_NEW(A_BUTTON))
                sBerryFix->state = MAINSTATE_BEGIN;
            break;
    }
}

static void BerryFix_GpuSet(void)
{
    s32 width, left;

    SetGpuReg(REG_OFFSET_BG0CNT, 0);
    SetGpuReg(REG_OFFSET_BG1CNT, 0);
    SetGpuReg(REG_OFFSET_BG0HOFS, 0);
    SetGpuReg(REG_OFFSET_BG0VOFS, 0);
    SetGpuReg(REG_OFFSET_BG1HOFS, 0);
    SetGpuReg(REG_OFFSET_BG1VOFS, 0);
    SetGpuReg(REG_OFFSET_BLDCNT, 0);

    DmaFill32(3, 0, VRAM, VRAM_SIZE);
    DmaFill32(3, 0, OAM, OAM_SIZE);
    DmaFill32(3, 0, PLTT, PLTT_SIZE);
    ResetBgsAndClearDma3BusyFlags(0);

    InitBgsFromTemplates(0, sBerryFixBgTemplates, ARRAY_COUNT(sBerryFixBgTemplates));
    ChangeBgX(0, 0, 0);
    ChangeBgY(0, 0, 0);
    ChangeBgX(1, 0, 0);
    ChangeBgY(1, 0, 0);
    InitWindows(sBerryFixWindowTemplates);
    DeactivateAllTextPrinters();

    DmaCopy32(3, sBerryFixPalColors, BG_PLTT + 0x1E0, sizeof(sBerryFixPalColors));
    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_1D_MAP);
    FillWindowPixelBuffer(2, PIXEL_FILL(0));
    FillWindowPixelBuffer(3, PIXEL_FILL(0));
    FillWindowPixelBuffer(0, PIXEL_FILL(10));

    width = GetStringWidth(0, sText_Emerald, 0);
    left = (120 - width) / 2;
    AddTextPrinterParameterized3(2, 0, left, 3, sGameTitleTextColors, TEXT_SPEED_FF, sText_Emerald);

    width = GetStringWidth(0, sText_RubySapphire, 0);
    left = (120 - width) / 2 + 120;
    AddTextPrinterParameterized3(2, 0, left, 3, sGameTitleTextColors, TEXT_SPEED_FF, sText_RubySapphire);

    width = GetStringWidth(0, sText_RubySapphire, 0);
    left = (112 - width) / 2;
    AddTextPrinterParameterized3(3, 0, left, 0, sGameTitleTextColors, TEXT_SPEED_FF, sText_RubySapphire);

    width = GetStringWidth(1, sText_BerryProgramUpdate, 0);
    left = (208 - width) / 2;
    AddTextPrinterParameterized3(0, 1, left, 2, sBerryProgramTextColors, TEXT_SPEED_FF, sText_BerryProgramUpdate);

    CopyWindowToVram(2, 2);
    CopyWindowToVram(3, 2);
    CopyWindowToVram(0, 2);
}

static int BerryFix_TrySetScene(int scene)
{
    if (sBerryFix->curScene == scene)
        return scene;

    if (sBerryFix->curScene == SCENE_NONE)
    {
        BerryFix_SetScene(scene);
        sBerryFix->curScene = scene;
    }
    else
    {
        BerryFix_HideScene();
        sBerryFix->curScene = SCENE_NONE;
    }
    return sBerryFix->curScene;
}

static void BerryFix_SetScene(int scene)
{
    FillBgTilemapBufferRect_Palette0(0, 0, 0, 0, 32, 32);
    FillWindowPixelBuffer(1, PIXEL_FILL(10));
    AddTextPrinterParameterized3(1, 1, 0, 0, sBerryProgramTextColors, -1, sBerryProgramTexts[scene]);
    PutWindowTilemap(1);
    CopyWindowToVram(1, 2);
    switch (scene)
    {
    case SCENE_ENSURE_CONNECT:
    case SCENE_TRANSMITTING:
    case SCENE_FOLLOW_INSTRUCT:
    case SCENE_TRANSMIT_FAILED:
        PutWindowTilemap(2);
        break;
    case SCENE_TURN_OFF_POWER:
        PutWindowTilemap(3);
        break;
    case SCENE_BEGIN:
        PutWindowTilemap(0);
        break;
    }
    CopyBgTilemapBufferToVram(0);
    LZ77UnCompVram(sBerryFixGraphics[scene].gfx, (void *)BG_CHAR_ADDR(1));
    LZ77UnCompVram(sBerryFixGraphics[scene].tilemap, (void *)BG_SCREEN_ADDR(31));
    CpuCopy32(sBerryFixGraphics[scene].palette, (void *)BG_PLTT, 0x100);
    ShowBg(0);
    ShowBg(1);
}

static void BerryFix_HideScene(void)
{
    HideBg(0);
    HideBg(1);
}
