/*

 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.
 
*/

#include "commands.h"

#include "timer.h"

//~ attributes used:

//~ PLAYER (string):

const string SEX = "sex";     // player gender

//~ PLAYER (number):
  
const string ROOM = "room";     // current room


StringMap Male, Female, Neutral, Multiple,
          TMale, TFemale, TNeutral, TMultiple;

// for translating messages into the appropriate form

// eg. A wind took %<his> %<what> and gave it to %<t_him>

// PRONOUN SUBSTITUTION:

// %<he>:   Subjective pronoun          - he/she/it/they 
// %<his>:  Possessive pronoun          - his/her/its/their 
// %<him>:  Objective pronoun           - him/her/it/them 
// %<hims>: Absolute possessive pronoun - his/hers/its/theirs 

// Plus, for the target, with t_ in front, eg. t_he, t_his etc.
 
// these will be automatically added into the info.sub_ map
// depending on the gender of the enactor and target respectively
static int LoadGendersMap ()  // done once
  {
  Male  ["he"]     = "he";     // player  (he ate an apple)
  Male  ["his"]    = "his";    //         (it is his apple)
  Male  ["him"]    = "him";    //         (I gave the apple to him)
  Male  ["hims"]   = "his";    //         (the apple is his)
  TMale ["t_he"]   = "he";     // target, as above
  TMale ["t_his"]  = "his";
  TMale ["t_him"]  = "him";
  TMale ["t_hims"] = "his";   
  
  Female  ["he"]     = "she";    // player  (she ate an apple)
  Female  ["his"]    = "her";    //         (it is her apple)
  Female  ["him"]    = "her";    //         (I gave the apple to her)
  Female  ["hims"]   = "hers";   //         (the apple is hers)
  TFemale ["t_he"]   = "she";    // target, as above
  TFemale ["t_his"]  = "her";
  TFemale ["t_him"]  = "her";
  TFemale ["t_hims"] = "hers";      

  Neutral  ["he"]     = "it";     // player  (it ate an apple)
  Neutral  ["his"]    = "its";    //         (it is its apple)
  Neutral  ["him"]    = "it";     //         (I gave the apple to it)
  Neutral  ["hims"]   = "its";    //         (the apple is its)
  TNeutral ["t_he"]   = "it";     // target, as above
  TNeutral ["t_his"]  = "its";
  TNeutral ["t_him"]  = "it";
  TNeutral ["t_hims"] = "its";  
  
  Multiple  ["he"]     = "they";     // player  (they ate an apple)
  Multiple  ["his"]    = "their";    //         (it is their apple)
  Multiple  ["him"]    = "them";     //         (I gave the apple to them)
  Multiple  ["hims"]   = "theirs";   //         (the apple is theirs)
  TMultiple ["t_he"]   = "they";     // target, as above
  TMultiple ["t_his"]  = "their";
  TMultiple ["t_him"]  = "them";
  TMultiple ["t_hims"] = "theirs";     
 
  return 0;
  } // end of LoadGendersMap

/* process commands when player is connected */

void ProcessCommand (Player * p, istream & sArgs)
  {
  ProcessCommand (dynamic_cast <Character *> (p), sArgs);
  }

/*

We will precompile all commands, this gives about a 10-fold speed increase,
compared to compiling hundreds of regular expressions every time.

*However*, if you add new commands (or change old ones) at runtime the 
list will need to be recompiled. In that case you would simply need to call
CompileCommands ()  (below) to make that happen.

*/

// each command will be paired with its regular expression
typedef pair<Object, Regexp> CompiledCommand;
typedef list<CompiledCommand> CompiledCommandList;
static CompiledCommandList compiledCommands;

// functor to help finding a object by using a regexp
struct fFindCommand : unary_function<CompiledCommand &, bool>
{
  const string target_;
  StringMap & wildcards_;

  // ctor
  fFindCommand (const string & target, StringMap & wildcards) 
    : target_ (target), wildcards_ (wildcards) {} 
  // check for matching command
  bool operator() (CompiledCommand & t) const
    {
    if (t.second.Execute (target_))  // true if match
      {
      t.second.BuildResultMap (wildcards_);
      return true;
      }
    return false;
    } // end of operator()  
};  // end of fFindCommand
  
// find one command
CompiledCommandList::iterator FindCommand (const string & target, StringMap & wildcards)
{
  return  find_if (ISEQ (compiledCommands), fFindCommand (target, wildcards));
} /* end of FindCommand */
 
// compile the "match" text, save it paired with its command object
void compileOneCommand ( Object & command)
  {
  compiledCommands.push_back (
      pair<Object, Regexp> (command, Regexp (command.gets ("match"), PCRE_CASELESS)));
  }

