#include "global.h"
#include "bike.h"
#include "event_data.h"
#include "field_camera.h"
#include "field_effect_helpers.h"
#include "field_player_avatar.h"
#include "fieldmap.h"
#include "metatile_behavior.h"
#include "overworld.h"
#include "quest_log.h"
#include "script.h"
#include "sound.h"
#include "task.h"
#include "constants/flags.h"
#include "constants/songs.h"
#include "constants/vars.h"

static void DummyPerStepCallback(u8 taskId);
static void AshGrassPerStepCallback(u8 taskId);
static void IcefallCaveIcePerStepCallback(u8 taskId);
static void CrackedFloorPerStepCallback(u8 taskId);

static const TaskFunc sPerStepCallbacks[] =
{
    DummyPerStepCallback,
    AshGrassPerStepCallback,
    DummyPerStepCallback,
    DummyPerStepCallback,
    IcefallCaveIcePerStepCallback,
    DummyPerStepCallback,
    DummyPerStepCallback,
    CrackedFloorPerStepCallback
};

static const u8 sIcefallCaveIceTileCoords[][2] =
{
    { 0x08, 0x03 },
    { 0x0a, 0x05 },
    { 0x0f, 0x05 },
    { 0x08, 0x09 },
    { 0x09, 0x09 },
    { 0x10, 0x09 },
    { 0x08, 0x0a },
    { 0x09, 0x0a },
    { 0x08, 0x0e }
};

static void Task_RunPerStepCallback(u8 taskId)
{
    int idx = gTasks[taskId].data[0];
    sPerStepCallbacks[idx](taskId);
}

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

    if (!ScriptContext2_IsEnabled())
    {
        if (gQuestLogState != 2 && gQuestLogState != 3)
        {
            sub_8056078(&data[1], &data[2]);
        }
    }
}

void SetUpFieldTasks(void)
{
    if (!FuncIsActiveTask(Task_RunPerStepCallback))
    {
        u8 taskId = CreateTask(Task_RunPerStepCallback, 0x50);
        gTasks[taskId].data[0] = 0;
    }

    if (!FuncIsActiveTask(Task_RunTimeBasedEvents))
        CreateTask(Task_RunTimeBasedEvents, 0x50);
}

void ActivatePerStepCallback(u8 callbackId)
{
    u8 taskId = FindTaskIdByFunc(Task_RunPerStepCallback);
    if (taskId != 0xff)
    {
        s32 i;
        s16 *data = gTasks[taskId].data;

        for (i = 0; i < 16; i++)
            data[i] = 0;

        if (callbackId >= NELEMS(sPerStepCallbacks))
        {
            data[0] = 0;
        }
        else
        {
            data[0] = callbackId;
        }
    }
}

void ResetFieldTasksArgs(void)
{
    u8 taskId;
    s16 *data;

    taskId = FindTaskIdByFunc(Task_RunPerStepCallback);
    if (taskId != 0xff)
    {
        data = gTasks[taskId].data;
    }
    taskId = FindTaskIdByFunc(Task_RunTimeBasedEvents);
    if (taskId != 0xff)
    {
        data = gTasks[taskId].data;
        data[1] = 0;
        data[2] = 0;
    }
}

static void DummyPerStepCallback(u8 taskId)
{
}

static void MarkIcefallCaveCoordVisited(s16 x, s16 y)
{
    u8 i = 0;
    for (; i < NELEMS(sIcefallCaveIceTileCoords); ++i)
    {
        if (sIcefallCaveIceTileCoords[i][0] + 7 == x && sIcefallCaveIceTileCoords[i][1] + 7 == y)
        {
            FlagSet(i + 1);
            break;
        }
    }
}

void Special_SetIcefallCaveCrackedIceMetatiles(void)
{
    u8 i = 0;
    for (; i < NELEMS(sIcefallCaveIceTileCoords); ++i)
    {
        if (FlagGet(i + 1) == TRUE)
        {
            int x = sIcefallCaveIceTileCoords[i][0] + 7;
            int y = sIcefallCaveIceTileCoords[i][1] + 7;
            MapGridSetMetatileIdAt(x, y, 0x35a);
        }
    }
}

