///////////////////////////////////////////////////////////
///////////////// Have an itch? Scratch it! ///////////////
///////////////////////// SCRATCH /////////////////////////
/////////////////////  A MUD  Server   ////////////////////
///////////////////// By: Jared Devall ////////////////////
/////////////////////      Thanks:     ////////////////////
/////////////////////  DIKU/Merc/ROM   ////////////////////
///////////////////// Aetas/Deus Gang  ////////////////////
/////////////////////       Beej       ////////////////////
///////////////////////////////////////////////////////////

#include <string>
#include <iostream>
#include <fstream>
#include <time.h>
#include <process.h> // for _execl
#include <cstdio>
#include <signal.h>
#include <list>
#include "stringutil.h"
#include "world.h"
#include "timestamp.h"
#include "log.h"

using namespace std;

// from strhash.cpp
bool InitStrHash();

// Holds the "infinite" loop that runs the mud
void World::Exist( const string &startMessage, int port, bool copyover )
{
	string buf;
    
	if ( copyover == false )
		SocketServer::Instance().Start();
    else
		CopyoverRecovery();

    if ( !InitStrHash() )
    {
      glog( "Failed to initialize string hashing table! Aborting..." );
      abort();
    }

    if ( !LoadRooms() )
    {
      glog("Failed to load rooms! Aborting..." );
      abort();
    }

    if ( !LoadHelps() )
    {
      glog("Failed to load help files! Aborting..." );
      abort();
    }

	buf << "\n\t" << startMessage << " on port " << port;
	buf << "\n\tTime: " << Timestamp::Instance().GetDateTime();
	cout << buf << endl << endl;

	_worldExists = true; // This is used so we can have a Character Shutdown the Server.


	// _worldExists can be set to false by shutdown; otherwise run
	while( _worldExists )
		Monitor();

	cout << "Shutdown successfully." << endl;
	return;
}

// scan the database for all rooms
bool World::LoadRooms( )
{
	Mysql::LMAP::iterator room_info;
	string query = "SELECT room_id FROM room";
	unsigned int i = 0;

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

    Mysql::RESULT z = Mysql::Instance()._info;

    cout << "\n\nLoading Rooms...";

	for ( int x = 0; x < z.size(); x++ )
    {
        i++;
        room_info = z[x].find("room_id");

        new Room( (unsigned int)atoi((room_info->second).c_str()));
	}
	
	cout << " Done.\n";
	cout << i << " rooms loaded.\n\n";
	
	cout << "\nLinking exits...";

    // link exits
	for ( RoomMap::const_iterator x = World::Instance()._room_map.begin(); x != World::Instance()._room_map.end(); x++ )
      x->second->InterpExitStr(x->second->Get("exit_str"));

    cout << " Done.\n\n";

	return true;
}

// Load all the help files
bool World::LoadHelps( )
{
	Mysql::LMAP::iterator help_info;
	string query = "SELECT name FROM help";
	unsigned int i = 0;

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

    Mysql::RESULT z = Mysql::Instance()._info;

    cout << "\n\nLoading helps...";

	for ( int x = 0; x < z.size(); x++ )
    {
        i++;
        help_info = z[x].find("name");

        new Help( help_info->second );
	}

	cout << " Done.\n";
	cout << i << " help files loaded.\n\n";

	return true;
}

Help * World::FindHelp( const string &target )
{
    HelpMap::const_iterator r = World::Instance()._help_map.find(target);

    // didn't match flat out, search via keyword FIXME!!
    if (r == World::Instance()._help_map.end())
      return NULL;

    return r->second;
}

Room * World::FindRoom( const string &target )
{
    RoomMap::const_iterator r = World::Instance()._room_map.find(target);

    if (r == World::Instance()._room_map.end())
    {
//     glog( "FindRoom: Room '%s' does not exist", target.c_str());
     return NULL;
    }

    return r->second;
}

// Saves connections and spawns a new instance of the mud
void World::Copyover( )
{
	list< Avatar *>::iterator a_it;
	Avatar * dead;
	ofstream fp("file.cpy");
	
	if ( !fp ) {
		cout << "Copyover file could not be opened." << endl;
		return;
	}

	for ( a_it = _avatarList.begin(); a_it != _avatarList.end(); ) {
		// These people aren't logged in. Kill them!
		if ( (*a_it)->GetStatus() != CONNECTED ) {
			dead = (*a_it);
			a_it = _avatarList.erase( a_it );
			
			delete dead;
			continue;
		}
		
		
		(*a_it)->Save();
		fp << (*a_it)->GetSocket()->GetDescriptor() << " " <<
			(*a_it)->Get( "name" ) << " " <<
			(*a_it)->GetSocket()->GetSocketIP() <<  endl;
		a_it++;
	}
	
	fp << "-1";
	fp.close();
	string controlSock;
	controlSock << SocketServer::Instance().GetHostSocket();	
	_execl("./scratch", "3000", "-copyover", controlSock.c_str(), NULL );
}

