/*
C-style
#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#include <sys/time.h>
#endif


#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdarg.h>
*/

#include <cstdarg>
#include <iostream>
#include <vector>
using namespace std;

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

//Extern
void append_note args((NOTE_DATA *pnote));


/*Event prototypes*/
/*Print event*/
void    fire_print_event    args((EVENT * ev));
void    destroy_print_event args((EVENT * ev));
/*Wait event*/
void    fire_wait_event    args((EVENT * ev));
void    destroy_wait_event args((EVENT * ev));
/*Save event*/
void    fire_save_event    args((EVENT * ev));
void    destroy_save_event args((EVENT * ev));

/*Poll event*/
void    fire_poll_event    args((EVENT * ev));
void    destroy_poll_event args((EVENT * ev));

/*Prog (delayed) event*/
void    fire_prog_event    args((EVENT * ev));
void    destroy_prog_event args((EVENT * ev));

/*Prog cooldown event*/
void    fire_prog_cooldown_event    args((EVENT * ev));
void    destroy_prog_cooldown_event args((EVENT * ev));

/*Affhandle event*/
void    fire_affhandle_event    args((EVENT * ev));
void    destroy_affhandle_event args((EVENT * ev));

/*Menu event*/
void    fire_menu_event    args((EVENT * ev));
void    destroy_menu_event args((EVENT * ev));

/*
 ******************************** PRINT EVENT ********************************
 * A print event is a delayed send_to_char.
*/
void print_event(CHAR_DATA *ch, int delay, char * fmt, ...)
{
        char buf[2*MSL];
        va_list args;

        va_start(args, fmt);
        vsprintf(buf, fmt, args);
        va_end(args);

        EVENT *ev = new_event("Print event");

        ev->fire = &fire_print_event;
        ev->destroy = &destroy_print_event;
        ev->type = EVENT_PRINT;
        ev->event_args.print.ch = ch;
        ev->event_args.print.message = str_dup(buf);
        ev->delay = delay;

        ev->next = events;
        events   = ev;
        return;
}

void fire_print_event(EVENT * ev)
{
    send_to_char(ev->event_args.print.message, ev->event_args.print.ch);
}

void destroy_print_event(EVENT * ev)
{
    free_string(ev->event_args.print.message);
    //free_event(ev); -- done in update.c now
}

/*
 ******************************** /PRINT EVENT/ ********************************
*/

/*
 ******************************** WAIT EVENT ********************************
 * A wait event 'lags' the character.
*/
void wait_event(CHAR_DATA *ch, int delay, sh_int wait)
{
        EVENT *ev = new_event("Wait event");

        ev->fire = &fire_wait_event;
        ev->destroy = &destroy_wait_event;

        ev->type = EVENT_WAIT;
        ev->event_args.wait.ch = ch;
        ev->event_args.wait.seconds = wait;
        ev->delay = delay;

        ev->next = events;
        events   = ev;
        return;
}

void fire_wait_event(EVENT * ev)
{
    WAIT_STATE(ev->event_args.wait.ch, ev->event_args.wait.seconds*PULSE_PER_SECOND);
}

void destroy_wait_event(EVENT * ev)
{
    //free_event(ev); -- done in update.c now
}

/*
 ******************************** /WAIT EVENT/ ********************************
*/

/*
 ******************************** SAVE EVENT ********************************
    Mud-wide 'asave changed' and player saver.  Fun!  This will never end,
    once called.
*/
void save_event(time_t delay)
{
        EVENT *ev = new_event("Global, 15 min. automated save event");

        ev->fire = &fire_save_event;
        ev->destroy = &destroy_save_event;

        ev->type = EVENT_SAVE;
        ev->delay = delay;
        ev->event_args.save.active = TRUE;

        ev->next = events;
        events   = ev;
        return;
}

void fire_save_event(EVENT * ev)
{
    DESCRIPTOR_DATA *d;

    do_asave (NULL, "changed"); /*autosave changed areas */

    for (d = descriptor_list; d != NULL; d = d->next)
    {
        if (d->character != NULL && d->connected == CON_PLAYING)
            save_char_obj(d->character);
    }

}

