// Room.cpp - handles rooms
// By: Odis

#include "world.h"
#include "room.h"
#include "smysql.h"
#include "stringutil.h"
#include "log.h"
#include "commandTable.h"
#include "split.h"
#include "timestamp.h"

//const
Room::Room(unsigned int rid)
{
  id = rid;
  
  // load contents from DB
  Load(id);
}

Room::Room( const string& vnum )
{
  LoadVnum(vnum);
}

// destructor
Room::~Room()
{
  RoomMap::iterator end = World::Instance()._room_map.end();
  
  // transfer any and all people from this room to limbo
  Room *limbo = World::Instance().FindRoom(ROOM_LIMBO);

  if ( !limbo )
  {
    glog( "Room::~Room -> Fatal Error: Limbo not found!" );
    abort();
  }
  
  while( _charList.size() > 0 )
    limbo->InsertAvatar( _charList.front(), true );


  //now remove from world wide list
  RoomMap::iterator j = World::Instance()._room_map.find(Get("vnum"));

  if ( j != World::Instance()._room_map.end() )
   World::Instance()._room_map.erase(j);
}

bool Room::Set( const string &set, const string &value )
{
   info[set] = value;
   return true;
}

string Room::Get( const string &get )
{
	return info[get];
}

bool Room::Load(unsigned int r)
{
	string query;
	string exits;
	Mysql::LMAP::iterator room_info;
	Mysql::RESULT z;

	query << "SELECT * FROM room WHERE room_id=" << id;

	if ( !Mysql::Instance().Load( query.c_str() ) )
		return false;

   z = Mysql::Instance()._info;

   // version first
   room_info = z[0].find("version");
   Set( "version", room_info->second );

   // load name
   room_info = z[0].find("name");
   Set( "name", room_info->second );

   // load description
   room_info = z[0].find("description");
   Set( "description", room_info->second );

   // load exits
   room_info = z[0].find("exits");
   Set( "exit_str", room_info->second );
   
   // load vnum
   room_info = z[0].find("vnum");
   Set( "vnum", room_info->second );
   
   // put in room map
   query = "";
   query << room_info->second;
   
   World::Instance()._room_map[query] = this;

   return true;
}

bool Room::LoadVnum(const string& vnum)
{
	string query;
	string exits;
	Mysql::LMAP::iterator room_info;
	Mysql::RESULT z;

	query << "SELECT * FROM room WHERE vnum=" << vnum;

	if ( !Mysql::Instance().Load( query.c_str() ) )
		return false;

   z = Mysql::Instance()._info;

   // version first
   room_info = z[0].find("version");
   Set( "version", room_info->second );

   // now the room id (which is not the vnum)
   room_info = z[0].find("room_id");
   id = (unsigned int)atoi((room_info->second).c_str());

   // load name
   room_info = z[0].find("name");
   Set( "name", room_info->second );

   // load description
   room_info = z[0].find("description");
   Set( "description", room_info->second );

   // load exits
   room_info = z[0].find("exits");
   Set( "exit_str", room_info->second );

   // load vnum
   room_info = z[0].find("vnum");
   Set( "vnum", room_info->second );

   // put in room map
   query = "";
   query << vnum;

   World::Instance()._room_map[query] = this;

   return true;
}

// insert an avatar into a room
// removes from previous room if applicable
// force a look depending on variable
void Room::InsertAvatar( Char *a, bool look )
{
  if ( a == NULL || a->_room == this)
   return;

  // in a previous room?
  if ( a->_room != NULL )
    a->_room->_charList.remove(a); // remove from list

  // put in new list and set new room ptr
  _charList.push_back(a);
  a->_room = this;
  
  if ( look == true )
   DoCmd( a, "look" );
}

void Room::Broadcast( const string &message )
{
	list< Char * >::iterator avatar;

    for ( avatar = _charList.begin(); avatar != _charList.end(); ++avatar )
    {
			if ( (*avatar)->GetStatus() == CONNECTED )
				(*avatar)->Send( message );
	}
}

bool Room::Save()
{
	string query;

	// We have to escape certain strings because they may possibly have a single quote in them.
	query << "UPDATE room SET name='" << Mysql::Instance().EscapeString( Get( "name" ) ) <<	"', ";
    query << "description='" << Mysql::Instance().EscapeString( Get("description") ) << "', ";
    query << "exits='" << GenExitStr() << "', ";
    query << "vnum=" << Get("vnum") << " WHERE room_id=" << id;

    Mysql::Instance().RealQuery( query.c_str() );
    return true;
}