// Sets up the connections after a copyover
void World::CopyoverRecovery( )
{
	Avatar * avatar;
	ifstream fp("file.cpy");
	int desc;
	std::string name;
	std::string host;
	std::string lhost;	

	if ( !fp ) {		
		cout << "Copyover file could not be opened." << endl;
		exit(1);
	}

	for(;;) {
		fp >> desc >> name >> host;
		if (desc == -1)
			break;
		avatar = new Avatar;
		avatar->GetSocket()->SetDescriptor( desc );
		avatar->GetSocket()->SetSocketIP( string(host) );
		avatar->Set( "name", name );
		avatar->Load();
		avatar->SetStatus( CONNECTED );
		// Add user to socket and avatar lists
		SocketServer::Instance()._socketList.push_back( avatar->GetSocket() );
		_avatarList.push_back( avatar );
		
		// We're all done. Let them know. :)
		avatar->Send( "Reboot Successful.\n\r" );
		
		// Stick them defaultly in InputHandler
		avatar->StackHandler( new InputHandler );		
	}
	fp.close();
	remove( "file.cpy" ); // Delete the temp file
	cout << "Copyover Recovery is now Complete." << endl;
}

Avatar * World::FindAvatar( const string &target )
{
	list< Avatar * >::iterator it;
	
	for ( it = _avatarList.begin(); it != _avatarList.end(); ++it ) {
		if ( str_cmp( (*it)->Get( "name" ), target ) )
			return (*it);
	}	
	return NULL;
}

// Sends a message to all avatars meeting broadcast_type
void World::Broadcast( const string &message, BROADCAST_TYPE type ) {
	list< Avatar * >::iterator avatar;

	if ( type == ALL )
    {
		for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); ++avatar )
			(*avatar)->Send( message );
	}
    else if ( type == WCONNECTED )
    {
		for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); ++avatar )
        {
			if ( (*avatar)->GetStatus() == CONNECTED ) 
				(*avatar)->Send( message );
		}		
	}
}

// This is the 'shutdown' of the MUD.
void World::Die( )
{
	_worldExists = false;
}

// This is where we process everything going on	
void World::Monitor( )
{
	bool newConnection = false;   
    
    FlushOutput();
    if ( ( newConnection = SocketServer::Instance().Monitor() ) == true )
    {
		Avatar * avatar = new Avatar;
		avatar->SetSocket( SocketServer::Instance()._socketList.back() );
		_avatarList.push_back( avatar );
		avatar->StackHandler( new GetNameHandler );
		newConnection = false;
		avatar->Send( "    ============================================= \n\r" );
		avatar->Send( "    ===              {WKaladea MUD{x              === \n\r" );
		avatar->Send( "    ============================================= \n\r\n\r" );
		avatar->Send( "                 {RIn Early Development.{x\n\r" );
		avatar->Send( "                       {W- Odis -{x\n\r\n\r" );

	} 
	UpdateTimers();
	UpdateAvatars();
	HandleInput();	
}

// Fixes the prompts for everyone
void World::FlushOutput( ) {
	list< Avatar * >::iterator avatar;

	for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); ++avatar ) {
		(*avatar)->FlushOutput();
	}
}

// Checks to see if any avatars have disconnected or got input
void World::UpdateAvatars( )
{
	list< Avatar * >::iterator avatar;

	for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); ++avatar )
    {
		if ( (*avatar)->GetSocket()->GotInput() == true )
			(*avatar)->SetGotInput( true );

		if ( (*avatar)->GetSocket()->IsDisconnected() )
			(*avatar)->SetDisconnected( true );
	}
	
	KillFlaggedAvatars();
}

// Deletes the avatars from the list and disconnects them
void World::KillFlaggedAvatars( )
{
	list< Avatar * >::iterator avatar;
	Avatar * avatar2;

	for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); )
    {
		avatar2 = (*avatar);
		if ( avatar2->IsDisconnected() )
        {
			avatar = _avatarList.erase( avatar );
			delete avatar2;
		}
        else
			++avatar;
	}
}

// Handles all avatar input
void World::HandleInput( )
{
	std::list< Avatar * >::iterator avatar;

	// This is for handling input.
	// Loops through avatars, checks to see if there's input to play with, 
	// then goes on that.
	if ( _avatarList.empty() != true )
	{
		for ( avatar = _avatarList.begin(); avatar != _avatarList.end(); ++avatar )
        {
			// Don't mess with input until delayed text is finished
			if ( (*avatar)->GotInput() == true /*&& (*avatar)->waiting == false*/ ) // We need to make sure there's actually some input to handle
            {
				(*avatar)->HandleInput( (*avatar)->GetSocket()->GetInput() ); // This sends user input to right handler.
				(*avatar)->GetSocket()->FlushInput( ); // We need to clean the inbuf or it'll loop using the same stuff.
			}
			
		}
	}

	UpdateAvatars();
}

// Updates our timers to see if they need to fire, etc
void World::UpdateTimers ()
{
	if ( _timers.empty() )
 		return;

	time_t now = time(NULL);
	Timer *timer;
	
	for ( timer = _timers.top(); timer->When() <= now; timer = _timers.top() ) {
		_timers.pop();

		if ( !timer->_enabled || !timer->Fire() ) {
			delete timer;
 
 		   if ( _timers.empty() ) 
 		   	break;
		} else {
			_timers.push( timer );
		}
 	}
 }

// Schedule a new timer event
void World::Schedule( Timer *timer ) {
	_timers.push( timer );
}

// a new timer event
void World::Unschedule( Timer *timer ) {
 	timer->_enabled = false;
}