/*

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

// standard library includes ...

#include <vector>
#include <stdexcept>
#include <iostream>
#include <fstream>
#include <iterator>

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

using namespace std; 

#include "utils.h"
#include "constants.h"
#include "strings.h"
#include "globals.h"
#include "exceptions.h"
#include "load.h"
#include "player.h"
#include "room.h"
#include "gameutils.h"
#include "save.h"

#include "message.h"
#include "xmltree.h"
#include "xmlerrors.h"

const string ROOM = "room";     // current room

void Player::Init ()
  {
  clear ();   // clear all attributes
  connstate_ = eAwaitingName;
  seti (ROOM, control ().geti ("initial_room", 1000));
  prompt_ = message ("connect_initial_prompt"); 
  time (&lastCommandTime_);    // for timeout checks
  commandsThisSecond_ = 0;     // commands entered this second
  } // end of Player::Init

Player::~Player () // dtor
  {
  try // exceptions in destructors are bad, might occur in Save
    {
    ProcessWrite ();    // send outstanding text
    if (s_ != NO_SOCKET) /* close connection if active */
      close (s_);
    if (connstate_ == ePlaying)
      {
      time_t t = time (NULL);
      seti ("last_disconnected_time", t);
      sets ("last_disconnected_at", stringDate (t));
      time_t time_on = geti ("last_connected_time");
      int total = t - time_on;  // total time this session
      if (total > 0)  // remember total time on
        seti ("total_time_on", geti ("total_time_on") + total);
      serverInfo ().sets ("last_player_quit", GetName ());
      serverInfo ().sets ("last_player_quit_date", stringDate (t));
      Save ();          // auto-save on close
      }
    } 
  catch (exception & e)
    {
    cerr << "Exception thrown in destructor for player " <<
         GetName () << ": " << e.what () << endl;
    }
  };

void NotPermitted (const string & message, const bool lookedUp)
  {
  if (message.empty ())
    throw msg ("not_permitted");
  else
    {
    if (lookedUp)   // we have a straight message
      throw runtime_error (message);
    else            // we have a code
      throw msg (message);
    }
  } // end of NotPermitted 

// functor to help finding a player by name
struct findPlayerName : unary_function<const Character *, bool>
{
  const string name_;
  // ctor
  findPlayerName (const string & name) : name_ ( name ) {} 
  // check for player with correct name, and actually playing
  bool operator() (const Character * p) const
    {
    return p->IsPlaying () && ciStringEqual (p->GetName (), name_);
    } // end of operator()  
};  // end of findPlayerName

// functor to help finding a player by name
struct findMobName : unary_function<const Character *, bool>
{
  const string name_;
  const int inRoom_;

  // ctor
  findMobName (const string & name, const int inRoom) 
    : name_ ( name ), inRoom_ (inRoom) {} 
  // check for mob with correct name, and actually playing
  bool operator() (const Character * p) const
    {
    if (!p->IsPlaying ())
      return false;
    if (inRoom_)
      if (p->geti ("room") != inRoom_)
        return false;   // wrong room
    string s = p->gets ("match"); // match regexp
    if (s.empty ())
      return false; // can't very well be the one
    
    Regexp re (s);  // compile match string
    
    return re.Execute (name_);    // true if name matches
    } // end of operator()  
};  // end of findMobName

/* find a player or mob by name */

Character * FindPlayer (const string & name, const int inRoom)
{
  // try connected players
  PlayerListIterator playeriter =
    find_if (ISEQ (playerlist ()), findPlayerName (name));

  if (playeriter != playerlist ().end ())
    return *playeriter;

  // not there? try mobs

  CharacterListIterator mobiter =
    find_if (ISEQ (moblist ()), findMobName (name, inRoom));

  if (mobiter != moblist ().end ())
    return *mobiter;
  
  return NULL;
  
} /* end of FindPlayer */