void destroy_save_event(EVENT * ev)
{
    save_event(900); /*Initiate a new instance of the save event*/
	writelogf ("Successfully autosaved.");
    //free_event(ev); -- done in update.c now
}
/*
 ******************************** /SAVE EVENT/ ********************************
*/
/*
 ******************************** POLL EVENT ********************************
 * A poll event creates a global poll that players can vote on.  When it
 *completes, a note will be posted to 'all' with the vote results.  A single
 *player cannot vote twice.  See do_vote for more info on voting.
 *This event is essentially a wrapper for a menu.
*/
void poll_event(MENU_DATA *menu, int delay)
{
        EVENT *ev = new_event("Poll event");

        ev->fire = &fire_poll_event;
        ev->destroy = &destroy_poll_event;

        ev->type = EVENT_POLL;
        ev->event_args.poll.menu = menu;
        ev->event_args.poll.voted = NULL; //can't hurt
        ev->delay = delay;

        ev->next = events;
        events   = ev;
        mud.ispoll = TRUE;
        return;
}

void fire_poll_event(EVENT * ev)
{
}

void destroy_poll_event(EVENT * ev)
{
	NOTE_DATA *pnote;
	OPTION_DATA *option;
	MENU_DATA *menu;
	char *strtime;
    char buf[MSL];
	BUFFER *b;

	b = new_buf();

	strtime = ctime (&current_time);
	strtime[strlen (strtime) - 1] = '\0';

    menu = ev->event_args.poll.menu;

	pnote = new_note();
	pnote->next = NULL;
	pnote->sender = str_dup ("[Skull]");
	pnote->type = NOTE_NOTE;
	pnote->date = str_dup (strtime);
	pnote->to_list = str_dup ("all");
	sprintf(buf, "The poll which asked {C%s{y has finished.{x", menu->text);
	pnote->subject = str_dup (buf);

    add_buf(b, "Here are the results of this poll:\n\r");
	sprintf(buf, "{GVotes  {CSelection{x\n\r");
    add_buf(b, buf);
    for (option = menu->option; option; option = option->next)
    {
        sprintf(buf, "{G%5d{x  {C%s{x\n\r", option->argi, option->args[0]);
        add_buf(b, buf);
    }

	pnote->text = str_dup (buf_string(b));
	pnote->date_stamp = current_time;

	append_note(pnote);
	pnote = NULL;

	wiznet("A poll has finished.", NULL, NULL, 0, 0, 0);
    free_menu(ev->event_args.poll.menu);
    free_buf(b);

    //free_event(ev); -- done in update.c now

    mud.ispoll = FALSE;

}

/*
 ******************************** /POLL EVENT/ ********************************
*/


/*
 ******************************** DELAYED PROG EVENT ********************************
 * A prog event delays execution of a mob/obj/room program.  "vpr" is a void
 * pointer to the mob/obj/room that the program is on.  'Type' specifies what
 * vpr should be cast as.  When this event expires, the program will continue
 * where it left off. (ev->event_args.prog.line = line;)
*/

void prog_event (CHAR_DATA *ch, void *vpr, int type, PROG_CODE *prog_code, int line, int delay)
{
    EVENT *ev = new_event("Prog delay event");

    ev->type = EVENT_PROG;
    ev->fire = &fire_prog_event;
    ev->destroy = &destroy_prog_event;

    switch (type)
    {
        case MOB:
            ev->event_args.prog.mob = (CHAR_DATA *)vpr;
            break;
        case OBJ:
            ev->event_args.prog.obj = (OBJ_DATA *)vpr;
            break;
        default:
            ev->event_args.prog.room = (ROOM_INDEX_DATA *)vpr;
            break;
    }

    ev->event_args.prog.ch = ch; //The 'doer' of the program

    //Type: Mob prog, obj prog, or room prog?  (Which is it attached to.  See above.)
    ev->event_args.prog.type = type;

    ev->event_args.prog.prog_code = prog_code;
    ev->event_args.prog.line = line;

    ev->delay = delay;

    ev->next = events;
    events   = ev;
}

