/*Menu.c*/
/*
#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
*/

using namespace std;
#include <iostream>

#include "merc.h"
#include "interp.h"
#include "recycle.h"

/*Stuff:
    menu_list is the prototype list of menus
*/

MENU_DATA *menu_free;
MENU_DATA *menu_list;
OPTION_DATA *option_free;
int top_menu;

//local for now
OPTION_DATA *get_option(MENU_DATA *menu, int whichopt);


/*************************************************************************
Steps to making a new menu:
MENU_DATA *menu = new_menu(); or
MENU_DATA *menu = new_menu("name");

Parameters:
int    id         --unused
int    menu_flags --unused for now
DO_FUN *fun_from  --What function called the menu
DO_FUN *do_fun    --Menus can call functions, but options usually do
char   *text      --General text--typically used for meat of menu
char   *opening   --If new menu is displayed for char, display/use this message (useful for acts)
char   *closing   --If menu is closed/quit from, display/use this message (useful for acts)

Add options:
OPTION_DATA *option = new_option();

Optional parameters:
int    key       -- Almost required, when presented with a menu, this is the
                    usually the value which is compared against your reply.
char   *args[5]  -- Use depending on menu.
DO_FUN *fun_call -- A function to be called when the option is selected

*************************************************************************/

//All the code for exec_menu is tentative.
//needs to be reworked, below is just, um, nonoperative, but OK.
//sit down with some paper and figure it out.
//since many menus are actually made in-code, I don't see how many
//menus will be called.  There's going to have to be some 'hardcoded'
//menus.
//Thinking update: Menus 'in code' will have to use fun calls, not menus.
void exec_menu (CHAR_DATA *ch, MENU_DATA *menu, int foo, char *argument)
{
    OPTION_DATA *option;
    bool mRemove = true;

    if (IS_NPC(ch)) return;

    //reminder: foo is 1-biased, code elsewhere treats it as 1-biased and converts to 0-biased

    ch_printf(ch, "menu.c %d: Exec_menu called\n\r", __LINE__);
    ch_printf(ch, "{GFoo: %d {CArgument: %s{x\n\r", foo, argument);

    //find out what to do: foo will be negative sometimes; if that's the case, use
    //the argument to snatch a special command.

    if (foo <= 0)
    {
        if (!is_number(argument))
        {
            switch (toupper(*argument))
            {
                case '?':
                    show_menu(ch, menu, TRUE);
                    break;
                case 'X':
                case 'Q':
                    //Later, when menus are events, just set timer to 1 or 0, that
                    //way the event that called the menu set (i.e. a conversation
                    //event or a skill edit event) can have a destroy funct
                    //save the table, edit the player's char, etc.
                    //menu_from_char(ch, menu);
                    //have to be careful with the below :) JH 6/10/2004 11:36PM
                    if (IS_SET(menu->menu_flags, MENU_NOEXIT))
                    {
                        ch_printf(ch, "Sorry, you cannot exit out of this menu.  A response is required.\n\r");
                        return;
                    }
                    
                    //fixme bug (unavoidable), noticed 6/17/2004 12:17AM:
                    //
                    //you can spam 'x' like 4 times before the event actually boots you.
                    //is this fixable without being hackish?
                    //is it worth fixing?
                    //force the menu to non-phoenix..kind of sloppy? JH 6/10/2004 11:33PM
                        //ch_printf(ch, "Exiting '%s'.\n\r", menu->name);
                    REMOVE_BIT(menu->menu_flags, MENU_PHOENIX);
                    menu->ev->delay = 0; //potentially crashy  6/10/2004 11:19PM not anymore?
                    break;
                default:
                    ch_printf(ch, "Invalid response.  Try 'Q' to quit, '?' to see your options.\n\r");
            }
            return;
        }
        else
        {
            foo = atoi(argument);
        }
    }

    if (menu->fun_call)
        do_function(ch, menu->fun_call, argument);
    else
    {
        if ((option = get_option(menu, foo)) == NULL)
        {
            //invalid choice code, don't free.  Person just typed in shit.
            ch_printf(ch, "Invalid response.  Try 'Q' to quit, '?' to see your options.\n\r");
            mRemove = false;
        }
        else
        {
            if (option->fun_call)
                do_function(ch, option->fun_call, argument);
            else if (option->menu_call)
            {
                menu_to_char(ch, option->menu_call);
                ch->pcdata->menu_locked = option->menu_call;
                show_menu(ch, option->menu_call);
            }
            else
            {
                //nothing to do error
                mRemove = false;
            }
        }
    }

    if (mRemove)
    {
        if(IS_SET(menu->menu_flags, MENU_PHOENIX))
            menu->ev->delay = 1; //give the phoenixed menu a little bit to fade.
        else
            menu->ev->delay = 0;
    }
}

OPTION_DATA *get_option(MENU_DATA *menu, int whichopt)
{
    OPTION_DATA *option = NULL;

    if (whichopt < 0)
        return NULL;

    int i = 1; //input from ch is typically 1-biased

    for (option = menu->option; option; option = option->next)
        if (i++ == whichopt)
            break;

    return option;
}