// find a player or mob based on their unique ID
Character * FindPlayerID (const string & id)
{
  PlayingMap::iterator playingIter = playing ().find (id);
 
  // not there?
  if (playingIter == playing ().end ())
    return NULL;
  
  Character *  p = playingIter->second;

  // they need to be playing
  if (p->IsPlaying ())
    return p;
  
  return NULL;
} // end of FindPlayerID

// member function to find another playing, including myself
Character * Character::GetPlayer (const string & name, 
                                  const string & noNameMessage, 
                                  const bool & notme,
                                  const bool & inroom)
{
  if (name.empty ())
    if (noNameMessage.empty ())
      throw msg ("do_that_to_who");
    else
      throw runtime_error (noNameMessage);
  Character * p = this;
  if (ciStringEqual (name, "me") || ciStringEqual (name, "self"))
    p = this;
  else
    p = FindPlayer (name, inroom ? GetRoom () : 0);
  if (p == NULL)
    throw msg ("target_not_connected");
  if (notme && p == this)
    throw msg ("target_not_self");
  return p;  
} // end of GetPlayer

void Player::ProcessException ()
{
  /* signals can cause exceptions, don't get too excited. :) */
  cerr << "Exception on socket " << s_ << endl;
} /* end of Player::ProcessException */


void Player::Load ()
{
string name = PLAYER_DIR + gets ("name") + PLAYER_EXT;
  
  try
    {    
    XmlNode root;
    // load player file
    LoadXml (name, root);
    // find <player> node - must be there    
    list<XmlNode>::const_iterator playerIter = NeedNode (root.children_, "player");
    // load player basic attributes and all contents (such as inventory)
    LoadTopThing (*playerIter, *this);
    }
  catch (exception & e)
    {
    cerr << "Problem loading player file: " << name << 
            "\n" << e.what () << endl;
    if (e.what () [0] == '*')   // file not found will start with an asterisk
      throw msg ("connect_player_does_not_exist");
    else
      throw msg ("error_loading");      
    }

} /* end of Player::Load */

void Player::Save ()
{
  WriteTopThing (PLAYER_DIR + gets ("name") + PLAYER_EXT, 
                "player", *this); 
} /* end of Player::Save */

void Character::DoCommand (const string & command, const bool forced)
{
  istringstream is (command);
  ProcessCommand (this, is, forced);
} /* end of Character::DoCommand */

// is flag set?
bool Character::HaveFlag (const string & name)
{
  return getb (name);
} // end of HaveFlag

// flag must be set
void Character::NeedFlag (const string & name, const string & message, const bool lookedUp)
{
  if (!name.empty () && !HaveFlag (name))
    NotPermitted (message, lookedUp);
} // end of NeedFlag

// flag must not be set
void Character::NeedNoFlag (const string & name, const string & message, const bool lookedUp)
{
  if (!name.empty () && HaveFlag (name))
    NotPermitted (message, lookedUp);
} // end of NeedNoFlag


/* Here when there is outstanding data to be read for this player */