void Room::InterpExitStr(string s)
{
  string key;
  string vnum;
  bool inkey = true;
  
  if ( s.empty() )
  {
    glog( "InterpExitStr: Room %d(%s) has no exits!", this->id, this->Get("name").c_str() );
    return;
  }
  
  for ( int j = s.length(), i = 0; i < j; i++ )
  {
    if ( s[i] == ';' )
    {
      exits[key] = World::Instance().FindRoom(vnum);
      inkey = true;
      
      // reset strings
      key = "";
      vnum = "";
      
      continue;
    }
    else if ( s[i] == ' ' )
    {
     inkey = false; // key has been loaded
     continue;
    }
     
    if ( inkey == true )
     key += s[i];
    else
     vnum += s[i];
  }
}

string Room::GenExitStr()
{
  string str;
  
  for ( RoomMap::iterator j = exits.begin(); j != exits.end(); j++ )
     str << j->first << " " << j->second->Get("vnum") << ";";
     
  return str;
}

void Room::AddExit( string s, Room *r )
{
  if ( s.empty() )
   return;

  exits[s] = r;
}

void Room::RemExit( string s )
{
  if ( s.empty() )
   return;

  RoomMap::iterator j = exits.find(s);
  
  if ( j != exits.end() )
   exits.erase(j);
}

// room editor

EditRoomHandler::EditRoomHandler()
{
	_edit = NULL;
}

EditRoomHandler::EditRoomHandler( Avatar * avatar, const string &vnum )
{
	string buf;

	if ( (_edit = World::Instance().FindRoom(vnum)) == NULL && CheckVnumRange( avatar, vnum ) )
    {
        avatar->Send("\n\rCreating new room...\n\r");

  	    buf << "INSERT INTO room ( version, vnum, name ) VALUES ( "
        << ROOM_VERSION << ", " << vnum << ", 'An Empty Room' )";

        if ( !Mysql::Instance().Query( buf.c_str() ) )
            glog( "EditRoomHandler::EditRoomHandler -> Failed Query!" );
        else
          _edit = new Room(vnum);
    }

	buf = "";
    buf << "You are now editing [{b" << vnum << "{x]\n\r";
	avatar->Send( buf );
}

void EditRoomHandler::Enter( Avatar * avatar )
{
	avatar->Send( "================================\n\r" );
	avatar->Send( "===      {WRoom   Editor{x       ===\n\r" );
	avatar->Send( "=== #help - More Information ===\n\r" );
	avatar->Send( "=== #quit - Exits Editor     ===\n\r" );
	avatar->Send( "================================\n\r" );
}

void EditRoomHandler::Exit( Avatar * avatar )
{
     delete this;
}