void fire_prog_event(EVENT * ev)
{
    extern void program_flow( sh_int, char *, CHAR_DATA *, OBJ_DATA *, ROOM_INDEX_DATA *,
                              CHAR_DATA *, const void *, const void *, int );

    //writelogf("timer.c:281: fire_prog_event\n\r");

    /*Evaluate if the ch is still valid to receive the rest of the program.
     *Conditions:
     * - The character must be in the room of the rprog if a room prog.
     * - The character must be in the room of the object if a object prog,
     *   or be carrying the object. -- CHANGED 2/5/2004 6:03PM:
     *   The object can be in another object (host); checks for that.
     *
     * - The character must be in the room of the mob if a mob prog.
     *
     * - Also, all three will be checked to make sure they exist yet.
     * If a condition isn't met, program_flow won't be called and the
     * event will self-terminate (destroy_prog_event).
     */
    switch (ev->event_args.prog.type)
    {
        case MOB:
            if (ev->event_args.prog.mob && ev->event_args.prog.ch &&
                ev->event_args.prog.mob->in_room == ev->event_args.prog.ch->in_room
                && IS_VALID(ev->event_args.prog.mob))
            {
                program_flow(ev->event_args.prog.prog_code->vnum,
                             ev->event_args.prog.prog_code->code,
                             ev->event_args.prog.mob,
                             NULL,
                             NULL,
                             ev->event_args.prog.ch,
                             NULL, NULL, ev->event_args.prog.line);
            }

            break;
        case OBJ:
            if (!ev->event_args.prog.obj || !ev->event_args.prog.ch)
                return;

            //Additional check because of crashyness
            if (!IS_VALID(ev->event_args.prog.obj))
                return;

            OBJ_DATA *host;
            if ((host = find_container_or_obj(ev->event_args.prog.obj)) == NULL)
                return;

            /*Below if-check:
             *Object exist?  Char exist?  Obj in a room?  If so, rooms match?
             *If not, is it being carried by someone? If so, rooms match?
             *If not, 0 -- false condition.
            */

            //JH: for now, objprograms will execute until they finish.
            //That means a player doesn't need to be in the same room as the obj.
            //If this proves problematic in the future..well.. I don't know. :)
            /*if (host->in_room && host->in_room != ev->event_args.prog.ch->in_room)
                return;

            if (host->carried_by && host->carried_by->in_room != ev->event_args.prog.ch->in_room)
                return;
            */
            //writelogf("timer.c:281: 332: calling program_flow\n\r");
            program_flow(ev->event_args.prog.prog_code->vnum,
                         ev->event_args.prog.prog_code->code,
                         NULL,
                         ev->event_args.prog.obj,
                         NULL,
                         ev->event_args.prog.ch,
                         NULL, NULL, ev->event_args.prog.line);

            break;
        default:
            //Removed the following check because of the possibility of room at.  Grumble.
            // JH 3/2/2004 6:57PM
            //if (ev->event_args.prog.ch && ev->event_args.prog.room &&
            //    ev->event_args.prog.ch->in_room == ev->event_args.prog.room)

                program_flow(ev->event_args.prog.prog_code->vnum,
                             ev->event_args.prog.prog_code->code,
                             NULL,
                             NULL,
                             ev->event_args.prog.room,
                             ev->event_args.prog.ch,
                             NULL, NULL, ev->event_args.prog.line);


            break;
    }//end switch


}

void destroy_prog_event(EVENT * ev)
{
    //ch_printf(ev->event_args.prog.ch, "\n\rtimer.c:253: destroy_prog_event\n\r");
    //free_event(ev); -- done in update.c now
}

/*
 ******************************** /DELAYED PROG EVENT/ ********************************
*/


/*
 ******************************** COOLDOWN PROG EVENT ********************************
* A cooldown prog event prevents a program from being immediately called again.
* An example is, say, if there is a catapult in the game and you don't want
* your players firing 4,000 shots a second.  A cooldown line can be written
* into the program to prevent this.
*/

void prog_cooldown_event (PROG_LIST *prog_list, int delay, char *msg)
{
    EVENT *ev = new_event("Prog cooldown event");

    ev->type = EVENT_PROG_COOLDOWN;
    ev->fire = &fire_prog_cooldown_event;
    ev->destroy = &destroy_prog_cooldown_event;

    ev->event_args.prog_cooldown.prog_list = prog_list;

    if (msg[0] != '\0')
        ev->event_args.prog_cooldown.message = str_dup(msg);
    else
        ev->event_args.prog_cooldown.message = str_dup("Huh?");

    ev->delay = delay;
    ev->next = events;
    events   = ev;
    //writelogf("prog_cooldown_event called");
}

void fire_prog_cooldown_event(EVENT * ev)
{
}

void destroy_prog_cooldown_event(EVENT * ev)
{
    free_string(ev->event_args.prog_cooldown.message);
}

/*
 ******************************** /COOLDOWN PROG EVENT/ ********************************
*/