void Player::ProcessRead ()
{
static const int maxCommandsPerSecond = control ().geti ("commands_per_second_maximum");
  
  if (closing_)
    return;   // once closed, don't handle any pending input
  
  // I make it static to save allocating a buffer each time.
  // Hopefully this function won't be called recursively.
  static vector<char> buf (1000);  // reserve 1000 bytes for reading into
  
  int nRead = read (s_, &buf [0], buf.size ());
  
  if (nRead == -1)
    {
    #ifdef WIN32
    if (errno != WSAEWOULDBLOCK)
    #else
    if (errno != EWOULDBLOCK)
    #endif
      {
      perror ("read from player");
      ClosePlayer ();
      }
    return;
    }

  if (nRead <= 0)
    {
    close (s_);
    cerr << "Connection " << s_ << " closed" << endl;
    s_ = NO_SOCKET;
    if (connstate_ == ePlaying)
      DoCommand ("quit");  // tell others the s/he has left
    return;
    }

  inbuf_ += string (&buf [0], nRead);    /* add to input buffer */

  /* try to extract lines from the input buffer */
  while (!closing_)
    {
    string::size_type i = inbuf_.find ('\n');
    if (i == string::npos)
      break;  /* no more at present */

    string sLine = trim (inbuf_.substr (0, i));  // extract first line 
    inbuf_.erase (0, i + 1);              // keep rest of string

    time_t now = time (NULL);
    
    // count # commands entered in a given second
    // we can use this to guard against spamming
    if (now == lastCommandTime_)
      {
      ++commandsThisSecond_;
      // check for rapid command entry right now (might happen in connect phase)
      if (commandsThisSecond_ > maxCommandsPerSecond)
         {
         send ("too_many_commands_per_second", messagemap ());
         inbuf_.clear ();
         ClosePlayer ();
         return;
         }  // end of timeout on playing player
      }
    else
      commandsThisSecond_ = 1;  // first command this second
        
    // empty lines don't count
    if (!sLine.empty ())
      lastCommandTime_ = now;             // note time, for idle timeout
    
    ProcessPlayerInput (this, trim (sLine));  /* now, do something with it */        
    }
    
} /* end of Player::ProcessRead */

/* Here when we can send stuff to the player. We are allowing for large
 volumes of output that might not be sent all at once, so whatever cannot
 go this time gets put into the list of outstanding strings for this player. */

void Player::ProcessWrite ()
{
  /* we will loop attempting to write all in buffer, until write blocks */
  while (s_ != NO_SOCKET && !outbuf_.empty ())
    {

    // how much to send
    int iLength = outbuf_.size ();

    // send to player
    int nWrite = write (s_, outbuf_.c_str (), iLength );

    // check for bad write
    if (nWrite < 0)
      {
      #ifdef WIN32
      if (errno != WSAEWOULDBLOCK)
      #else
      if (errno != EWOULDBLOCK)
      #endif
        {
        perror ("send to player");  /* some other error? */
        ClosePlayer ();
        }
      return;
      }

    // remove what we successfully sent from the buffer
    outbuf_.erase (0, nWrite);
      
    // if partial write, exit
    if (nWrite < iLength)
       break;

    } /* end of having write loop */

}   /* end of Player::ProcessWrite  */

// functor for sending a message (code) to all players

// The code (eg. "cannot_carry_that_weight") is looked up in message map
// (eg. messagemap () or errormap () or a command's map)
//
// This is done based on the player's language
//
// Substitutions are done (eg. %<player> becomes "Nick") based on entries in 
//  the ThingMap "subs"
// 
// The message is *not* sent to except1 nor except2
// 
// If room is non-zero, it is only sent to that room
//
// If notFoundIsOk is true, then a missing message is silently discarded

struct sendToPlayer
{
  const string code_;
  const StringMap & whichmap_;
  const ThingMap & subs_;
  const Character * except1_;
  const Character * except2_;
  const int room_;
  const bool notFoundIsOk_;
  
  // ctor
  sendToPlayer (const string & code,          // message code
                const StringMap & whichmap,   // where to look it up
                const ThingMap & subs,        // substitutions map
                const Character * except1,    // except this player (unless NULL)
                const Character * except2,    // except this player too  (unless NULL)
                const int room,               // only in this room (unless zero)
                const bool notFoundIsOk)      // don't worry if no message in 'whichmap'
      : code_ (code), whichmap_ (whichmap), subs_ (subs),
        except1_ (except1), except2_ (except2), room_ (room), notFoundIsOk_ (notFoundIsOk) {}
  // send to this player
  void operator() (Character * p) 
    {
    if (p->IsPlaying () && 
        p != except1_ && 
        p != except2_ && 
        (room_ == 0 || p->geti (ROOM) == room_))
      p->send (code_, whichmap_, subs_, true, notFoundIsOk_);  
    } // end of operator()  
};  // end of sendToPlayer