static void IcefallCaveIcePerStepCallback(u8 taskId)
{
    s16 x, y;
    u8 tileBehavior;
    u16 *iceStepCount;
    s16 *data = gTasks[taskId].data;
    switch (data[1])
    {
        case 0:
            PlayerGetDestCoords(&x, &y);
            data[2] = x;
            data[3] = y;
            data[1] = 1;
            break;
        case 1:
            PlayerGetDestCoords(&x, &y);
            if (x != data[2] || y != data[3])
            {
                data[2] = x;
                data[3] = y;
                tileBehavior = MapGridGetMetatileBehaviorAt(x, y);
                if (MetatileBehavior_IsThinIce(tileBehavior) == TRUE)
                {
                    MarkIcefallCaveCoordVisited(x, y);
                    data[6] = 4;
                    data[1] = 2;
                    data[4] = x;
                    data[5] = y;
                }
                else if (MetatileBehavior_IsCrackedIce(tileBehavior) == TRUE)
                {
                    data[6] = 4;
                    data[1] = 3;
                    data[4] = x;
                    data[5] = y;
                }
            }
            break;
        case 2:
            if (data[6] != 0)
            {
                data[6]--;
            }
            else
            {
                x = data[4];
                y = data[5];
                PlaySE(SE_RU_BARI);
                MapGridSetMetatileIdAt(x, y, 0x35a);
                CurrentMapDrawMetatileAt(x, y);
                data[1] = 1;
            }
            break;
        case 3:
            if (data[6] != 0)
            {
                data[6]--;
            }
            else
            {
                x = data[4];
                y = data[5];
                PlaySE(SE_RU_GASYAN);
                MapGridSetMetatileIdAt(x, y, 0x35b);
                CurrentMapDrawMetatileAt(x, y);
                VarSet(VAR_TEMP_1, 1);
                data[1] = 1;
            }
            break;
    }
}

// This is leftover from pokeruby and effectively a no-op.
static void AshGrassPerStepCallback(u8 taskId)
{
    s16 x, y;
    u16 *ashGatherCount;
    s16 *data = gTasks[taskId].data;
    PlayerGetDestCoords(&x, &y);
    if (x != data[1] || y != data[2])
    {
        data[1] = x;
        data[2] = y;
        if (MetatileBehavior_ReturnFalse_4((u8)MapGridGetMetatileBehaviorAt(x, y)))
        {
            if (MapGridGetMetatileIdAt(x, y) == 0x20a)
                StartAshFieldEffect(x, y, 0x212, 4);
            else
                StartAshFieldEffect(x, y, 0x206, 4);
        }
    }
}

static void SetCrackedFloorHoleMetatile(s16 x, s16 y)
{
    MapGridSetMetatileIdAt(x, y, MapGridGetMetatileIdAt(x, y) == 0x22f ? 0x206 : 0x237);
    CurrentMapDrawMetatileAt(x, y);
}

// This is leftover from pokeruby and effectively a no-op.
static void CrackedFloorPerStepCallback(u8 taskId)
{
    s16 x, y;
    u16 behavior;
    s16 *data = gTasks[taskId].data;
    PlayerGetDestCoords(&x, &y);
    behavior = MapGridGetMetatileBehaviorAt(x, y);
    if (data[4] != 0 && (--data[4]) == 0)
        SetCrackedFloorHoleMetatile(data[5], data[6]);

    if (data[7] != 0 && (--data[7]) == 0)
        SetCrackedFloorHoleMetatile(data[8], data[9]);

    if ((x != data[2] || y != data[3]))
    {
        data[2] = x;
        data[3] = y;
        if (MetatileBehavior_ReturnFalse_13(behavior))
        {
            if (GetPlayerSpeed() != 4)
                VarSet(VAR_ICE_STEP_COUNT, 0);

            if (data[4] == 0)
            {
                data[4] = 3;
                data[5] = x;
                data[6] = y;
            }
            else if (data[7] == 0)
            {
                data[7] = 3;
                data[8] = x;
                data[9] = y;
            }
        }
    }
}

static void sub_806ED38(void)
{
    FlagSet(FLAG_SYS_POKEDEX_GET);
    FlagSet(FLAG_SYS_POKEMON_GET);
}