/*
 ******************************** AFFHANDLE EVENT ********************************
 * Rather than have a affect_update or whatever, all (cast) affects have an
 * event wrapper.  This allows much more flexibility with what you can achieve
 * with your affects.
*/
void affhandle_event(CHAR_DATA *victim, AFFECT_DATA *paf, CHAR_DATA *caster)
{
    EVENT *ev;
    ev = new_event("Affhandle event");
    ev->type = EVENT_AFFHANDLE;

    ev->fire = &fire_affhandle_event;
    ev->destroy = &destroy_affhandle_event;

    //ev->event_args.affhandle.ch = ch;
    //hack fixme
    ev->event_args.affhandle.victim = victim;
    if (caster)
        ev->event_args.affhandle.caster = caster;
    else
        ev->event_args.affhandle.caster = victim;    
    
    ev->event_args.affhandle.paf = paf;
    ev->delay = paf->duration;

    ev->next = events;
    events   = ev;
}


void fire_affhandle_event(EVENT * ev)
{
    AFFECT_DATA *paf = ev->event_args.affhandle.paf;
    CHAR_DATA *victim = ev->event_args.affhandle.victim;

    if (paf->type >= 0 && paf->type < SkillTable.size())
    {
        if (!SkillTable[paf->type].off_self.empty())
        	ch_printf(victim, "%s\n\r", SkillTable[paf->type].off_self.c_str());
        if (!SkillTable[paf->type].off_room.empty())
            act( SkillTable[paf->type].off_room.c_str(), victim, NULL, NULL, TO_ROOM );
    }
    affect_remove (victim, paf);
}

void destroy_affhandle_event(EVENT * ev)
{
}

/*
 ******************************** /AFFHANDLE EVENT/ ********************************
*/

/*
 ******************************** MENU EVENT ********************************
 * Make a menu for a person to respond to.  See the menu_event in wiz_edit.c
 * for an example of how this is to be called.
*/
void menu_event(CHAR_DATA *ch, MENU_DATA *menu)
{
    //this shouldn't be necessary, but meh.
    if (IS_NPC(ch))
        return;

    EVENT *ev;
    ev = new_event("Menu handler event");
    ev->type = EVENT_MENU;

    ev->fire = &fire_menu_event;
    ev->destroy = &destroy_menu_event;

    ev->event_args.menu.ch = ch;
    ev->event_args.menu.menu = menu;
    menu->ev = ev;
    ev->delay = 360; //give a player 6 minutes to respond to a menu.  Shrug.
                     //theoretically, we could not even have a delay at all,
                     //but I decided upon this.

    //use the pointer rather than find it from the char's LL.  Lazy.
    //(make the old locked menu expire)
    if (ch->pcdata->menu_locked)
        ch->pcdata->menu_locked->ev->delay = 0;
    
    menu_to_char(ch, menu);

    //Reset the locked menu here.
    if (!IS_SET(menu->menu_flags, MENU_NOLOCK))
        ch->pcdata->menu_locked = menu;
    else
        ch->pcdata->menu_locked = NULL;

    ev->next = events;
    events   = ev;

/*    writelogf("%s:%d:menu_event:  Attached new menu '%s' to '%s'",
        __FILE__, __LINE__, menu->name, ch->name);*/


}


void fire_menu_event(EVENT * ev)
{
}

void destroy_menu_event(EVENT * ev)
{
    //this function takes care of the freeing of the menu and, if the menu
    //is set to Phoenix, rebirths it.

    CHAR_DATA *ch = ev->event_args.menu.ch;
    MENU_DATA *menu = ev->event_args.menu.menu;

    /*nothing to do if the menu disappeared somehow*/
    if (!menu)
        return;

    /*character still around? Unsafe if not. Just toast menu.*/
    if (!ch)
    {
        free_menu(menu);
        return;
    }

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

    //remove menu from char's LL.
    menu_from_char(ch, menu);

    if (IS_SET(menu->menu_flags, MENU_PHOENIX))
    {
/*        writelogf("%s:%d:destroy_menu_event:  Menu '%s' on '%s' will phoenix",
            __FILE__, __LINE__, menu->name, ch->name);*/
        do_function(ch, menu->fun_from, itoa(menu->argi));
    }
    
    //toast the menu, if applicable
    //'hardcoded' menus will never be freed, that'd suck.
    if (!IS_SET(menu->menu_flags, MENU_NOFREE))
    {
/*
        writelogf("%s:%d:destroy_menu_event:  Menu '%s' on '%s' is being freed",
            __FILE__, __LINE__, menu->name, ch->name);*/
        free_menu(menu);
    }

}

/*
 ******************************** /MENU EVENT/ ********************************
*/