// send message to all connected players
// possibly excepting one (eg. the player who did something)
// or another (eg. the player to whom we did something)
// possibly only in one room (eg. for saying in a room)
void SendToAll (const string & code,          // message code
                const StringMap & whichmap,   // where to look it up
                const ThingMap & subs,        // substitutions map
                const Character * Except1,    // except this player (unless NULL)
                const Character * Except2,    // except this player too  (unless NULL)
                const int InRoom,             // only in this room (unless zero)
                const bool notFoundIsOk)      // don't worry if no message in 'whichmap'
{                                        
  if (!code.empty ())
    {
    for_each (ISEQ (playerlist ()), 
              sendToPlayer (code, whichmap, subs, Except1, Except2, InRoom, notFoundIsOk)); 
    // and mobs - is this wise?
    for_each (ISEQ (moblist ()), 
              sendToPlayer (code, whichmap, subs, Except1, Except2, InRoom, notFoundIsOk)); 
    }
} /* end of SendToAll */

// functor for sending messages to all players with a flag set
struct sendToPlayerWithFlag
{
  const string message_;
  const string flag_;
  
  // ctor
  sendToPlayerWithFlag (const string & message, const string flag)
      : message_ (message), flag_ (flag) {}
  // send to this player
  void operator() (Character * p) 
    {
    if (p->IsPlaying () && 
        p->getb (flag_))
      p->send (message_);   // TODO: fix this up
    } // end of operator()  
};  // end of sendToPlayerWithFlag

// send message to all connected players
// who have a particular flag set (eg. admin)
void SendToAll (const string & message, 
                const string & flag)
{
  if (!message.empty ())
    {
    for_each (ISEQ (playerlist ()), sendToPlayerWithFlag (message, flag)); 
    }
} /* end of SendToAll */

ObjectList::const_iterator getAnObject (const string & what,  // what they typed
                                         const ObjectList & contents,   // where to look (eg. room, player)
                                         const string & emptymessage,  // if nothing requested
                                         const string & notfoundmessage) // if not there
  {
  if (what.empty ())
    throw msg (emptymessage);

  // find it - if not found show error message
  return findItem (what, contents, notfoundmessage);

}

ObjectList::iterator getAnObject (const string & what,  // what they typed
                                   ObjectList & contents,   // where to look (eg. room, player)
                                   const string & emptymessage,  // if nothing requested
                                   const string & notfoundmessage) // if not there
  {
  if (what.empty ())
    throw msg (emptymessage);

  // find it - if not found show error message
  return findItem (what, contents, notfoundmessage);

}

const string Character::GetCoins (const int coins) const
  {
  if (coins <= 0)
    return control ().gets (prefix () + "currency_nil");
  else if (coins == 1)
    return control ().gets (prefix () + "currency_one");

  return MAKE_STRING (coins << " " << control ().gets (prefix () + "currency_plural"));

  } // end of Character::GetCoins

const string Character::GetName () const
  {
  string name = gets ("name");
  if (!name.empty ())
    return name;
  
  name = gets ("short");
  if (!name.empty ())
    return name;
 
  name = gets ("id");
  if (!name.empty ())
    return name;
 
  return "##noname##";
    
  } // end of Character::GetName

const int Character::GetRoom () const
  {
  return geti ("room");
  } // end of Character::GetRoom