/* stuff for recycling menus */
/*Overloaded new_menu, for when you passing in name of menu*/
MENU_DATA *new_menu (const char *argument)
{
    static MENU_DATA menu_zero;
    MENU_DATA *menu;

    if (menu_free == NULL)
        menu = (MENU_DATA *) alloc_perm (sizeof (*menu));
    else
    {
        menu = menu_free;
        menu_free = menu_free->next;
    }

    *menu = menu_zero;

    VALIDATE (menu);
    menu->name = str_dup(argument);
    /*paranoia*/
    menu->fun_from = NULL;
    menu->fun_call = NULL;
    menu->text = NULL;
    menu->opening = NULL;
    menu->closing = NULL;

    return menu;
}

MENU_DATA *new_menu (void)
{
    static MENU_DATA menu_zero;
    MENU_DATA *menu;

    if (menu_free == NULL)
        menu = (MENU_DATA *) alloc_perm (sizeof (*menu));
    else
    {
        menu = menu_free;
        menu_free = menu_free->next;
    }

    *menu = menu_zero;

    VALIDATE (menu);

    /*paranoia*/
    menu->name = NULL;
    menu->fun_from = NULL;
    menu->fun_call = NULL;
    menu->text = NULL;
    menu->opening = NULL;
    menu->closing = NULL;

    return menu;
}

void free_menu (MENU_DATA * menu)
{
    OPTION_DATA *option, *option_next;

    if (!IS_VALID (menu))
        return;

    free_string(menu->name);
    free_string(menu->text);
    free_string(menu->opening);
    free_string(menu->closing);

    menu->argi = 0;

    INVALIDATE (menu);
    for (option = menu->option; option; option = option_next)
    {
        option_next = option->next;
        free_option(option);
    }

    menu->next = menu_free;
    menu_free = menu;
    return;
}

void free_option (OPTION_DATA * option)
{
    if (!IS_VALID (option))
        return;

    int i;

    for (i = 0; i < 5; i++)
            free_string(option->args[i]);

    option->argi = 0;
    
    INVALIDATE (option);
    option->next = option_free;
    option_free = option;
}

OPTION_DATA *new_option (void)
{
    static OPTION_DATA option_zero;
    OPTION_DATA *option;

    if (option_free == NULL)
        option = (OPTION_DATA *) alloc_perm (sizeof (*option));
    else
    {
        option = option_free;
        option_free = option_free->next;
    }

    *option = option_zero;

    VALIDATE (option);
    /*paranoia*/
    for (int i = 0; i < 5; i++)
        option->args[i] = NULL;
    option->fun_call = NULL;
    option->menu_call = NULL;

    return option;
}

void fiddlesticks()
{
//    int cmd;
    MENU_DATA *menu;

    writelogf("Pointing! (fiddlesticks)");

    menu = new_menu();
    menu->name = str_dup("HC00_do_list");
    menu->fun_call = do_buy; //wtf was I thinking below?
    /*
    for (cmd = 0; cmd_table[cmd].name[0] != '\0'; cmd++)
    {
        if (!str_cmp(cmd_table[cmd].name, "buy"))
        {
            writelogf("Pointing! (fiddlesticks)");
            menu->fun_call = cmd_table[cmd].do_fun;
        }

    }
    */
    menu_to_global(menu);

    return;
}

/*Adds a menu to the global list*/
void menu_to_global (MENU_DATA * menu)
{
    menu->next = menu_list;
    menu_list = menu;
    return;
}

/*Removes a menu from the global list*/
void menu_from_global (MENU_DATA *menu)
{
    if (menu == menu_list)
    {
        menu_list = menu->next;
    }
    else
    {
        MENU_DATA *prev;

        for (prev = menu_list; prev != NULL; prev = prev->next)
        {
            if (prev->next == menu)
            {
                prev->next = menu->next;
                break;
            }
        }

        if (prev == NULL)
        {
            bug ("menu_delete: Menu not found.", 0);
            return;
        }
    }

    free_menu(menu); /*Recycle*/
    return;
}

/*Give a menu to a char*/
void menu_to_char (CHAR_DATA *ch, MENU_DATA * menu)
{
    if (IS_NPC(ch)) return;

    menu->next = ch->pcdata->menu;
    ch->pcdata->menu = menu;

    return;
}


/* Remove an menu from a char.*/
void menu_from_char (CHAR_DATA * ch, MENU_DATA * menu)
{

    if (IS_NPC(ch)) return;

    if (ch->pcdata->menu == NULL)
    {
        bug ("Menu_remove: no menu.", 0);
        return;
    }

    if (menu == ch->pcdata->menu)
    {
        ch->pcdata->menu = menu->next;
    }
    else
    {
        MENU_DATA *prev;

        for (prev = ch->pcdata->menu; prev != NULL; prev = prev->next)
        {
            if (prev->next == menu)
            {
                prev->next = menu->next;
                break;
            }
        }

        if (prev == NULL)
        {
            bug ("Menu_from_char: cannot find menu.", 0);
            return;
        }
    }

    //unlock!
    if (menu == ch->pcdata->menu_locked)
        ch->pcdata->menu_locked = NULL;

//    free_menu (menu);

    return;
}

