/*

 BabbleMud - A MUD server

 Author:  Nick Gammon 
          http://www.gammon.com.au/ 

(C) Copyright Nick Gammon 2004. Permission to copy, use, modify, sell and
distribute this software is granted provided this copyright notice appears
in all copies. This software is provided "as is" without express or implied
warranty, and with no claim as to its suitability for any purpose.
 
*/

#ifdef WIN32
#include <time.h>
#endif

#include "commands.h"

const string FIGHTING = "fighting";   // player is fighting flag

void EventSendToAll (Event & e)
  {
  SendToAll ("msg", e.get_sdata (), ThingMap (), NULL, NULL, e.geti ("room")); 
  } // end of EventSendToAll

void EventApplyEffect (Event & e)
  {
Character * p = FindPlayerID (e.gets ("id"));
  
  if (p == NULL)
     return;    // not there, can't do it  -- TODO: should we delete the event?
  
  string effect = e.gets ("effect");  // what to effect
  int by = e.geti ("by");             // by how much
  
  int value = p->geti (effect);  // current value
  value += by;  // amend it
  if (value < 0)
    value = 0;  // not negative
  p->seti (effect, value);   // set new value

  string msg;
  // send messages if not empty
  msg = e.gets ("msg_target");
  if (!msg.empty ())
    p->send ("msg_target", e.get_sdata ());    // player
  
  // who the target is, for the "msg_room" messages
  ThingMap sub;
  sub ["target"] = *p;
  
  msg = e.gets ("msg_room");
  if (!msg.empty ())
    SendToAll ("msg_room", e.get_sdata (), sub, p, NULL, p->GetRoom ());    // room he is currently in
  
  } // end of EventApplyEffect

void EventMoveMob (Event & e)
  {
Character * p = FindPlayerID (e.gets ("id"));
     
  if (p == NULL)
    {
    e.SetCount (0); // delete event
    return;    // not there, can't do it 
    }

  // mob is fighting - leave it where it is
  if (p->getb (FIGHTING))
    return;
  
  // if dead, leave it there
  if (p->geti ("hp") <= 0)
    {
    e.SetCount (0); // delete event
    return;    // dead mobs don't move
    }
  
  e.sub_ ["vnum"].sets ("short", MAKE_STRING (p->geti ("room")));
  World & r = FindRoom (p->GetRoom ());  // find room it is in

  vector<string> v;
  for (ObjectList::const_iterator exititer = r.exits ().begin ();
       exititer != r.exits ().end (); 
      ++exititer)
    if (exititer->gets ("handler") == "DoDirection" &&  // a direction exit
        !exititer->getb ("nomob"))      // allowed to mobs
       v.push_back (exititer->gets ("short"));
  
  if (v.empty ())
    {
    warnAdmin ("Mob " + e.gets ("id") + " cannot move.");
//    e.SetCount (0); // delete event (later maybe)
    return;
    }
  
  // get a random one to the front
  random_shuffle (ISEQ (v));

  p->DoCommand (v [0]);   // send movement command (eg. north)
  
  } // end of EventMoveMob

struct checkIdlePlayers
{
const int connectingLimit_;
const int playingLimit_;

  checkIdlePlayers (const int connectingLimit, 
                    const int playingLimit) 
      : connectingLimit_ (connectingLimit), playingLimit_ (playingLimit) {}
  
  // handle this player
  void operator() (Player * p) 
    {
    // forget it if no connection or leaving us
    if (!p->Connected () || p->closing_)
      return;
    
    // playing players have a different time-limit to connecting ones
    if (p->IsPlaying ())
      {
      if (p->GetLastCommandTime () < playingLimit_)
         {
         p->send ("\n\n" + p->message ("idle_timeout_playing"));
         p->ClosePlayer ();
         }  // end of timeout on playing player
      
      }   // end of playing
    else
      {
      if (p->GetLastCommandTime () < connectingLimit_)
        {
         p->send ("\n\n" + p->message ("idle_timeout_connecting"));
         p->ClosePlayer ();
        } // end of timeout on connecting player
      
      } // end of connecting
       
    } // end of operator()  
      
};  // end of checkIdlePlayers

// check for idle players - the check is done every minute
void EventCheckIdle (Event & e)
  {
static int connectingIdleTimeoutSeconds = control ().geti ("idle_timeout_minutes_connect") * 60;
static int playingIdleTimeoutSeconds    = control ().geti ("idle_timeout_minutes_playing") * 60;

time_t now = time (NULL);
 
  for_each (ISEQ (playerlist ()), 
            checkIdlePlayers (now - connectingIdleTimeoutSeconds, 
                              now - playingIdleTimeoutSeconds));      
  }

// save one player
void SaveEachPlayer (Player * p)
  {
  if (p->IsPlaying ())
    p->Save ();
  }

// save connected players
void EventSavePlayers (Event & e)
  {
  for_each (ISEQ (playerlist ()), SaveEachPlayer);
  }

void MainEventHandler (Event & event)
  {
static EventHandlerMap & eventmap = getEventHandlerMap ();

  string type = event.gets ("type");
  
  try
    {
   try
      {
      // find event handler
      EventHandlerMap::const_iterator handlerIter = eventmap.find (type);
        
      if (handlerIter == eventmap.end ())
        {
        throw runtime_error ("No event handler for event: " + type);
        }
  
      // handle event
      handlerIter->second (event);
     
      } // end of try for msg exceptions
    catch (msg & e)
      {
      // re-throw with code substitution
      throw runtime_error (convertErrorCode (e.what ()));
      }    
    } // end of try for exceptions
  // catch exceptions during events
  
// we need to catch exceptions here because we are not in the main player loop
// if we don't the program ends... blah!
// we hope to not get them, so we will output a fair bit of debugging stuff
  
  catch (exception & e)
    {
    // substitute (eg. which vnum is missing)
    string message = FixMessage (control ().gets ("default_prefix"), e.what (), event.sub_, false);
        
    // notify admins
    SendToAll (MAKE_STRING ("`RNOTIFY: Exception during event " << event.gets ("type") 
                << ": " <<  message << "`X\n"), "notify_admin");
    
    // log it
    gamelog (MAKE_STRING ("Event (" << event.gets ("type") << ") exception: " <<  message));

    // output to stderr
    cerr << "EXCEPTION during event type " << event.gets ("type") << endl;
    cerr << "          Error: " << message << endl;
    if (!event.empty ())    // output event parameters, if any
      {
      cerr << " <event>\n";
      cerr << event;
      cerr << " </event>" << endl;
      }
    
    event.SetCount (0); // delete event
    
    } // end of catch
  
  } // end of MainEventHandler



static int LoadEventsMap ()
  {
static EventHandlerMap & eventmap = getEventHandlerMap ();

  cout << "Loading event handlers in " __FILE__ << endl;
    
  EVENT (EventSendToAll);       // send message to al
  EVENT (EventApplyEffect);     // apply a spell effect
  EVENT (EventMoveMob);         // mob movement
  EVENT (EventCheckIdle);       // check for idle players
  EVENT (EventSavePlayers);     // save players

  return 0;
  } // end of LoadEventsMap

static int loadEvents = LoadEventsMap ();
