#include "global.h"
#include "pokenav.h"
#include "event_data.h"
#include "main.h"
#include "sound.h"
#include "constants/songs.h"

struct Pokenav_Menu
{
    u16 menuType;
    s16 cursorPos;
    u16 currMenuItem;
    u16 helpBarIndex;
    u32 menuId;
    u32 (*callback)(struct Pokenav_Menu*);
};

static bool32 UpdateMenuCursorPos(struct Pokenav_Menu *);
static void ReturnToConditionMenu(struct Pokenav_Menu *);
static void ReturnToMainMenu(struct Pokenav_Menu *);
static u32 GetMenuId(struct Pokenav_Menu *);
static void SetMenuIdAndCB(struct Pokenav_Menu *, u32);
static u32 CB2_ReturnToConditionMenu(struct Pokenav_Menu *);
static u32 CB2_ReturnToMainMenu(struct Pokenav_Menu *);
static u32 HandleConditionSearchMenuInput(struct Pokenav_Menu *);
static u32 HandleConditionMenuInput(struct Pokenav_Menu *);
static u32 HandleCantOpenRibbonsInput(struct Pokenav_Menu *);
static u32 HandleMainMenuInputEndTutorial(struct Pokenav_Menu *);
static u32 HandleMainMenuInputTutorial(struct Pokenav_Menu *);
static u32 HandleMainMenuInput(struct Pokenav_Menu *);
static u32 (*GetMainMenuInputHandler(void))(struct Pokenav_Menu*);
static void SetMenuInputHandler(struct Pokenav_Menu *);

// Number of entries - 1 for that menu type
static const u8 sLastCursorPositions[] =
{
    [POKENAV_MENU_TYPE_DEFAULT]           = 2,
    [POKENAV_MENU_TYPE_UNLOCK_MC]         = 3,
    [POKENAV_MENU_TYPE_UNLOCK_MC_RIBBONS] = 4,
    [POKENAV_MENU_TYPE_CONDITION]         = 2,
    [POKENAV_MENU_TYPE_CONDITION_SEARCH]  = 5
};

static const u8 sMenuItems[][MAX_POKENAV_MENUITEMS] =
{
    [POKENAV_MENU_TYPE_DEFAULT] =
    {
        POKENAV_MENUITEM_MAP,
        POKENAV_MENUITEM_CONDITION,
        [2 ... MAX_POKENAV_MENUITEMS - 1] = POKENAV_MENUITEM_SWITCH_OFF
    },
    [POKENAV_MENU_TYPE_UNLOCK_MC] =
    {
        POKENAV_MENUITEM_MAP,
        POKENAV_MENUITEM_CONDITION,
        POKENAV_MENUITEM_MATCH_CALL,
        [3 ... MAX_POKENAV_MENUITEMS - 1] = POKENAV_MENUITEM_SWITCH_OFF
    },
    [POKENAV_MENU_TYPE_UNLOCK_MC_RIBBONS] =
    {
        POKENAV_MENUITEM_MAP,
        POKENAV_MENUITEM_CONDITION,
        POKENAV_MENUITEM_MATCH_CALL,
        POKENAV_MENUITEM_RIBBONS,
        [4 ... MAX_POKENAV_MENUITEMS - 1] = POKENAV_MENUITEM_SWITCH_OFF
    },
    [POKENAV_MENU_TYPE_CONDITION] =
    {
        POKENAV_MENUITEM_CONDITION_PARTY,
        POKENAV_MENUITEM_CONDITION_SEARCH,
        POKENAV_MENUITEM_CONDITION_CANCEL,
        [3 ... MAX_POKENAV_MENUITEMS - 1] = POKENAV_MENUITEM_SWITCH_OFF
    },
    [POKENAV_MENU_TYPE_CONDITION_SEARCH] =
    {
        POKENAV_MENUITEM_CONDITION_SEARCH_COOL,
        POKENAV_MENUITEM_CONDITION_SEARCH_BEAUTY,
        POKENAV_MENUITEM_CONDITION_SEARCH_CUTE,
        POKENAV_MENUITEM_CONDITION_SEARCH_SMART,
        POKENAV_MENUITEM_CONDITION_SEARCH_TOUGH,
        POKENAV_MENUITEM_CONDITION_SEARCH_CANCEL
    },
};