void EditRoomHandler::Handle( Avatar * avatar, const string &input)
{
	string checked;
	string option;
	string change;
	string buf;
	bool command = false;

	if ( input.empty () )
		return;

	checked = input;

	// Check to see if they're trying to
	for ( string::const_iterator cChar = input.begin(); cChar != input.end(); ++cChar )
    {
		if ( (*cChar) == '/' && cChar == input.begin() )
        {
			option = input.substr( 1, input.length() );
			if ( !strstr( input.c_str(), "quit" )  )// FIXEME: do not quit while editing
				CommandTable::Instance().Execute( avatar, option );
			return;
		}
        else if ( (*cChar) == '#' && cChar == input.begin() )
        {
			command = true;
			option = input.substr( 1, input.length() );
			checked = option;
			option.erase();
			break;
		}
        else if ( ( (*cChar) == '\n' || (*cChar) == '\r' ) && cChar == input.begin() )
        {
			checked.erase();
			break;
		}
	 }

	split( checked, option, change );

	if ( !strcasecmp( option, "quit" ) && command == true )
    {
		if ( _edit )
        {
 		 _edit->Save();
         _edit = NULL;
        }
		else
			avatar->ExitHandler();
		return;

	}
	else if ( !strcasecmp( option, "abort" ) && command == true )
    {
		if ( _edit )
            _edit = NULL;
		else
			avatar->ExitHandler();
		return;

	}
    else if ( ( !strcasecmp( option, "commands" ) || !strcasecmp( option, "help" ) ) && command == true  )
    {
		string buf;
		if ( !_edit )
			buf << "Enter the vnum you wish to edit.\n\r";
		else
        {

 	   	buf << "|------------------------------ Redit ---------------------------------|\n\r";
 	   	buf << "| #commands         This help file.                                    |\n\r";
 	   	buf << "| #show             Shows what you are editing.                        |\n\r";
 	   	buf << "| #quit             Quits the Room Editor and saves the room           |\n\r";
 	   	buf << "| #abort            Quits the Room Editor and -does not- save the room |\n\r";
 	   	buf << "| #save             Save your current progress.                        |\n\r";
 	   	buf << "| #delete           Delete the room you're editing.                    |\n\r";
 	   	buf << "|                                                                      |\n\r";
 	   	buf << "| Editing Commands                                                     |\n\r";
  		buf << "| name     <name>         Changes the room name.                       |\n\r";
 	   	buf << "| desc     <description>  Changes the room description.                |\n\r";
		buf << "| exit     <dir> <vnum>   Adds or changes an exit.                     |\n\r";
		buf << "| bexit    <dir> <vnum>   Adds or changes an exit and the opposite exit|\n\r";
		buf << "|                          in vnum's room.                             |\n\r";
   	    buf << "| vnum     <num>          Changes the vnum of the room.                |\n\r";
 	   	buf << "|                                                                      |\n\r";
 	   	buf << "| Note: To remove an exit, set the directions vnum to 0.               |\n\r";
 	   	buf << "|                                                                      |\n\r";
 	   	buf << "| {R*{x Commands preceded by a {B/{x will be executed as regular commands.     |\n\r";
 	   	buf << "|----------------------------------------------------------------------|\n\r\n\r";
 	   }
 	   avatar->Send( buf );
 	   return;
	}

		if ( checked.empty() || ( !strcasecmp( option, "show" ) && command == true ) )
        {
			if ( _edit )
            {
                RoomMap x = _edit->GetExits();

				buf << "[name: " << _edit->Get( "name" ) << "]\n\r";

                // display exits via loop
 				buf << "[exits:";
                for ( RoomMap::const_iterator i = x.begin(); i != x.end(); i++ )
                    buf << " " << i->first << "(#" << i->second->Get("vnum") << ")";
                    
                buf << " ]\n\r";


                buf << "[vnum: " << _edit->Get("vnum") << " ]\n\r";

				buf << "[description:\n\r\n\r" << _edit->Get("description") << "\n\r";
			}
            else
				buf << "You are not currently editing a room.\n\r";
			avatar->Send( buf );
			return;
		}
        else if ( _edit )
        {
			if ( !strcasecmp( option, "name" ) )
            {
				_edit->Set( "name", change );
				avatar->Send( "Name updated.\n\r" );
				return;
			}
            else if ( !strcasecmp( option, "desc" ) || !strcasecmp(option, "description") )
            {
				_edit->Set( "description", change );
				avatar->Send( "Description updated.\n\r" );
				return;
			}
            else if ( !strcasecmp( option, "exit" ) )
            {
                string dir = "";
  	            string vnum = "";

                // grab two arguments
  	            split( change, dir, vnum );

                if ( change.empty() || dir.empty() || vnum.empty() )
                {
                  avatar->Send("You must supply adaquete data!\n\r");
                  return;
                }

                // create or update direction
                _edit->AddExit(dir, World::Instance().FindRoom(vnum));
                
                // if set to null, remove the exit
                if ( _edit->GetExit(dir) == NULL )
                  _edit->RemExit( dir );

				avatar->Send( "Exits updated.\n\r" );
				return;
			}
            else if ( !strcasecmp( option, "bexit" )  )
            {
                Room *Old = NULL, *New = NULL;
                string dir, oppdir;
  	            string vnum;

                // grab two arguments
  	            split( change, dir, vnum );
                oppdir = GenOppDir( dir ); // generate opposite dir
                
                if ( oppdir.empty() )
                {
                 avatar->Send( "Only cardinal directions are valid for bexit's.\n\r" );
                 return;
                }
  	            
                // save old exit data so bidirectional exit can be destroyed if need be
                Old = _edit->GetExit(dir);

                // create or update direction in _edit
                _edit->AddExit(dir, (New = World::Instance().FindRoom(vnum)));
                
                // make/change the bidirectional exit
                if ( New )
                {
                 New->AddExit( oppdir, _edit );
                 New->Save();
                }

                // if set to null, remove the exit
                if ( _edit->GetExit(dir) == NULL )
                {
                  _edit->RemExit( dir );
                  Old->RemExit( oppdir );
                  Old->Save();
                }

				avatar->Send( "Exits updated.\n\r" );
				
				if ( New )
				{
                  RoomMap rmap = New->GetExits();
                  string buf;
                  
                  buf << "Room '" << New->Get("name") << "' (#" << New->Get("vnum") << ")'s exits:";

                  for ( RoomMap::const_iterator i = rmap.begin(); i != rmap.end(); i++ )
                    buf << " " << i->first << "(#" << i->second->Get("vnum") << ")";
                }
				
				return;
			}
            else if ( !strcasecmp( option, "vnum" ) )
            {
                Room *r = NULL;

                if ( !CheckVnumRange( avatar, change ) )
                {
                   string buf = "Vnum range: ";

                   buf << MIN_VNUM << " - " << MAX_VNUM << ". Please enter a valid vnum.\n\r";
                   avatar->Send(buf);

                   return;
                }
                
                if ( (r = World::Instance().FindRoom(change)) != NULL)
                {
                  avatar->Send( "That vnum is already being used!\n\r" );
                  return;
                }
                
                RoomMap::iterator x = World::Instance()._room_map.find(_edit->Get("vnum"));
                
                if ( x != World::Instance()._room_map.end() )
                 World::Instance()._room_map.erase(x);

                // do the changing
                World::Instance()._room_map[change] = _edit;
                _edit->Set("vnum", change);
                
				avatar->Send( "Vnum changed.\n\r" );
				return;
			}
			else if ( ( !strcasecmp( option, "save" ) && command == true ) )
            {
				_edit->Save();
				avatar->Send( "Room saved to database.\n\r" );
				return;
			}
            else if ( ( !strcasecmp( option, "delete" ) && command == true ) )
            {
				string buf;

                // remove from db
 	            buf << "DELETE FROM room WHERE room_id=" << _edit->id;
  	            Mysql::Instance().Query( buf.c_str() );

				// delete from mem
				delete _edit;

    	  		_edit = NULL;
    	  		
    	  		buf = "";
				buf << _edit->Get( "name" ) << "(#" << _edit->Get("vnum") << ") has been deleted.\n\r";
    	  		avatar->Send( buf );
    	  		
  	    		return;
  	  	    }
		}
        else if ( !_edit )
        {
        	string buf;

      	    if ( (_edit = World::Instance().FindRoom(option)) == NULL && CheckVnumRange( avatar, option ) )
            {
                avatar->Send("\n\rCreating new room...\n\r");

          	    buf << "INSERT INTO room ( version, vnum, name ) VALUES ( "
                << ROOM_VERSION << ", " << option << ", 'An Empty Room' )";

                if ( !Mysql::Instance().Query( buf.c_str() ) )
                    glog( "EditRoomHandler::EditRoomHandler -> Failed Query!" );
                else
                  _edit = new Room(option);
            }

        	buf = "";
            buf << "You are now editing [{b" << option << "{x]\n\r";
        	avatar->Send( buf );

       		return;
       	}

	if ( command )
		avatar->Send( "That is an invalid # command. Try #help\n\r" );
}

