#include "global.h"
#include "event_data.h"
#include "field_effect.h"
#include "main.h"
#include "palette.h"
#include "pokemon_menu.h"
#include "overworld.h"
#include "rom6.h"
#include "script.h"
#include "sound.h"
#include "sprite.h"
#include "task.h"
#include "constants/map_types.h"
#include "constants/songs.h"

struct MapTypeFadePairs
{
    u8 mapTypeA;
    u8 mapTypeB;
    u8 fadeToType;   // 0 = fade to black,   1 = fade to white
    u8 fadeFromType; // 0 = fade from black, 1 = fade from white
    void (*func)(void);
};

extern u8 gLastFieldPokeMenuOpened;
extern void (*gPostMenuFieldCallback)(void);

extern u8 gUnknown_081B694A[];

static void sub_810CBFC(void);
static void sub_810CC34(void);
static bool8 sub_810CD5C(void);
static void sub_810CE5C(u8);
static void sub_810CE78(u8);
static void sub_810CF18(u8);
static void sub_810CF5C(u8);
static void sub_810CFC4(u8);
static void sub_810D00C(u8);
static void sub_810D028(u8);
static void sub_810D0C4(u8);
static void sub_810D128(u8);
static void CreateEnterUndergroundEffectTask(void);
static void CreateExitUndergroundTask(void);

static const struct MapTypeFadePairs gMapTypeFadePairs[] =
{
    {MAP_TYPE_TOWN,        MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_CITY,        MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_ROUTE,       MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_UNDERWATER,  MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_6,           MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_7,           MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_INDOOR,      MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_SECRET_BASE, MAP_TYPE_UNDERGROUND, 1, 0, CreateEnterUndergroundEffectTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_TOWN,        0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_CITY,        0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_ROUTE,       0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_UNDERWATER,  0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_6,           0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_7,           0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_INDOOR,      0, 1, CreateExitUndergroundTask},
    {MAP_TYPE_UNDERGROUND, MAP_TYPE_SECRET_BASE, 0, 1, CreateExitUndergroundTask},
    {0, 0, 0, 0, NULL},
};

// TODO: Make these extracted palettes?
static const u16 gCaveTransitionPalette_White[] = {0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF};
static const u16 gCaveTransitionPalette_Black[] = {0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};

static const u16 gUnknown_083F808C[] = INCBIN_U16("graphics/misc/83F808C.gbapal");
static const u16 gUnknown_083F809C[] = INCBIN_U16("graphics/misc/83F809C.gbapal");
static const u16 gCaveTransitionTilemap[] = INCBIN_U16("graphics/misc/cave_transition_map.bin.lz");
static const u8 gCaveTransitionTiles[] = INCBIN_U8("graphics/misc/cave_transition.4bpp.lz");

#if DEBUG

void debug_sub_8122080(void)
{
    if (gMapHeader.cave == 1 && !FlagGet(FLAG_SYS_USE_FLASH))
        sub_810CBFC();
    else
        ScriptContext2_Disable();
}

#endif

bool8 SetUpFieldMove_Flash(void)
{
    if (gMapHeader.cave == TRUE && !FlagGet(FLAG_SYS_USE_FLASH))
    {
        gFieldCallback = FieldCallback_PrepareFadeInFromMenu;
        gPostMenuFieldCallback = sub_810CBFC;
        return TRUE;
    }

    return FALSE;
}

static void sub_810CBFC(void)
{
    u8 taskId = oei_task_add();
    gFieldEffectArguments[0] = gLastFieldPokeMenuOpened;
    gTasks[taskId].data[8] = (uintptr_t)sub_810CC34 >> 16;
    gTasks[taskId].data[9] = (uintptr_t)sub_810CC34;
}

static void sub_810CC34(void)
{
    PlaySE(SE_M_REFLECT);
    FlagSet(FLAG_SYS_USE_FLASH);
    ScriptContext1_SetupScript(gUnknown_081B694A);
}

void sub_810CC54(void)
{
    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
    UpdatePaletteFade();
}

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

void sub_810CC80(void)
{
    u16 ime;

    SetVBlankCallback(NULL);
    REG_DISPCNT = 0;
    REG_BG2CNT = 0;
    REG_BG1CNT = 0;
    REG_BG0CNT = 0;
    REG_BG2HOFS = 0;
    REG_BG2VOFS = 0;
    REG_BG1HOFS = 0;
    REG_BG1VOFS = 0;
    REG_BG0HOFS = 0;
    REG_BG0VOFS = 0;
    DmaFill16(3, 0, (void *)VRAM, VRAM_SIZE);
    DmaFill32(3, 0, (void *)OAM, OAM_SIZE);
    DmaFill16(3, 0, (void *)(PLTT + 2), PLTT_SIZE - 2);
    ResetPaletteFade();
    ResetTasks();
    ResetSpriteData();
    ime = REG_IME;
    REG_IME = 0;
    REG_IE |= INTR_FLAG_VBLANK;
    REG_IME = ime;
    SetVBlankCallback(VBlankCB);
    SetMainCallback2(sub_810CC54);
    if (!sub_810CD5C())
        SetMainCallback2(gMain.savedCallback);
}

static bool8 sub_810CD5C(void)
{
    u8 i;
    u8 prevMapType = GetLastUsedWarpMapType();
    u8 curMapType = Overworld_GetMapTypeOfSaveblockLocation();

    for (i = 0; gMapTypeFadePairs[i].mapTypeA; i++)
    {
        if (gMapTypeFadePairs[i].mapTypeA == prevMapType && gMapTypeFadePairs[i].mapTypeB == curMapType)
        {
            gMapTypeFadePairs[i].func();
            return TRUE;
        }
    }

    return FALSE;
}

