/*
....[@@@..[@@@..............[@.................. MUD++ is a written from
....[@..[@..[@..[@..[@..[@@@@@....[@......[@.... scratch multi-user swords and
....[@..[@..[@..[@..[@..[@..[@..[@@@@@..[@@@@@.. sorcery game written in C++.
....[@......[@..[@..[@..[@..[@....[@......[@.... This server is an ongoing
....[@......[@..[@@@@@..[@@@@@.................. development project.  All 
................................................ contributions are welcome. 
....Copyright(C).1995.Melvin.Smith.............. Enjoy. 
------------------------------------------------------------------------------
Melvin Smith (aka Fusion)         msmith@falcon.mercer.peachnet.edu 
MUD++ development mailing list    mudpp-list@spice.com
------------------------------------------------------------------------------
main.cc
*/


// The game loop is here.
// Also the main objects are created here: server, players, mobs, objs

#include "io.h" 
#include "string.h"
#include "server.h"
#include "room.h"
#include "indexable.h"
#include "llist.h"
#include "area.h"
#include "edit.h"
#include "help.h"
#include "env.h"
#include "global.h"

#include <sys/time.h>
#include <sys/resource.h>

const char * version = "\n\r\
MUD++ Cyberspace Server version 0.1\n\r\
Copyright(C) 1995, 1996 Melvin Smith\n\n\r";

#if defined( ultrix )
extern "C"
{
	int getrlimit( int, struct rlimit * );
	int setrlimit( int, struct rlimit * );
}
#endif

Server server( 4000 );

LList<PC>		pcs;
LList<NPC>		npcs;
LList<Area>		areas;
LList<Object>	objects;

void loadAreas();
void buildWorld();
void Nanny( PC *, String & str = "" );

int DOWN;
int REBOOT;

int pulse = PULSE_PER_SEC;