// compile all commands
int CompileCommands ()
  {
  cout << "Compiling " << commands ().size () << " commands ..." << endl;
  compiledCommands.clear ();
  for_each (ISEQ (commands ()), compileOneCommand);
  cout << "Done." << endl;
  } // end of CompileCommands

// main command handler to handle commands typed, or forced or otherwise
// associated with player "p"
void ProcessCommand (Character * p, istream & sArgs, const bool forced)
{
static int x = CompileCommands ();    // compile all commands once when first command called
static int loadGenders = LoadGendersMap ();   // also load up gender substitution map

static CommandHandlerMap & commandmap = getCommandHandlerMap ();
static const string ColourCodeFind (1, COLOURCODE);
static const string ColourCodeReplace (2, COLOURCODE);

  // just in case, however this shouldn't happen
  if (p == NULL)
    return;
    
  // build command info structure with current player and current room   
  CommandInfo info (p, FindRoom (p->geti (ROOM)));
  
  string himself = "himself";

  getline (sArgs, info.line_);

  // ignore blank commands
  if (info.line_.empty ())
    return;

  // time command parser
//  timer t ("Command processing: " + info.line_);
    
  // get rid of multiple spaces between words
  do
    info.line_ = FindAndReplace (info.line_, "  ", " ");
  while (info.line_.find ("  ") != string::npos);
  
  // double up the colour code character so they can't spam each other with coloured messages
  info.line_ = FindAndReplace (info.line_, ColourCodeFind, ColourCodeReplace);
  
  // some errors are already translated (eg. error messages from the command file)
  // they will throw runtime_error, not msg, thus they don't need to be caught by the
  // inner try block
   
  // try block catches errors so we can do token substitution on them
  // before re-throwing them
  try   // runtime_error (which is already translated)
    {    
    try  // msg (which needs substitution per language)
      {   
      
      serverInfo ().seti ("commands_entered", serverInfo ().geti ("commands_entered") + 1);
      
      // look for excessively duplicated commands (eg. "look, look, look" 100 times)
      if (!p->mob ())
        {
        if (info.line_ == p->gets ("last_command"))
          {   // same command as last time
          p->seti ("command_count", p->geti ("command_count") + 1);
          if (p->geti ("command_count") > control ().geti ("command_count_maximum"))
            {
            info.sub_ ["max_commands"].sets ("short", MAKE_STRING (control ().geti ("command_count_maximum")));
            p->ClosePlayer ();  // kick them off
            throw msg ("too_many_consecutive_commands");
            } // end of maximum exceeded
          }   // end of the same command
        else  // not the same command, remember this one and clear the counter
          {   
          p->sets ("last_command", info.line_);
          p->seti ("command_count", 1);
          }   // end of a new command
        } // end of not a mob
    
      // the enactor is ourselves, this is substitution text "player"
      info.sub_ ["player"] = *p;
      
      //~ // get sex of player
      //~ string sex = tolower (p->gets (SEX));
      //~ if (sex.empty ())
        //~ sex = "m";
      //~ switch (sex [0])
        //~ {
        //~ case 'm':   // male, man
            //~ copy (ISEQ (Male), inserter (info.sub_, info.sub_.end ())); 
            //~ break;
        //~ case 'f':   // female
        //~ case 'w':   // woman
            //~ copy (ISEQ (Female), inserter (info.sub_, info.sub_.end ())); 
            //~ break;
        //~ case 'p':   // plural
            //~ copy (ISEQ (Multiple), inserter (info.sub_, info.sub_.end ())); 
            //~ break;
        //~ case 'n':   // neutral
        //~ default:    // anything else, assume neutral
            //~ copy (ISEQ (Neutral), inserter (info.sub_, info.sub_.end ())); 
            //~ break;      
        //~ } // end of switch
  
      ObjectList::const_iterator commandIter;
      
      // first, have they typed a command matching a room exit? (eg. north)
      // note, these are not precompiled as there will be comparatively few of them
      // and they are scattered all over theh place
      commandIter = FindObjectRegexp (info.room ().exits (), "match", info.line_, info.wildcards_);
           
      if (commandIter != info.room ().exits ().end ())
        info.command_ = *commandIter;
      else
        {   // ordinary command (not exit)  
       
        // next find command in precompiled commands table
        CompiledCommandList::iterator compiter = FindCommand (info.line_, info.wildcards_);
        
        // not there? we don't understand them at all
         if (compiter == compiledCommands.end ())
            throw msg ("huh");
  
         // remember command so we can look up its arguments (eg. what they can do)
        info.command_ = compiter->first;
        }   // not an exit
      
      // we need to copy wildcards to info.sub_ so that speech commands, etc., work.
      // eg. say (?P<what>.+) - the wildcard "what" needs to end up as substitution text
      for (StringMap::const_iterator wild_iter = info.wildcards_.begin ();
          wild_iter != info.wildcards_.end ();
          ++wild_iter)
        info.sub_ [wild_iter->first].sets ("short", wild_iter->second);
      
      // a "usage" field indicates an error to be thrown - 'how to use the command'
      // however before we throw the usage we will test if they can do it or not
      // - the test is on the alias (if any) so we need to find that first
      string usage = info.command_.gets ("usage");
      
      // see if the command has an alias, that way we can have duplicated commands
      // without having to put the conditions in twice
      string alias = info.command_.gets ("alias");
      if (!alias.empty ())
        {
        // TODO: scan exits also?
        commandIter = FindObject (commands (), "name", alias);
        if (commandIter == commands ().end ())
          {
          info.sub_ ["alias"].sets ("short", alias);
          throw msg ("no_command_alias");
          }
        info.command_ = *commandIter;   // take new command now
        }
      
      // if they specified a target in the command, find who it is
      if (!info.wildcards_ ["target"].empty ())
        {
        info.target_ = p->GetPlayer (
              info.wildcards_ ["target"], 
              info.command_.gets ("no_target_message"), 
              info.command_.getb ("target_not_self"),
              info.command_.getb ("target_in_room")); 
        info.sub_ ["target"] = info.target ();
        
       //~ // get sex of target
        //~ string tsex = tolower (info.target ().gets (SEX));
        //~ if (tsex.empty ())
          //~ tsex = "m";
        //~ switch (tsex [0])
          //~ {
          //~ case 'm':   // male, man
              //~ copy (ISEQ (TMale), inserter (info.sub_, info.sub_.end ())); 
              //~ himself = "himself";
              //~ break;
          //~ case 'f':   // female
          //~ case 'w':   // woman
              //~ copy (ISEQ (TFemale), inserter (info.sub_, info.sub_.end ())); 
              //~ himself = "herself";
              //~ break;
          //~ case 'p':   // plural
              //~ copy (ISEQ (TMultiple), inserter (info.sub_, info.sub_.end ())); 
              //~ himself = "themselves";
              //~ break;
          //~ case 'n':   // neutral
          //~ default:    // anything else, assume neutral
              //~ copy (ISEQ (TNeutral), inserter (info.sub_, info.sub_.end ())); 
              //~ himself = "itself";
              //~ break;      
          //~ } // end of switch
        
        if (info.target ().GetRoom () != info.player ().GetRoom () &&
            info.command_.getb ("target_in_room"))
           throw msg ("target_not_in_same_room");
  
        } // end of wanting a target
                  
      // if they specified a spell in the command, find it
      if (!info.wildcards_ ["spell"].empty ())
        {
        ObjectList & spells = control ().items_ ["spells"];
        ObjectIter iSpell = FindObject (spells, "name", info.wildcards_ ["spell"]);
        if (iSpell == spells.end ())
          throw msg ("no_such_spell");
        // only builders can modify inactive spells
        if (!iSpell->getb ("active") && !p->HaveFlag ("can_add_spell"))
          throw msg ("spell_not_active");
          
        info.spell_ = &(*iSpell);  // get spell address
        } // end of wanting a spell
      
      // check they can do it
      // eg. in commands.xml:  needflag="can_goto" needflag_failure="You can't go to other rooms."
      p->NeedFlag (info.command_.gets ("needflag"), info.command_.gets ("needflag_failure"), true);
      // eg. in commands.xml:  neednoflag="gagged" neednoflag_failure="You can't talk."
      p->NeedNoFlag (info.command_.gets ("neednoflag"), info.command_.gets ("neednoflag_failure"), true);
      
      // some commands can't be forced (eg. the force command itself)
      if (info.command_.getb ("no_force") && forced)
        throw msg ("command_cannot_be_forced");
      
      // a non-empty usage field means we just tell them the command usage, eg. "Say what?"
      if (!usage.empty ())
        throw runtime_error (usage);
      
     // find what handler routine it takes
      // eg. in commands.xml:  handler="DoLook"
      info.handlerName_ = info.command_.gets ("handler");
      
      // look up the handler routine - if any
      if (!info.handlerName_.empty ())
        {
        CommandHandlerMap::const_iterator handlerIter = 
                commandmap.find (info.handlerName_);
        
        if (handlerIter == commandmap.end ())
          {
          info.sub_ ["handler"].sets ("short", info.handlerName_);
          throw msg ("no_command_handler");
          }
        
        // call command handler
        handlerIter->second (info);  // execute command (eg. DoLook)
        } // end of needing a handler
      }   // end of try for msg exceptions
    catch (msg & e)
      {
      // re-throw with substitution
      throw runtime_error (p->error (e.what ()));
      }
    } // end of try for all exceptions
  catch (exception & e)
    {
        
    // substitute words into error message (like target name)
    string msg = FixMessage (p->prefix (), e.what (), info.sub_, false);
        
    // player failures are:
    //  * a simple mistake,
    //  * badly-designed system (commands should be explained better)
    //  * an attack on the system (eg. trying to find weaknesses)
    
    // log all failed commands, if wanted - not mobs
    if (control ().getb ("log_failures") && !p->mob ())
      failedlog ("\n    -- Command: " + info.line_ + 
                 "\n    -- Message: " + e.what (), 
                p);

    // mob failures are a programming or building error
    
    // log all failed commands, if wanted - mobs
    if (control ().getb ("log_mob_failures") && p->mob ())
      mobfailedlog ("\n    -- Command: " + info.line_ + 
                    "\n    -- Message: " + e.what (), 
                   p);
    
    if (p->mob ())
      serverInfo ().seti ("mob_command_errors", serverInfo ().geti ("mob_command_errors") + 1);
    else
      serverInfo ().seti ("command_errors", serverInfo ().geti ("command_errors") + 1);
      
    // re-throw with substitutions
    throw runtime_error (msg);
    } // end of catch
  
  // here for successful handling of command (ie. no exceptions)

  if (p->mob ())
    serverInfo ().seti ("mob_command_success", serverInfo ().geti ("mob_command_success") + 1);
  else
    serverInfo ().seti ("command_success", serverInfo ().geti ("command_success") + 1);

  // warn admins if they want to know about it
  if (info.command_.getb ("notify_admin"))
    SendToAll ("NOTIFY: " + p->GetName () + ": " + info.line_ + "\n", "notify_admin");

  // log it?
  if (info.command_.getb ("log"))
    gamelog (info.line_, p);
  
  // special case - doing something to yourself - TODO - put this back!
  //~ if (p == info.target_)
    //~ info.sub_ ["target"] = "yourself";
  
  Character * exceptSelf = p; // exclude us from "tell all"
    
  string successSelf = info.command_.gets (p->prefix () + "success_self");  
  if (successSelf.empty ())
    {
    if (info.command_.gets (p->prefix () + "depart_self").empty ())
      exceptSelf = NULL;    // include ourselves in any messages to the room or whole MUD
    }
  else
    p->send (p->prefix () + "success_self", info.command_.get_sdata (), info.sub_); // tell us we did it

  Character * exceptTarget = info.target_; // exclude target from "tell all"
 
  // tell target what we did - if we have a target, and we have a special message for them
  if (info.target_ != NULL &&   // if we have a target
      (info.target_ != p || successSelf.empty ()))  // and it wasn't ourself
    {
    string successTarget = info.command_.gets (p->prefix () + "success_target");  
    if (successTarget.empty ())
      {
      if (info.command_.gets (p->prefix () + "depart_target").empty ())
        exceptTarget = NULL;    // include target in any messages to the room or whole MUD
      }
    else
      info.target ().send (info.target ().prefix () + "success_target", 
                          info.command_.get_sdata (), info.sub_); // tell them we did it
    }   // end of having a target
  
 // special case - doing something to yourself (eg. heal self) - Nick heals himself.
 //   rather than saying "Nick heals Nick".
  
  // TODO - put this back!
  //~ if (p == info.target_)
    //~ info.sub_ ["target"] = himself;   // herself, itself etc.
   
  // tell whole MUD except ourselves and our target
  //~ SendToAll (FixMessage (info.command_.gets ("success_all"), info), exceptSelf, exceptTarget);
  SendToAll ("success_all", info.command_.get_sdata (), info.sub_, exceptSelf, exceptTarget, 0, true);
  
  // tell everyone in room except ourselves and our target
  //~ SendToAll (FixMessage (info.command_.gets ("success_room"), info), exceptSelf, exceptTarget, 
            //~ p->geti (ROOM)); 
  SendToAll ("success_room", info.command_.get_sdata (), info.sub_, exceptSelf, exceptTarget,
              p->geti (ROOM), true);
  
  
  // deferred "look" - used in movement commands and similar to look around the room
  // *after* the success messages have been shown.
  //  eg. You arrive at blah blah. (now look around)
  if (info.command_.getb ("do_look"))
    p->DoCommand ("look");   // look around new room  

} /* end of ProcessCommand */