u8 GetMapPairFadeToType(u8 currentMapType, u8 destinationMapType)
{
    u8 i;
    u8 curMapType = currentMapType;
    u8 destMapType = destinationMapType;

    for (i = 0; gMapTypeFadePairs[i].mapTypeA; i++)
    {
        if (gMapTypeFadePairs[i].mapTypeA == curMapType && gMapTypeFadePairs[i].mapTypeB == destMapType)
            return gMapTypeFadePairs[i].fadeToType;
    }

    return FALSE;
}

u8 GetMapPairFadeFromType(u8 currentMapType, u8 destinationMapType)
{
    u8 i;
    u8 curMapType = currentMapType;
    u8 destMapType = destinationMapType;

    for (i = 0; gMapTypeFadePairs[i].mapTypeA; i++)
    {
        if (gMapTypeFadePairs[i].mapTypeA == curMapType && gMapTypeFadePairs[i].mapTypeB == destMapType)
        {
            return gMapTypeFadePairs[i].fadeFromType;
        }
    }

    return FALSE;
}

static void CreateExitUndergroundTask(void)
{
    CreateTask(sub_810CE5C, 0);
}

static void sub_810CE5C(u8 taskId)
{
    gTasks[taskId].func = sub_810CE78;
}

static void sub_810CE78(u8 taskId)
{
    REG_DISPCNT = 0;
    LZ77UnCompVram(gCaveTransitionTiles, (void *)(VRAM + 0xC000));
    LZ77UnCompVram(gCaveTransitionTilemap, (void *)(VRAM + 0xF800));
    LoadPalette(gCaveTransitionPalette_White, 0xE0, 0x20);
    LoadPalette(gUnknown_083F809C, 0xE0, 0x10);
    REG_BLDCNT = 15937;
    REG_BLDALPHA = 0;
    REG_BLDY = 0;
    REG_BG0CNT = 7948;
    REG_DISPCNT = 4416;
    gTasks[taskId].func = sub_810CF18;
    gTasks[taskId].data[0] = 16;
    gTasks[taskId].data[1] = 0;
}

static void sub_810CF18(u8 taskId)
{
    u16 count = gTasks[taskId].data[1];
    u16 blend = count + 0x1000;
    REG_BLDALPHA = blend;
    if (count <= 0x10)
    {
        gTasks[taskId].data[1]++;
    }
    else
    {
        gTasks[taskId].data[2] = 0;
        gTasks[taskId].func = sub_810CF5C;
    }
}

static void sub_810CF5C(u8 taskId)
{
    u16 count;

    REG_BLDALPHA = 4112;

    count = gTasks[taskId].data[2];

    if (count < 8)
    {
        gTasks[taskId].data[2]++;
        LoadPalette(&gUnknown_083F809C[count], 0xE0, 16 - 2 * count);
    }
    else
    {
        LoadPalette(gCaveTransitionPalette_White, 0, 0x20);
        gTasks[taskId].func = sub_810CFC4;
        gTasks[taskId].data[2] = 8;
    }
}

static void sub_810CFC4(u8 taskId)
{
    if (gTasks[taskId].data[2])
        gTasks[taskId].data[2]--;
    else
        SetMainCallback2(gMain.savedCallback);
}

static void CreateEnterUndergroundEffectTask(void)
{
    CreateTask(sub_810D00C, 0);
}

static void sub_810D00C(u8 taskId)
{
    gTasks[taskId].func = sub_810D028;
}

static void sub_810D028(u8 taskId)
{
    REG_DISPCNT = 0;
    LZ77UnCompVram(gCaveTransitionTiles, (void *)(VRAM + 0xC000));
    LZ77UnCompVram(gCaveTransitionTilemap, (void *)(VRAM + 0xF800));
    REG_BLDCNT = 0;
    REG_BLDALPHA = 0;
    REG_BLDY = 0;
    REG_BG0CNT = 7948;
    REG_DISPCNT = 4416;
    LoadPalette(gCaveTransitionPalette_White, 0xE0, 0x20);
    LoadPalette(gCaveTransitionPalette_Black, 0, 0x20);
    gTasks[taskId].func = sub_810D0C4;
    gTasks[taskId].data[0] = 16;
    gTasks[taskId].data[1] = 0;
    gTasks[taskId].data[2] = 0;
}

static void sub_810D0C4(u8 taskId)
{
    u16 count = gTasks[taskId].data[2];
    if (count < 16)
    {
        gTasks[taskId].data[2]++;
        gTasks[taskId].data[2]++;
        LoadPalette(&gUnknown_083F808C[15 - count], 0xE0, 2 * (count + 1));
    }
    else
    {
        REG_BLDALPHA = 4112;
        REG_BLDCNT = 15937;
        gTasks[taskId].func = sub_810D128;
    }
}

static void sub_810D128(u8 taskId)
{
    u16 count = 16 - gTasks[taskId].data[1];
    u16 blend = count + 0x1000;
    REG_BLDALPHA = blend;
    if (count)
    {
        gTasks[taskId].data[1]++;
    }
    else
    {
        LoadPalette(gCaveTransitionPalette_Black, 0, 0x20);
        SetMainCallback2(gMain.savedCallback);
    }
}