string EditRoomHandler::Prompt( Avatar * avatar )
{
	return (_edit == NULL ? "Enter vnum: " : ">> ");
}

////

// very important function, eliminates a great many reprodutions and hard coding
bool CheckVnumRange( Avatar *av, const string& str )
{
   unsigned int vnum = (unsigned int)atoi(str.c_str());
   bool is_imp = false;

   if (av->Get("group").find( "imp" ) != string::npos )
    is_imp = true;
    
   if ( (vnum < MIN_VNUM && is_imp == false) || vnum < 1 || vnum > MAX_VNUM )
    return false;

   return true;
}

// generate an opposite direction. Return result, stick in varaible if supplied
string GenOppDir( string dir, string* str )
{
  string oppdir;
  char c = '\0';

  if ( dir.length() > 1 )
   c = dir[1];
  
  switch ( dir[0] )
  {
    default:
             glog( "GenOppDir: Default case! Ack! Dir = %s.", dir.c_str() );
             return "";
             
    case 'n':
             if ( c == 'w' )
              oppdir = "se";
             else if ( c == 'e' )
              oppdir = "sw";
             else if ( dir == "northwest" )
              oppdir = "southeast";
             else if ( dir == "northeast" )
              oppdir = "southwest";
             else
              oppdir = "south";
             break;

    case 's':
             if ( c == 'w' )
              oppdir = "ne";
             else if ( c == 'e' )
              oppdir = "nw";
             else if ( dir == "southwest" )
              oppdir = "northeast";
             else if ( dir == "southeast" )
              oppdir = "northwest";
             else
              oppdir = "north";
             break;

    case 'w':
              oppdir = "east";
              break;

    case 'e':
              oppdir = "west";
              break;
  }
  
  if ( str != NULL )
   (*str) = oppdir;
   
  return oppdir;
}