static u8 GetPokenavMainMenuType(void)
{
    u8 menuType = POKENAV_MENU_TYPE_DEFAULT;

    if (FlagGet(FLAG_ADDED_MATCH_CALL_TO_POKENAV))
    {
        menuType = POKENAV_MENU_TYPE_UNLOCK_MC;

        if (FlagGet(FLAG_SYS_RIBBON_GET))
            menuType = POKENAV_MENU_TYPE_UNLOCK_MC_RIBBONS;
    }

    return menuType;
}

bool32 PokenavCallback_Init_MainMenuCursorOnMap(void)
{
    struct Pokenav_Menu *menu = AllocSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER, sizeof(struct Pokenav_Menu));
    if (!menu)
        return FALSE;

    menu->menuType = GetPokenavMainMenuType();
    menu->cursorPos = POKENAV_MENUITEM_MAP;
    menu->currMenuItem = POKENAV_MENUITEM_MAP;
    menu->helpBarIndex = HELPBAR_NONE;
    SetMenuInputHandler(menu);
    return TRUE;
}

bool32 PokenavCallback_Init_MainMenuCursorOnMatchCall(void)
{
    struct Pokenav_Menu *menu = AllocSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER, sizeof(struct Pokenav_Menu));
    if (!menu)
        return FALSE;

    menu->menuType = GetPokenavMainMenuType();
    menu->cursorPos = POKENAV_MENUITEM_MATCH_CALL;
    menu->currMenuItem = POKENAV_MENUITEM_MATCH_CALL;
    menu->helpBarIndex = HELPBAR_NONE;
    SetMenuInputHandler(menu);
    return TRUE;
}

bool32 PokenavCallback_Init_MainMenuCursorOnRibbons(void)
{
    struct Pokenav_Menu *menu = AllocSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER, sizeof(struct Pokenav_Menu));
    if (!menu)
        return FALSE;

    menu->menuType = GetPokenavMainMenuType();
    menu->cursorPos = POKENAV_MENUITEM_RIBBONS;
    menu->currMenuItem = POKENAV_MENUITEM_RIBBONS;
    SetMenuInputHandler(menu);
    return TRUE;
}

bool32 PokenavCallback_Init_ConditionMenu(void)
{
    struct Pokenav_Menu *menu = AllocSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER, sizeof(struct Pokenav_Menu));
    if (!menu)
        return FALSE;

    menu->menuType = POKENAV_MENU_TYPE_CONDITION;
    menu->cursorPos = 0;   //party
    menu->currMenuItem = POKENAV_MENUITEM_CONDITION_PARTY;
    menu->helpBarIndex = HELPBAR_NONE;
    SetMenuInputHandler(menu);
    return TRUE;
}

bool32 PokenavCallback_Init_ConditionSearchMenu(void)
{
    struct Pokenav_Menu *menu = AllocSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER, sizeof(struct Pokenav_Menu));
    if (!menu)
        return FALSE;

    menu->menuType = POKENAV_MENU_TYPE_CONDITION_SEARCH;
    menu->cursorPos = GetSelectedConditionSearch();
    menu->currMenuItem = menu->cursorPos + POKENAV_MENUITEM_CONDITION_SEARCH_COOL;
    menu->helpBarIndex = HELPBAR_NONE;
    SetMenuInputHandler(menu);
    return TRUE;
}

static void SetMenuInputHandler(struct Pokenav_Menu *menu)
{
    switch (menu->menuType)
    {
    case POKENAV_MENU_TYPE_DEFAULT:
        SetPokenavMode(POKENAV_MODE_NORMAL);
        // fallthrough
    case POKENAV_MENU_TYPE_UNLOCK_MC:
    case POKENAV_MENU_TYPE_UNLOCK_MC_RIBBONS:
        menu->callback = GetMainMenuInputHandler();
        break;
    case POKENAV_MENU_TYPE_CONDITION:
        menu->callback = HandleConditionMenuInput;
        break;
    case POKENAV_MENU_TYPE_CONDITION_SEARCH:
        menu->callback = HandleConditionSearchMenuInput;
        break;
    }
}