int main( int argc, char *argv[] )
{
//	pid_t pid;
	Cout << version;

	int thePort = 4000;
	if( argc > 1 )
	{
		thePort = atoi( argv[1] );
		if( thePort <= 0 )
			thePort = 4000;
	}

	chdir( AREA_DIR );
	umask( ~( S_IRUSR | S_IWUSR ) );

	// Raise the soft limit on coredump size to the max (hard limit)
	// if it isn't already. This isn't POSIX but it is SYSV and 4.3+BSD
	// so it should compile. If it doesn't, delete these lines and
	// invoke mud++ through a csh or ksh after giving the command
	// 'unlimit coredumpsize'
	// otherwise you may not get a core for debugging.

	rlimit rl;
	
	getrlimit( RLIMIT_CORE, &rl );
	rl.rlim_cur = rl.rlim_max;
	setrlimit( RLIMIT_CORE, &rl );

/*	Zap off into daemon land.
	if( ( pid = fork() ) < 0 )
	{
		perror( "fork" );
		exit( 0 );
	}
	else if( pid != 0 )
	{
		// parent
		exit(0);
	}
*/

	// Create a new session. 
//	setsid();

	// See if we have been spawned by another instance.
	// If so, inherit all open descriptors.

	PC *pc;
	String str;

	Cout << "Looking for reboot.tab\n";

	InFile tab( "./reboot.tab" );

	if( !tab )
	{
		Cout << "No reboot.tab, this is a fresh boot.\n\r";
		server.boot( thePort );
	}
	else
	{
		char name[ 256 ];
		char host[ 256 ];
		int desc;
		int port;

		Cout << "Live reboot, loading descriptor data.\n\r";

		// For now I assume that first entry is the socket control
		// entry so I dont examine it.
		tab.getstring( name ); // name
		tab.getstring( host ); // hostname ignored for this entry 
		port = tab.getnum();
		desc = tab.getnum();
		server.boot( port, desc );

		for( ; ; )
		{
			if( tab.eof() )
				break;

			Cout << "Restoring old connection.\n";
			tab.getstring( name );
			if( tab.eof() )
				break;

			tab.getstring( host );
			if( tab.eof() )
				break;

			port = tab.getnum();
			desc = tab.getnum();
			Cout << "\n" << name << ":" << host << ":" << port << ":" << desc << endl;
			
			Socket *newSock = new Socket( host, port, desc );
			server.addSock( newSock );

			pc = new PC( &server, newSock, name );
			pcs.add( pc );
			newSock->write( "\n\rSuccessful Reboot.\n\r" );
			
			// Here I got lazy. The potential is here to go ahead
			// and load the player -and- the mob he was fighting
			// if there was one, along with mobs old hp/mana
			// For now this is still better than closing connection.
			pc->setState( STATE_GET_NAME );
			str = name;
			Nanny( pc, str );
		}

		Cout << "reboot.tab loaded.\n";
		tab.close();
		remove( "./reboot.tab" );
	}

	server.useNameServer();

	Cout << "Loading help Index.\n";
	loadHelps();
	Cout << "Loading areas.\n";
	loadAreas();
	Cout << "Linking world.\n";
	buildWorld();

	// OK here goes the game loop
	while( !DOWN )
	{
		// High precision timer
		server.sleep( PULSE );

		// Pulse
		if( !pulse-- )
		{
			heartbeat();
			pulse = PULSE_PER_SEC;
		}

		// Check for new connections
		if( server.newConnection() )
		{   
			Socket * sock = server.accept();
			if( sock )
			{
				pc = new PC( &server, sock, "" );
				printf("New connection from %s\n", sock->getHostName() );
				pc->setState( STATE_INIT );
				pc->view( "./title" );
				pcs.addTop( pc );
				Nanny( pc );
				pc->flush();
			} 
		}
 
		// Poll all active descriptors 
		server.poll();
 
		// Set the current pointer back to head of player list
		pcs.reset();

		while( ( pc = pcs.peek() ) )
		{
			String commd( 256 );

			// Keep this at top of loop, since continue is used
			pcs.next();

			// Check for guys to boot.
			if( pc->getState() == STATE_BOOT )
			{
				// Make sure he gets any pending text
				// This could be a disconnect, slay, etc. so inform him
				pc->flush();
				server.remove( pc->getSock() );
				pc->getSock()->close();

				if( pc->inRoom() )
					pc->inRoom()->rmCharInv( pc );

				pcs.remove( pc );
				delete pc;
				continue;
			}

			// Boot sockets with exceptional condition
			// Need to add handler instead of booting.
			if( server.error( pc->getSock() ) )
			{
				Cout << "Closing link to " << pc->getName() <<endl; 
				server.remove( pc->getSock() );
				pc->getSock()->close();

				if( pc->inRoom() )
					pc->inRoom()->rmCharInv( pc );

				pcs.remove( pc );
				delete pc;
				continue;
			}

			// Read socket if ready
			if( server.canRead( pc->getSock() ) )
			{
				pc->readInput();

				if( pc->getSock()->eof() )
				{
					pc->setState( STATE_BOOT );
					Cout << "EOF Read: Closing link to " << pc->getName() << endl;
					server.remove( pc->getSock() );
					pc->getSock()->close();
					pcs.remove( pc );
					delete pc;
					continue;
				}
			}

			if( !pc->inBuf() && !pc->outBuf() )
				continue;
			else if( pc->pagePending() )
				pc->page( "" );
			else if( pc->getEditor() )
			{
				if( !pc->getNextCommand() )
					continue;

				commd = pc->getCommand();
				if( commd == "quit" )
				{
					pc->quitEditor();
				}
				else
				{
					// Need to check return status and pass command on
					// to regular interpreter if not recognized
					pc->getEditor()->command( commd );
				}

				pc->flush();
				pc->putPrompt();
				continue;
			}
			else if( pc->inBuf() )
			{
				if( pc->getState() >= STATE_INIT 
						&& pc->getState() < STATE_PLAYING )
				{
					if( !pc->getNextCommand() )
						continue;

					commd = pc->getCommand();
					Nanny( pc, commd );
				}
				else if( pc->inBuf() && pc->getState() > STATE_PLAYING ) 
				{
					switch( pc->getState() )
					{
						default        : pc->setState( STATE_PLAYING );
						break;

						//case STATE_EMAIL       : pc->do_mail( buf ); break;
						//case STATE_EDIT_TEXT   : pc->Edit( buf ); break;
					}

//					pc->flush();
//					pc->putPrompt();
//					continue;
				}
				else
				{
					pc->getNextCommand();

					pc->command();

					if( pc->outBuf() )
						pc->flush( );

					if( pc->pagePending( ) )
						pc->page( "" );
					else
						pc->putPrompt( );
				}
			}

			// Process some buffered output
			if( pc->outBuf( ) )
			{
				pc->flush();
				if( pc->getState() == STATE_PLAYING ) 
					pc->putPrompt();
			}
		}
	}
	// Bottom of game loop

	if( REBOOT )
	{
		OutFile out( "reboot.tab" );

		// Write the control socket descriptor so the Server object can
		// pick it up on way back in after execl()

		out << "mudpp.1~" << "localhost~"
			<< server.getPort() << " "
			<< server.getDesc();

		// Write each player name and descriptor so mud++ can reload
		// the player files after execl()

		pcs.reset();
		while( ( pc = pcs.peek() ) )
		{
			out << "\n"
				<< pc->getName() << '~'
				<< pc->getSock()->getHostName() << '~'
				<< pc->getSock()->getPort() << " "
				<< pc->getSock()->getDesc();
			pcs.next();
		}
		
		out.close();

		execl( "../src/mud++", "mud++", (char*)0 );
	}

	exit( 0 );
}



void loadAreas()
{
	String areaKey;
	char filename[256];	
	char buf[256];
	Area *area;

	InFile in( AREA_FILE );

	while( in )
	{
		in.getline( filename );
		Cout << "Area-[" << filename << "]" << endl;
		if( !*filename )
			break;

		InFile inArea( filename );
		if( inArea )
		{
			inArea.getword( buf );
			if( *buf == 'A' )
			{
				areaKey = inArea.getword( buf );
				area = new Area( areaKey );
				area->readFrom( inArea );
				areas.addTop( area );
			}
			else
			{
				Cout << "Area file [" << filename << "] has no area header.\n";
				exit(0);
			}
		}
		else
		{
			Cout << "Error opening area file [" << filename << "]\n";
			exit(0);
		}
	}

	Cout <<"\nDatabase booted sucessfully.\n"; 
}


// Link rooms, do initial repop

void buildWorld()
{
	Area *area;

	areas.reset();

	while( ( area = areas.peek() ) )
	{
		areas.next();
		area->hardLink();
	}

	// 2 seperate loops in case I decide to do something funky with doors.

	areas.reset();

	while( ( area = areas.peek() ) )
	{
		areas.next();
		area->repop();
	}
}