const string FixMessage (const string & prefix,
                         const string & source, 
                         const ThingMap & subs,
                         const bool addNewline)
  {

  if (source.empty ())
    return source;
  
// look for %<blah> imbedded in the string

const char * p;
const char * pStart = source.c_str ();   // where buffer starts
string sFixed;

  // allow for same size as source so we do less reallocations
  sFixed.reserve (source.size ());

  while ((p = strchr (pStart, '%')))
    {
    // string before %
    sFixed += string (pStart, p++ - pStart);

    // p now points to the <blah>
    pStart = p; // where it starts

    if (*p == '%')
      sFixed += "%";
    else if (*p++ == '<')
      {
   
      // find first non-name
      p = fo_find_not_name (p);
  
      // not found? give up
      if (!p)
        break;
        
      // first non-name should be a >
      if (*p != '>')
        break; 
  
      // get name (eg. "target")
      string name = string (pStart + 1, p - pStart - 1);
      
      // look up named thing in substitution map
      ThingMap::const_iterator iter = subs.find (name);
      if (iter == subs.end ())
        sFixed += "{" + name + "}";   // indicator name not in map
      else  // look up short description of named item (per language)
        {
        string replacement = iter->second.gets (prefix + "short");
        // no short description? try short without language prefix
        if (replacement.empty ())
          replacement = iter->second.gets ("short");
        // no short description? try its name
        if (replacement.empty ())
          replacement = iter->second.gets (prefix + "name");
        // still nothing? try name without language prefix
        if (replacement.empty ())
          replacement = iter->second.gets ("name");
        // no luck - show them what we looked for
        if (replacement.empty ())
          replacement = "<<" + name + ":" + prefix + "short>>";
        sFixed += replacement;
        }
              
      }
    pStart = p + 1;   // move on past the %<blah> thing
                 
    } // end of loop
  
  sFixed += string (pStart);

  if (addNewline)
    sFixed += "\n";
  
  // make sentence caps
  return firstLetterCaps (sFixed);

  } // end of FixMessage

// warnings for admins that want them
void warnAdmin (const string & msg)
  {
  SendToAll ("WARNING: " + msg + "\n", "warn_admin");
  } // end of warnAdmin

const string Character::error (const string & code)
  {
  return convertErrorCode (code, gets ("prefix"));
  } // end of Character::error

const string Character::message (const string & code)
  {
  return convertMessageCode (code, gets ("prefix"));
  } // end of Character::message


// send a message which has been decoded
void Player::send (const string & msg, const bool newline)
  {
  outbuf_ += ConvertColour (msg, colourStack, currentColour, wantAnsi); 
  
  if (newline)
    outbuf_ += "\n";
  } // end of Player::send 

// send a message code - lookup in whichmap
void Player::send (const string & code, const StringMap & whichmap, const bool newline)
  {
  string prefix = gets ("prefix");    // language prefix, eg. fr_, en_
  
  // lookup message in map
  StringMap::const_iterator iter = whichmap.find (prefix + code);
  
  string msg;
  
  // if found, take it, otherwise use default of code
  if (iter != whichmap.end ())
      msg = iter->second;
  else
      msg = messagemap () [prefix + "no_message_message"] + " [" + prefix + code + "]";
  
  // send it
  outbuf_ += ConvertColour (msg, colourStack, currentColour, wantAnsi); 

  if (newline)
    outbuf_ += "\n";
  }

// send a decoded message, substitute with subs
void Player::send (const string & msg, const ThingMap & subs, const bool newline)
  {
  
  outbuf_ += ConvertColour (
      FixMessage (gets ("prefix"), msg, subs, false), 
                  colourStack, currentColour, wantAnsi); 
  if (newline)
    outbuf_ += "\n";
  } // end of Player::send

// send a message code - lookup in whichmap, substitute with subs
void Player::send (const string & code, const StringMap & whichmap, 
                   const ThingMap & subs, const bool newline, const bool notFoundIsOk)
  {
  string prefix = gets ("prefix");    // language prefix, eg. fr_, en_
  
  // lookup message in map
  StringMap::const_iterator iter = whichmap.find (prefix + code);
  
  string msg;
  
  // if found, take it, otherwise use default of code
  if (iter != whichmap.end ())
      msg = iter->second;
  else
      {
      if (notFoundIsOk)
        return;
      msg = messagemap () [prefix + "no_message_message"] + " [" + prefix + code + "]";
      }
      
  // send it
  outbuf_ += ConvertColour (FixMessage (prefix, msg, subs, false), colourStack, currentColour, wantAnsi); 

  if (newline)
    outbuf_ += "\n";
  } // end of Player::send