static u32 (*GetMainMenuInputHandler(void))(struct Pokenav_Menu*)
{
    switch (GetPokenavMode())
    {
    default:
    case POKENAV_MODE_NORMAL:
        return HandleMainMenuInput;
    case POKENAV_MODE_FORCE_CALL_READY:
        return HandleMainMenuInputTutorial;
    case POKENAV_MODE_FORCE_CALL_EXIT:
        return HandleMainMenuInputEndTutorial;
    }
}

u32 GetMenuHandlerCallback(void)
{
    struct Pokenav_Menu *menu = GetSubstructPtr(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
    return menu->callback(menu);
}

void FreeMenuHandlerSubstruct1(void)
{
    FreePokenavSubstruct(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
}

static u32 HandleMainMenuInput(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
        return POKENAV_MENU_FUNC_MOVE_CURSOR;

    if (JOY_NEW(A_BUTTON))
    {
        switch (sMenuItems[menu->menuType][menu->cursorPos])
        {
        case POKENAV_MENUITEM_MAP:
            menu->helpBarIndex = gSaveBlock2Ptr->regionMapZoom ? HELPBAR_MAP_ZOOMED_IN : HELPBAR_MAP_ZOOMED_OUT;
            SetMenuIdAndCB(menu, POKENAV_REGION_MAP);
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        case POKENAV_MENUITEM_CONDITION:
            menu->menuType = POKENAV_MENU_TYPE_CONDITION;
            menu->cursorPos = 0;
            menu->currMenuItem = sMenuItems[POKENAV_MENU_TYPE_CONDITION][0];
            menu->callback = HandleConditionMenuInput;
            return POKENAV_MENU_FUNC_OPEN_CONDITION;
        case POKENAV_MENUITEM_MATCH_CALL:
            menu->helpBarIndex = HELPBAR_MC_TRAINER_LIST;
            SetMenuIdAndCB(menu, POKENAV_MATCH_CALL);
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        case POKENAV_MENUITEM_RIBBONS:
            if (CanViewRibbonsMenu())
            {
                menu->helpBarIndex = HELPBAR_RIBBONS_MON_LIST;
                SetMenuIdAndCB(menu, POKENAV_RIBBONS_MON_LIST);
                return POKENAV_MENU_FUNC_OPEN_FEATURE;
            }
            else
            {
                menu->callback = HandleCantOpenRibbonsInput;
                return POKENAV_MENU_FUNC_NO_RIBBON_WINNERS;
            }
        case POKENAV_MENUITEM_SWITCH_OFF:
            return POKENAV_MENU_FUNC_EXIT;
        }
    }

    if (JOY_NEW(B_BUTTON))
        return POKENAV_MENU_FUNC_EXIT;

    return POKENAV_MENU_FUNC_NONE;
}

// Force the player to select Match Call during the call Mr. Stone pokenav tutorial
static u32 HandleMainMenuInputTutorial(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
        return POKENAV_MENU_FUNC_MOVE_CURSOR;

    if (JOY_NEW(A_BUTTON))
    {
        if (sMenuItems[menu->menuType][menu->cursorPos] == POKENAV_MENUITEM_MATCH_CALL)
        {
            menu->helpBarIndex = HELPBAR_MC_TRAINER_LIST;
            SetMenuIdAndCB(menu, POKENAV_MATCH_CALL);
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        }
        else
        {
            PlaySE(SE_FAILURE);
            return POKENAV_MENU_FUNC_NONE;
        }
    }

    if (JOY_NEW(B_BUTTON))
    {
        PlaySE(SE_FAILURE);
        return POKENAV_MENU_FUNC_NONE;
    }

    return POKENAV_MENU_FUNC_NONE;
}

// After calling Mr. Stone during the pokenav tutorial, force player to exit or use Match Call again
static u32 HandleMainMenuInputEndTutorial(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
        return POKENAV_MENU_FUNC_MOVE_CURSOR;

    if (JOY_NEW(A_BUTTON))
    {
        u32 menuItem = sMenuItems[menu->menuType][menu->cursorPos];
        if (menuItem != POKENAV_MENUITEM_MATCH_CALL && menuItem != POKENAV_MENUITEM_SWITCH_OFF)
        {
            PlaySE(SE_FAILURE);
            return POKENAV_MENU_FUNC_NONE;
        }
        else if (menuItem == POKENAV_MENUITEM_MATCH_CALL)
        {
            menu->helpBarIndex = HELPBAR_MC_TRAINER_LIST;
            SetMenuIdAndCB(menu, POKENAV_MATCH_CALL);
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        }
        else
        {
            return -1;
        }
    }
    else if (JOY_NEW(B_BUTTON))
    {
        return -1;
    }
    return POKENAV_MENU_FUNC_NONE;
}

// Handles input after selecting Ribbons when there are no ribbon winners left
// Selecting it again just reprints the Ribbon description to replace the "No Ribbon winners" message
static u32 HandleCantOpenRibbonsInput(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
    {
        menu->callback = GetMainMenuInputHandler();
        return POKENAV_MENU_FUNC_MOVE_CURSOR;
    }

    if (JOY_NEW(A_BUTTON | B_BUTTON))
    {
        menu->callback = GetMainMenuInputHandler();
        return POKENAV_MENU_FUNC_RESHOW_DESCRIPTION;
    }

    return POKENAV_MENU_FUNC_NONE;
}

static u32 HandleConditionMenuInput(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
        return POKENAV_MENU_FUNC_MOVE_CURSOR;

    if (JOY_NEW(A_BUTTON))
    {
        switch (sMenuItems[menu->menuType][menu->cursorPos])
        {
        case POKENAV_MENUITEM_CONDITION_SEARCH:
            menu->menuType = POKENAV_MENU_TYPE_CONDITION_SEARCH;
            menu->cursorPos = 0;
            menu->currMenuItem = sMenuItems[POKENAV_MENU_TYPE_CONDITION_SEARCH][0];
            menu->callback = HandleConditionSearchMenuInput;
            return POKENAV_MENU_FUNC_OPEN_CONDITION_SEARCH;
        case POKENAV_MENUITEM_CONDITION_PARTY:
            menu->helpBarIndex = 0;
            SetMenuIdAndCB(menu, POKENAV_CONDITION_GRAPH_PARTY);
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        case POKENAV_MENUITEM_CONDITION_CANCEL:
            PlaySE(SE_SELECT);
            ReturnToMainMenu(menu);
            return POKENAV_MENU_FUNC_RETURN_TO_MAIN;
        }
    }
    if (JOY_NEW(B_BUTTON))
    {
        if (menu->cursorPos != sLastCursorPositions[menu->menuType])
        {
            menu->cursorPos = sLastCursorPositions[menu->menuType];
            menu->callback = CB2_ReturnToMainMenu;
            return POKENAV_MENU_FUNC_MOVE_CURSOR;
        }
        else
        {
            PlaySE(SE_SELECT);
            ReturnToMainMenu(menu);
            return POKENAV_MENU_FUNC_RETURN_TO_MAIN;
        }
    }

    return POKENAV_MENU_FUNC_NONE;
}

static u32 HandleConditionSearchMenuInput(struct Pokenav_Menu *menu)
{
    if (UpdateMenuCursorPos(menu))
        return POKENAV_MENU_FUNC_MOVE_CURSOR;

    if (JOY_NEW(A_BUTTON))
    {
        u8 menuItem = sMenuItems[menu->menuType][menu->cursorPos];
        if (menuItem != POKENAV_MENUITEM_CONDITION_SEARCH_CANCEL)
        {
            SetSelectedConditionSearch(menuItem - POKENAV_MENUITEM_CONDITION_SEARCH_COOL);
            SetMenuIdAndCB(menu, POKENAV_CONDITION_SEARCH_RESULTS);
            menu->helpBarIndex = HELPBAR_CONDITION_MON_LIST;
            return POKENAV_MENU_FUNC_OPEN_FEATURE;
        }
        else
        {
            PlaySE(SE_SELECT);
            ReturnToConditionMenu(menu);
            return POKENAV_MENU_FUNC_RETURN_TO_CONDITION;
        }
    }
    if (JOY_NEW(B_BUTTON))
    {
        if (menu->cursorPos != sLastCursorPositions[menu->menuType])
        {
            menu->cursorPos = sLastCursorPositions[menu->menuType];
            menu->callback = CB2_ReturnToConditionMenu;
            return POKENAV_MENU_FUNC_MOVE_CURSOR;
        }
        else
        {
            PlaySE(SE_SELECT);
            ReturnToConditionMenu(menu);
            return POKENAV_MENU_FUNC_RETURN_TO_CONDITION;
        }
    }
    return POKENAV_MENU_FUNC_NONE;
}

static u32 CB2_ReturnToMainMenu(struct Pokenav_Menu *menu)
{
    ReturnToMainMenu(menu);
    return POKENAV_MENU_FUNC_RETURN_TO_MAIN;
}

static u32 CB2_ReturnToConditionMenu(struct Pokenav_Menu *menu)
{
    ReturnToConditionMenu(menu);
    return POKENAV_MENU_FUNC_RETURN_TO_CONDITION;
}

static void SetMenuIdAndCB(struct Pokenav_Menu *menu, u32 menuId)
{
    menu->menuId = menuId;
    menu->callback = GetMenuId;
}

static u32 GetMenuId(struct Pokenav_Menu *menu)
{
    return menu->menuId;
}

static void ReturnToMainMenu(struct Pokenav_Menu *menu)
{
    menu->menuType = GetPokenavMainMenuType();
    menu->cursorPos = 1;
    menu->currMenuItem = sMenuItems[menu->menuType][menu->cursorPos];
    menu->callback = HandleMainMenuInput;
}

static void ReturnToConditionMenu(struct Pokenav_Menu *menu)
{
    menu->menuType = POKENAV_MENU_TYPE_CONDITION;
    menu->cursorPos = 1;
    menu->currMenuItem = sMenuItems[POKENAV_MENU_TYPE_CONDITION][1];
    menu->callback = HandleConditionMenuInput;
}

static bool32 UpdateMenuCursorPos(struct Pokenav_Menu *menu)
{
    if (JOY_NEW(DPAD_UP))
    {
        if (--menu->cursorPos < 0)
            menu->cursorPos = sLastCursorPositions[menu->menuType];

        menu->currMenuItem = sMenuItems[menu->menuType][menu->cursorPos];
        return TRUE;
    }
    else if (JOY_NEW(DPAD_DOWN))
    {
        menu->cursorPos++;
        if (menu->cursorPos > sLastCursorPositions[menu->menuType])
            menu->cursorPos = 0;

        menu->currMenuItem = sMenuItems[menu->menuType][menu->cursorPos];
        return TRUE;
    }
    else
    {
        return FALSE;
    }
}

int GetPokenavMenuType(void)
{
    struct Pokenav_Menu *menu = GetSubstructPtr(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
    return menu->menuType;
}

// Position of cursor relative to number of current menu options
int GetPokenavCursorPos(void)
{
    struct Pokenav_Menu *menu = GetSubstructPtr(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
    return menu->cursorPos;
}

// ID of menu item the cursor is currently on
int GetCurrentMenuItemId(void)
{
    struct Pokenav_Menu *menu = GetSubstructPtr(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
    return menu->currMenuItem;
}

u16 GetHelpBarTextId(void)
{
    struct Pokenav_Menu *menu = GetSubstructPtr(POKENAV_SUBSTRUCT_MAIN_MENU_HANDLER);
    return menu->helpBarIndex;
}