//HOPE this works...............
void menu_ch_to_world (CHAR_DATA * ch, MENU_DATA * menu)
{

    if (IS_NPC(ch)) return;

    //rewritten, inefficient    JH 6/3/2004 5:04PM
    menu_to_global (menu);
    menu_from_char(ch, menu);

    /*
    if (ch->pcdata->menu == NULL)
    {
        bug ("Menu_remove: no menu.", 0);
        return;
    }

    if (menu == ch->pcdata->menu)
    {
        ch->pcdata->menu = menu->next;
    }
    else
    {
        MENU_DATA *prev;

        for (prev = ch->pcdata->menu; prev != NULL; prev = prev->next)
        {
            if (prev->next == menu)
            {
                prev->next = menu->next;
                break;
            }
        }

        if (prev == NULL)
        {
            bug ("Menu_remove: cannot find menu.", 0);
            return;
        }
    }

    */
}

/* Remove an option from a menu*/
void option_from_menu (MENU_DATA * menu, OPTION_DATA *option)
{

    if (menu->option == NULL)
    {
        bug ("Option_from_menu: no options.", 0);
        return;
    }

    if (option == menu->option)
    {
        menu->option = option->next;
    }
    else
    {
        OPTION_DATA *prev;

        for (prev = menu->option; prev != NULL; prev = prev->next)
        {
            if (prev->next == option)
            {
                prev->next = option->next;
                break;
            }
        }

        if (prev == NULL)
        {
            bug ("Option_from_menu: cannot find option.", 0);
            return;
        }
    }

    free_option (option);

    return;
}

// void append_option
//Tack on an option to a menu.  Similar to push_back() for the STL vector
//class.
//Key is optional.  If specified, the option will be assigned said key.
//Otherwise it'll be assigned a key based on its position.
//(note: show_menu ignores keys at the moment)
void append_option(MENU_DATA * menu, OPTION_DATA * option, int key)
{
    OPTION_DATA *oLast;
    int key_last = 0;

    if (!menu->option)
    {
        menu->option = option;
        return;
    }

    for (oLast = menu->option; oLast; oLast = oLast->next, ++key_last)
    {
        if (oLast->next == NULL)
        {
            (key > 0) ? option->key = key : option->key = key_last + 1;
            oLast->next = option;
            oLast = menu->option;
            menu->option = oLast;
            break;
        }
    }
    return;
}


/*Utility commands - show menu to char, */
//unfinished, IMO (key stuff?)
void show_menu(CHAR_DATA *ch, MENU_DATA *menu, bool ShowKey)
{
    OPTION_DATA *option;
    BUFFER *output;

    char buf[MSL];
    *buf = '\0';
    int i = 0;
    output = new_buf();

    if (!menu)
    {
        ch_printf(ch, "Uhoh! %s:%d, Menu is null.\n\r", __FILE__, __LINE__);
        return;
    }

    //if (IS_NPC(ch)) return;

    //If (menu->opening)

    for (option = menu->option; option; option = option->next)
    {
        sprintf(buf, "{G%2d. {x%s\n\r", ++i, option->args[0]);
        add_buf(output, buf);
    }

    page_to_char (buf_string (output), ch);
    free_buf (output);

    return;
}

//Utility: Find a menu from the list
MENU_DATA *find_menu(const char *argument)
{
    MENU_DATA *menu;

    for (menu = menu_list; menu; menu = menu->next)
        if (!str_cmp(argument, menu->name)) return menu;

    return NULL;
}

MENU_DATA *find_menu_char(CHAR_DATA *ch, const char *argument)
{
    MENU_DATA *menu;

    if (IS_NPC(ch))
        return NULL;

    for (menu = ch->pcdata->menu; menu; menu = menu->next)
        if (!str_cmp(argument, menu->name))
            return menu;

    return NULL;
}

//Utility: New menu's name is okay?
bool bad_menu_name (const char *argument)
{
    MENU_DATA *menu;

    for (menu = menu_list; menu; menu = menu->next)
        if (!str_cmp(argument, menu->name)) return TRUE;

    return FALSE;

}


int create_menu()
{

    return 0;
}

//Lock a menu onto a character.  Having a menu locked to you is like being
//in an OLC editor.
bool menu_lock(CHAR_DATA *ch, MENU_DATA *menu)
{

    if (IS_NPC(ch))
    {
        free_menu(menu);
        return false;
    }

    if (ch->pcdata->menu_locked)
    {
        wiznet(create_string("DEBUG: menu_lock: ch '%s' has menu locked already", ch->name),
                NULL, NULL, WIZ_DEBUG, 0, MAX_LEVEL);
        ch_printf(ch, "{RError: cannot lock menu '%s' to you.  {YPlease note immortal.{x\n\r", menu->name);
        bugf("%s:%d:menu_lock:  Failed locking menu to '%s'.  Freeing menu.",
            __FILE__, __LINE__, ch->name);
        free_menu(menu);
        return false;
    }

    ch->pcdata->menu_locked = menu;

    return true;
}