/*

 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 <fstream>
#include <set>
#include <iostream>

using namespace std; 

#include "xmltree.h"
#include "xmlerrors.h"
#include "utils.h"
#include "globals.h"
#include "message.h"
#include "load.h"
#include "timer.h"
#include "resets.h"
#include "directory.h"
#include "gameutils.h"

// turn string into boolean
const bool convertBool (const string & s)
  {
string value = trim (s);
    
  return value == "1" || value == "y" || value == "Y";
  } // end of convertBool
    
void GetFilesInDirectory (const string & directory, set<string> & filenames)
  {
  Dir dir (directory);

  if (!dir.ok ())
    throw runtime_error (MAKE_STRING ("Directory " << directory << " not found."));
  
 // I'll put the names into a set simply to get them into alphabetic order
  
  string file;
  while (!(file = dir.read ()).empty ())
    if (file.size () >= 5 && 
        tolower (file.substr (file.size () - 4, 4)) == ".xml")
          filenames.insert (directory + file);
    
  } // end of GetFilesInDirectory
    
// load things from the control file (directions, prohibited names, blocked addresses)
void LoadControlFile ()
{
     
  cout << "Loading control data ..." << endl;

  XmlNode root;
  // load control file
  LoadXml (CONTROL_FILE, root);
  // find <control> node - must be there    
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "control");
  // load control attributes
  LoadTopThing (*i, control ());

} // end of LoadControlFile

struct fCopyMessagesToMap
  {
  string prefix_;
  StringMap & whichmap_;
  
  // ctor
  fCopyMessagesToMap (const string & prefix, StringMap & whichmap) 
    : prefix_ (prefix), whichmap_ (whichmap) {}
  
  // copy message to map prefixed by language prefix
  void operator() (const pair<string, string> msg)
    {
    whichmap_ [prefix_ + msg.first] = msg.second;
    }
  
  };  // end of fCopyMessagesToMap

void LoadLanguageErrors (const Object & lang)
  {
  cout << "Loading up errors in " << tocapitals (lang.gets ("language")) << endl;
  for_each (ISEQ (lang.get_sdata ()), fCopyMessagesToMap (lang.gets ("prefix"), errormap ()));
  } // end of LoadLanguageErrors

void LoadLanguageMessages (const Object & lang)
  {
  cout << "Loading up messages in " << tocapitals (lang.gets ("language")) << endl;
  for_each (ISEQ (lang.get_sdata ()), fCopyMessagesToMap (lang.gets ("prefix"), messagemap ()));
  } // end of LoadLanguageMessages

// load error messages for exceptions
void LoadMessages ()
{
     
TopThing messages;

  cout << "Loading errors and messages in each language ..." << endl;

  XmlNode root;
  // load control file
  LoadXml (MESSAGES_FILE, root);
  // find <errors> node - must be there    
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "errors_and_messages");
  // load errors attributes
  LoadTopThing (*i, messages);

  // put languages into their maps
  for_each (ISEQ (messages.items_ ["errors"]), LoadLanguageErrors);
  for_each (ISEQ (messages.items_ ["messages"]), LoadLanguageMessages);

  cout << ULONG( messagemap ().size () ) << " messages." << endl;
  cout << ULONG( errormap ().size () ) << " errors." << endl;

} // end of LoadMessages

struct fCheckMob
  {
  int count_;
  
  // ctor
  fCheckMob () : count_ (0) {}
  
  void operator() (Object & mob)
    {
    ++count_;   // count them for interest
    
    // check regexp can be compiled
    string match = mob.gets ("match");
  
    if (match.empty ())
      throw runtime_error ("No 'match' text for mob.");
    
    try
      {
      Regexp re;
      re.Compile (match);
      }
    catch (exception & e)
      {
      throw runtime_error (
        MAKE_STRING ("Bad 'match' text for mob: " << match
                    << "\n" << e.what ()));
      }
    
    if (mob.gets ("id").empty ())
      mob.sets ("id", UniqueID ());
    
    // roll some HP for this mob
    mob.seti ("hp", 
      roll (mob.geti ("hit_number"), mob.geti ("hit_dice")) + mob.geti ("hit_plus"));
    
    }  // end of operator ()
 
  const int GetCount () const { return count_; }

  }; // end of fCheckMob


// load a mob file
void LoadOneMobFile (const string & file)
{
  cout << "Loading mob file " << file << " ..." << endl;
  XmlNode root;
  LoadXml (file, root);   // load mobs file
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "mobs");
  int count = LoadList (*i, mobs ());    // load all mobs
  cout << count << " mobs." << endl;
} // end of LoadOneMobFile

// load all mob files
void LoadMobs ()
{
  timer t ("Loading mobs");
  set<string> filenames;
  GetFilesInDirectory (MOBS_DIR, filenames);
  for_each (ISEQ (filenames), LoadOneMobFile);    
  cout << ULONG( mobs ().size () )  << " total mobs in all mobs files." << endl;   

  cout << "Checking mobs ..." << endl;
  for_each (ISEQ (mobs ()), fCheckMob ());

} // end of LoadMobs

// load an object file
void LoadOneObjectFile (const string & file)
{
  cout << "Loading object file " << file << " ..." << endl;
  XmlNode root;
  LoadXml (file, root);   // load objects file
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "objects");
  int count = LoadList (*i, objects ());    // load all objects
  cout << count << " objects." << endl;
} // end of LoadOneObjectFile

// load all object files
void LoadObjects ()
{
  timer t ("Loading objects");
  set<string> filenames;
  GetFilesInDirectory (OBJECTS_DIR, filenames);
  for_each (ISEQ (filenames), LoadOneObjectFile);    
  cout << ULONG( objects ().size () )  << " total objects in all object files." << endl;    
} // end of LoadObjects

// load a reset file
void LoadOneResetFile (const string & file)
{
  cout << "Loading reset file " << file << " ..." << endl;
  XmlNode root;
  LoadXml (file, root);   // load resets file
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "resets");
  int count = LoadList (*i, resets ());    // load all resets
  cout << count << " resets." << endl;
} // end of LoadOneResetFile

// load all reset files
void LoadResets ()
{
  timer t ("Loading resets");
  set<string> filenames;
  GetFilesInDirectory (RESETS_DIR, filenames);
  for_each (ISEQ (filenames), LoadOneResetFile);    
  cout << ULONG( resets ().size () )  << " total resets in all resets files." << endl;    
} // end of LoadResets

struct fCheckExit
  {
  int count_;
  
  // ctor
  fCheckExit () : count_ (0) {}
  
  void operator() (Object & exit)
    {
    ++count_;   // count them for interest
    
    string handler = exit.gets ("handler");
    
    // check regexp can be compiled
    string match = exit.gets ("match");
  
    if (match.empty ())
      throw runtime_error ("No 'match' text for exit.");
    
    try
      {
      Regexp re;
      re.Compile (match);
      }
    catch (exception & e)
      {
      throw runtime_error (
        MAKE_STRING ("Bad 'match' text for exit: " << match
                    << "\n" << e.what ()));
      }
    
    int target = exit.geti ("target");
    
    if (world ().find (target) == world ().end ())
      throw runtime_error (MAKE_STRING (
          "Target room " << target << " does not exist for exit: " << match));
    }  // end of operator ()
 
  const int GetCount () const { return count_; }

  }; // end of fCheckExit



struct fCheckRoomExits
  {
  int count_;
  
  // ctor
  fCheckRoomExits () : count_ (0) {}
  
  void operator() (Object & room)
    {
    try
      {
     // a bit of correctness checking for each exit, count them too
      count_ += for_each (ISEQ (room.contents_), fCheckExit ()).GetCount ();  
      }
    catch (exception & e)
      {
      throw runtime_error (MAKE_STRING (
            "Error in room " << room.geti ("vnum") << ": " << e.what ()));
      }  
    } // end of operator ()
  
  const int GetCount () const { return count_; }
  
  }; // end of fCheckRoomExits
  
// load a room file
void LoadOneRoomFile (const string & file)
{
  cout << "Loading room file " << file << " ..." << endl;
  XmlNode root;
  LoadXml (file, root);   // load rooms file
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "rooms");
  int count = LoadList (*i, rooms ());    // load all rooms
  cout << count << " rooms." << endl;
} // end of LoadOneRoomFile

// load all room files
void LoadRooms ()
{
  timer t ("Loading rooms ...");

  set<string> filenames;
  GetFilesInDirectory (ROOMS_DIR, filenames);
  for_each (ISEQ (filenames), LoadOneRoomFile);    
  cout << ULONG( rooms ().size () )  << " total rooms in all rooms files." << endl;  

 // and, add them to the "world" map

  cout << "Rebuilding world map ..." << endl;
  RebuildWorldMap ();

  // we may as well make sure that rooms are valid, and count them :)
  cout << "Checking room exits ..." << endl;
  cout << for_each (ISEQ (rooms ()), fCheckRoomExits ()).GetCount () << 
        " exits found." << endl;    

} // end of LoadRooms

void fCheckCommand (Object & command)
  {
static CommandHandlerMap & commandmap = getCommandHandlerMap ();
  
  string handler = command.gets ("handler");
  
  // check handler routine exists
  if (!handler.empty ())
    {
     CommandHandlerMap::const_iterator handlerIter = 
              commandmap.find (handler);
    // check handler exists
    if (handlerIter == commandmap.end ())
      throw runtime_error ("Command handler " + handler + " does not exist.");
    }

  // check regexp can be compiled
  string match = command.gets ("match");

  if (match.empty ())
    throw runtime_error ("No 'match' text for command.");
  
  try
    {
    Regexp re;
    re.Compile (match);
    }
  catch (exception & e)
    {
    throw runtime_error (
        MAKE_STRING ("Bad 'match' text for command: " << match
                    << "\n" << e.what ()));
    }
  
  // see if the command has an alias, that way we can have duplicated commands
  // without having to put the conditions in twice
  string alias = command.gets ("alias");
  if (!alias.empty ())
    {
    ObjectList::iterator commandIter = FindObject (commands (), "name", alias);
    if (commandIter == commands ().end ())
      throw  runtime_error (MAKE_STRING ("Command for alias " << alias << " does not exist."));
    
    }

  } // end of fCheckCommand


// load a command file
void LoadOneCommandFile (const string & file)
{
  cout << "Loading command file " << file << " ..." << endl;

  XmlNode root;
  // load commands file
  LoadXml (file, root);
  // find <commands> node - must be there    
  list<XmlNode>::const_iterator i = NeedNode (root.children_, "commands");
  
  int count = LoadList (*i, commands ());    // load all commands
  
  // a bit of correctness checking
  for_each (ISEQ (commands ()), fCheckCommand);

  cout << count << " commands." << endl;

} // end of LoadOneCommandFile

// load all command files
void LoadCommands ()
{
  cout << "Loading commands ..." << endl;

  set<string> filenames;
  GetFilesInDirectory (COMMANDS_DIR, filenames);
  for_each (ISEQ (filenames), LoadOneCommandFile);    
 
  cout << ULONG( commands ().size () )  << " total commands in all command files." << endl;
    
} // end of LoadCommands

//~ void LoadRoom (const XmlNode & node)
  //~ {

  //~ // only want <room> nodes
  //~ if (node.GetName () != "room") 
     //~ return;
 
  //~ Room room;    // build it up here
  
  //~ LoadTopThing (node, room);  // load room attributes
  
  //~ int vnum = room.geti ("vnum");   // get the vnum
  //~ if (vnum == 0)
    //~ throw runtime_error (MAKE_STRING 
      //~ ("No vnum for room at byte " << ULONG( node.GetPosition () ) << " in rooms file."));
  
  //~ // don't have duplicate rooms
  //~ if (rooms ().find (vnum) != rooms ().end ())
    //~ throw runtime_error (MAKE_STRING 
      //~ ("Room " << vnum << " duplicated."));
  
  //~ // insert into map
  //~ rooms () [vnum] = room; 
  
  //~ } // end of LoadRoom



//~ // load rooms and exits
//~ void LoadOneRoomFile (const string & file)
//~ {
  //~ cout << "Loading room file " << file << " ..." << endl;

  //~ XmlNode root;
  //~ // load rooms file
  //~ LoadXml (file, root);
  //~ // find <rooms> node - must be there    
  //~ list<XmlNode>::const_iterator i = NeedNode (root.children_, "rooms");
  //~ uint oldcount = uint( rooms ().size () );
  //~ // load each room
  //~ for_each (ISEQ (i->children_), LoadRoom);  

  //~ uint count = uint( rooms ().size () ) - oldcount;
  //~ // summarise
  //~ cout << count << " rooms." << endl;
  
//~ } // end of LoadOneRoomFile

//~ // load all rooms files
//~ void LoadRooms ()
//~ {
  //~ timer t ("Loading rooms");

  //~ set<string> filenames;
  //~ GetFilesInDirectory (ROOMS_DIR, filenames);
  //~ for_each (ISEQ (filenames), LoadOneRoomFile);    
  
  //~ // we may as well make sure that rooms are valid, and count them :)
  //~ cout << "Checking room exits ..." << endl;
  //~ cout << for_each (ISEQ (rooms ()), fCheckRoomExits ()).GetCount () << 
        //~ " exits found." << endl;    
    
  //~ cout << uint( rooms ().size () )  << " total rooms in all rooms files." << endl;
    
//~ } // end of LoadRooms

// build up our commands map and connection states
void LoadThings ()
{

  timer t ("Loading all MUD data");

  
  // load files
  LoadControlFile ();     // general control information
  LoadCommands ();        // commands, eg. "look"
  LoadMessages ();        // success/failure messages
  LoadRooms ();           // all rooms
  LoadObjects ();         // objects ready for putting into rooms
  LoadMobs ();            // mobs
  LoadResets ();          // what goes where

  ProcessResets ();       // apply the resets

// every minute check for idle players
  eventQueue ().push (Event ("EventCheckIdle", 60000, -1, 0)); // check for idle players

  cout << "Loading completed." << endl;

  // save all players every 2 minutes
  eventQueue ().push (Event ("EventSavePlayers", 120000, -1, 0));
  
// a bit of code timing ...

  //~ timer t ("doing lots of things to Tsythia");
  //~ for (int i = 0; i < 5000; i++)
    //~ m->DoCommand ("look");
    
} // end of LoadThings

struct fInsertStrings
{
  // where to put them
  thing & t_;
  // ctor
  fInsertStrings (thing & t) : t_ (t) {}
  // insert one string
  void operator() (pair <string, string> what)
      { t_.sets (what.first, what.second);  }
};  // end of fInsertStrings

struct fInsertNumbers
{
  // where to put them
  thing & t_;
  // ctor
  fInsertNumbers (thing & t) : t_ (t) {}
  // insert one string
  void operator() (pair <string, string> what)
      {
      int i;
      istringstream is (what.second);
      is >> i >> ws;
      if (is.fail () || !is.eof ())
        throw runtime_error (
          MAKE_STRING ("Bad numeric value '" << what.second << 
                       "' for attribute '" << what.first << "'"));
      t_.seti (what.first, i); 
      }
};  // end of fInsertNumbers

struct fInsertFloats
{
  // where to put them
  thing & t_;
  // ctor
  fInsertFloats (thing & t) : t_ (t) {}
  // insert one string
  void operator() (pair <string, string> what)
      {
      double i;
      istringstream is (what.second);
      is >> i >> ws;
      if (is.fail () || !is.eof ())
        throw runtime_error (
          MAKE_STRING ("Bad floating-point value '" << what.second << 
                       "' for attribute '" << what.first << "'"));
      t_.setf (what.first, i); 
      }
};  // end of fInsertFloats

struct fInsertFlags
{
  // where to put them
  thing & t_;
  // ctor
  fInsertFlags (thing & t) : t_ (t) {}
  // insert one string
  void operator() (pair <string, string> what)
      {
      string value = trim (tolower (what.second));
        
     // false values
      if (value == "0" ||
          value == "n" ||
          value == "")   // empty can be considered false
       t_.setb (what.first, false);
      // true values
      else if ( 
          value == "1" ||
          value == "y"
          )
       t_.setb (what.first, true);
      else  throw runtime_error (
          MAKE_STRING ("Bad boolean (flag) value '" << what.second << 
                       "' for attribute '" << what.first << "'"));
      }
};  // end of fInsertFlags

// load from an XML node into a thing
void LoadThing (const XmlNode & node, thing & t)
{
list<XmlNode>::const_iterator nodeIter;

  try
    {
    // find <string> node
    nodeIter = FindNode (node.children_, "string");
      
    // insert all string attributes
    if (nodeIter != node.children_.end ())
      for_each (ISEQ (nodeIter->attributes_), fInsertStrings (t));
  
    // find <number> node
    nodeIter = FindNode (node.children_, "number");
      
    // insert all number attributes
    if (nodeIter != node.children_.end ())
      for_each (ISEQ (nodeIter->attributes_), fInsertNumbers (t));
  
    // find <float> node
    nodeIter = FindNode (node.children_, "float");
      
    // insert all float attributes
    if (nodeIter != node.children_.end ())
      for_each (ISEQ (nodeIter->attributes_), fInsertFloats (t));
  
    // find <flag> node
    nodeIter = FindNode (node.children_, "flag");
      
    // insert all flag attributes
    if (nodeIter != node.children_.end ())
      for_each (ISEQ (nodeIter->attributes_), fInsertFlags (t));
    }
  catch (exception & e)
    {
    // re-throw it with the node name and byte offset appended
    THROW_ERROR (XML_ERROR_BAD_ATTRIBUTE_VALUE,
                 e.what () <<
                 nodeIter->GetName () <<
                 uint( nodeIter->GetPosition () )  );
    }
} // end of LoadThing

void LoadXml (const string & name, XmlNode & root)
  {
  ifstream f (name.c_str (), ios::in);
  if (!f)
    throw runtime_error (MAKE_STRING ("** Cannot open file " << name));
  
  string sText, s;
   
  // read entire file
  while (getline (f, s))
    sText += s + "\n";
       
  XmlParser parser (sText, root);
  
  try
    {
    parser.Parse ();
    }

    // we will catch exceptions, add a bit of information to them, and re-throw them
  catch(exception & e)
    {
    string::size_type iPosition = parser.GetPosition ();
      
     // go back 30 characters, or to start
    int iStart = std::max<int> ( int(iPosition) - 30, 0);  
      
    // count linefeeds to work out which line it was on
    int iLine = (int) count_if (&sText [0], &sText [iPosition], bind2nd (equal_to<char> (), '\n'));
    // extract out the context (30 characters before, 20 characters after)
    string sDisplay = sText.substr (iStart, 50);
    // get rid of newlines, carriage returns, tabs etc.
    transform (sDisplay.begin (), 
               sDisplay.end (), 
               sDisplay.begin (), 
               non_printables_to_spaces);    
    
    // throw it
    THROW_ERROR (XML_ERROR_GENERAL_MESSAGE,
                 sDisplay << 
                 string (iPosition - iStart + 2, '-') << 
                 iLine + 1 << 
                 e.what ());

    }
  
  } // end of LoadXml
  
  
struct fLoadList
{
  list<Object> & items_;    // where to put it
  int count_;               // how many we did
  // ctor
  fLoadList (list<Object> & items)
    : items_ (items), count_ (0) {}
  void operator() (const XmlNode & node)
      {
      if (node.GetName () == "item")    // want this one
        {
        Object obj;
        obj.clear ();
        LoadThing (node, obj);
        // recurse to load things inside things
        LoadList (node, obj.contents_);   
        items_.push_back (obj); // add item
        ++count_;
        } // end of finding a wanted node        
      } // end of operator()
   int GetCount () const { return count_; }  
};  // end of fLoadList

// load a list of things, such as an inventory
int LoadList (const XmlNode & node, 
              list<Object> & items)
{
  return for_each (ISEQ (node.children_), fLoadList (items)).GetCount ();    
} // end of LoadList

// load a whole batch of things, eg. player inventory, equipment, spells
void LoadContents (const XmlNode & node,  // node to search (eg. player node)
                   const string & name,   // what for (eg. "inventory")
                   list<Object> & contents) // where to put it (eg. player.inventory)
  {
  list<XmlNode>::const_iterator nodeIter = FindNode (node.children_, name);
  if (nodeIter != node.children_.end ())
    LoadList (*nodeIter, contents);   // load entire inventory
  }

struct fLoadNode
  {
  TopThing & t_;
  // ctor
  fLoadNode (TopThing & t) : t_ (t) {}
  // load one node into the object
  void operator() (const XmlNode & node)
    {
    if (node.GetName ().empty ())
      return;
    
    try
      {
    
      if (node.GetName () == "string")
        for_each (ISEQ (node.attributes_), fInsertStrings (t_));
      else if (node.GetName () == "number")
        for_each (ISEQ (node.attributes_), fInsertNumbers (t_));
      else if (node.GetName () == "float")
        for_each (ISEQ (node.attributes_), fInsertFloats (t_));
      else if (node.GetName () == "flag")
        for_each (ISEQ (node.attributes_), fInsertFlags (t_));
      else
        LoadList (node, t_.items_ [node.GetName ()]);   // load entire whatever
        
      } // end of try
    catch (exception & e)
      {
      // re-throw it with the node name and byte offset appended
      THROW_ERROR (XML_ERROR_BAD_ATTRIBUTE_VALUE,
                   e.what () <<
                   node.GetName () <<
                   uint( node.GetPosition () )  );
      } // end of catch
    
    } // end of operator() 
  
  };  // end of fLoadNode

// load from an XML node into an top-level thing (eg. a player, room etc.)
void LoadTopThing (const XmlNode & node, TopThing & t)
{
  for_each (ISEQ (node.children_), fLoadNode (t));
} // end of LoadTopThing
