/*
....[@@@..[@@@..............[@.................. 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
------------------------------------------------------------------------------
room.cc
*/

#include "file.h"
#include "string.h"
#include "llist.h"
#include "description.h"
#include "repop.h"
#include "indexable.h"
#include "server.h"
#include "area.h"
#include "object.h"
#include "npc.h"
#include "shop.h"
#include "room.h"

#include "global.h"


Room::~Room()
{
	Repop *rep;
	Char *ch;
	Object *obj;
	LList<Object> tList = objects;

	repops.reset();
	while( ( rep = repops.remove() ) )
	{
		if( rep->getPtr() )
			rep->getPtr()->setRepop( 0 );

		delete rep;
	}

	chars.reset();
	while( ( ch = chars.remove() ) )
	{
		// Caller should verify whether there are PCs in the room because
		// we killem all.
		ch->fromWorld();
		delete ch;
	}

	inv.reset();
	while( ( obj = inv.remove() ) )
	{
		obj->fromWorld();
		delete obj;
	}
}


const String & Room::getScope() const
{
	return area->getKey();
}


Room * lookupRoom( const String & x )
{
	return lookupRoom( Index( x ) );
}

Room * lookupRoom( const char * x )
{
	return lookupRoom( Index( x ) );
}

Room * lookupRoom( const Index & x )
{
	Area *area;
	LList<Area> tlist = areas;
	tlist.reset();

	while( ( area = tlist.peek() ) )
	{
		tlist.next();
		if( area->getKey() == x.getScope() )
			break;
	}

	if( area )
		return area->lookupRoom( x );

	return 0;
}


void Room::addRepop( Repop *x )
{
	repops.addAfterCurrent( x );

	// Ugg, this is ugly, just because I am too lazy to write a
	// 'tail' into the LList class. Will have to do that soon.
	// For now this is so repops will add onto the list in order.
	repops.next();
}


void Room::repop()
{
			Repop	*repop;
			NPC		*npc;
			NPC		*npcLast = 0;
			Object	*obj = 0;
			Object	*objLast = 0;
	const	Object	*objProto;
	const	NPC		*npcProto;

	repops.reset();

	while( ( repop = repops.peek() ) )
	{
		repops.next();

		// This allows things like resetting a sword inside a stone
		// or other cool things. I don't do it with NPC's because
		// of possibility of a thief stealing something everytime
		// a NPC got re-equipped. No re-equips.

		if( repop->getPtr() )
		{
			if( repop->type == 'M' )
				npcLast = 0;
			else if( repop->type == 'O' )
				objLast = (Object *)repop->getPtr();

			continue;
		}

		switch( repop->type )
		{
			default: continue;

			// M - load NPC
			// O - load Object
			// G - give Object to last NPC
			// E - equip last NPC with Object
			// P - put Object in last Object loaded with 'O' code
			// N - nest object in 'immediate' last object loaded

			case 'M':		// Load NPC, set npcLast to this one.

							npcProto = lookupNPC( repop->index );
							if( npcProto )
							{
								if( npcProto->isShopKeeper() )
									npc = new ShopKeeper( *npcProto );
								else
									npc = new NPC( *npcProto ); 

								npc->toWorld();
								npcLast = npc;
								addCharInv( npc );

								npc->setRepop( repop );
								repop->setPtr( npc );
							}

							break;


			case 'O':		// Load object, set objLast to this one.

							objProto = lookupObj( repop->index );
							if( objProto )
							{
								obj = new Object( *objProto ); 

								obj->toWorld();
								objLast = obj;
								addObjInv( obj );

								obj->setRepop( repop );
								repop->setPtr( obj );
							}

							break;


			case 'E':		// Check for last NPC loaded. Equip object 

							if( !npcLast )
								continue;

							objProto = lookupObj( repop->index );
							if( objProto )
							{
								obj = new Object( *objProto );
								obj->toWorld();
								npcLast->addObjInv( obj );
								// val = wear position
								npcLast->equip( obj, repop->val );

								obj->setRepop( repop );
								repop->setPtr( obj );
							}

							break;


			case 'G':		// Check for last NPC loaded. Give object to him.

							if( !npcLast )
								continue;

							objProto = lookupObj( repop->index );
							if( objProto )
							{
								obj = new Object( *objProto );
								obj->toWorld();
								npcLast->addObjInv( obj );

								Cout << "Giving " << obj->getShort()
									<< " to " << npcLast->getShort()
									<< endl;

								obj->setRepop( repop );
								repop->setPtr( obj );
							}
							else
							{
								Cout << "Repop lookup failed for "
										<< repop->index.asString() << endl;
							}
							break;


			case 'P':		// Check for last object loaded

							if( !objLast )
								continue;

							objProto = lookupObj( repop->index );
							if( objProto )
							{
								obj = new Object( *objProto );
								obj->toWorld();
								objLast->addObjInv( obj );

								obj->setRepop( repop );
								repop->setPtr( obj );
							}

							break;

			case 'N':		// Nest object in immediate last object loaded.
							// Set last obj then to last immediate.
							if( !( objLast = obj ) )
								continue;

							objProto = lookupObj( repop->index );
							if( objProto )
							{
								obj = new Object( *objProto );
								obj->toWorld();
								objLast->addObjInv( obj );

								obj->setRepop( repop );
								repop->setPtr( obj );
							}

							break;
		}
	}
}


const String & Room::getExtraDesc( const char * x )
{
	static String retNULL;
	Description * d;

	ed.reset();
	while( ( d = ed.peek() ) )
	{
		ed.next();
		if( d->isName( x ) )
			return d->getDesc();
	}

	return retNULL;	
}


void Room::addObjInv( Object * obj )
{
	obj->toRoom( this );
	inv.add( obj );
}


void Room::rmObjInv( Object * obj )
{
	obj->fromRoom();
	inv.remove( obj );
}


void Room::addCharInv( Char *ch )
{
	ch->toRoom( this );
	chars.add( ch );
}


void Room::rmCharInv( Char *ch )
{
	ch->fromRoom();
	chars.remove( ch );
}


void Room::out( const char *str )
{
	Char *ch;
	chars.reset();
	while( ( ch = chars.peek() ) )
	{
		ch->out( str );
		chars.next();
	}
}


void Room::outAllCharExcept( const char *str, Char *ch1, Char *ch2 )
{
	Char *ch;
	chars.reset();
	while( ( ch = chars.peek() ) )
	{
		if( !(ch == ch1) && !(ch == ch2) )
			ch->out( str );
		chars.next();
	}
}



int Room::readFrom( InFile &in )
{
	int err;
	char buf[ BUF*2 ];

	if( !in )
		in.error("InFile stream not open.");

	if( *in.getword( buf ) != '{' )
		in.error( "Room::readFrom() - no '{' found");
 
//	index = in.getword( buf );
	name = in.getstring( buf );
	desc = in.getstring( buf );
	/*original_flags =*/ in.getnum();
	/* expansion   */ in.getnum();

	while(1)
	{
		in.getword( buf );
		switch( *buf )
		{ 
			//  Door block
			//  Syntax example: <dir> <name> <flags> <key-index> <to_room> <desc>
			//    D {
			//      4 coffin 1|2|32 thalos:key-3  keroon:room-1
			//    }

			case 'D':
			{
				in.getword( buf );
				if( !*buf == '{' )
					in.error( "Door block not found for symbol 'D'" );

				int    dir     =  in.getnum();
				String doorName =  in.getword( buf );
				int    flags   =  in.getnum();
				Index kIndex   =  in.getword( buf );
				Index rIndex   =  in.getword( buf );
				String str = in.getstring( buf );

				if( dir > 5 )
					in.error( "Room::readFrom - direction out of range.\n" );

				exits[ dir ] = new Exit( doorName, rIndex, kIndex, flags, str ); 

				if( *in.getword( buf ) != '}' )
					in.error( "Room::readFrom - door block invalid." );
				break;
			}
			break;

			case 'R':
			{
				Repop *repop = new Repop;
				if( ( err = repop->readFrom( in ) ) == 0 )
					addRepop( repop );
				else
				{
					Cout << "Invalid repop code, skipping.\n";
					delete repop;
				}
			}      
			break;

			case 'E':
			{
				// Extra description block
				Description *d = new Description;
				if( ( err = d->readFrom( in ) ) == 0 )
					ed.addTop( d );
				else
				{
					Cout << "Invalid extra description block, skipping.\n";
					delete d;
				}
			}
			break;

			default:
				return 1;
		}
	}
	return 1;
}


int Room::writeTo( OutFile &out ) const
{
	String str;

	if( !out )
		return 0;

	out << '{'   << endl;
//	out << index << endl;
	out << name  << '~' << endl;
	out << desc  << '~' << endl;
	out << 0 << ' ' << 0 << endl;

	/* write the doors */
	for( int i = 0; i < MAX_DIR; i++ )
	{
		if( exits[i] )
		{
			out << "D{" << i << " "
				<< exits[i]->getName() << " "
				<< exits[i]->original_flags[1] << " "
				<< exits[i]->key.asString() << " "
				<< exits[i]->room_index.asString() << " "
				<< exits[i]->desc << "~}\n";
		}
	}
	// end doors

	// write the repops
	Repop *repop;
	repops.reset();
	while( (repop = repops.peek()) )
	{
		repops.next();
		out<<"R";
		repop->writeTo( out );
		out<<endl;  
	} 

	out << '}' << endl;
	return 1;
}


// Destroy ExitIndex, create Exit and link to the real room.
void Room::hardLink()
{
	for( int i = 0; i < MAX_DIR; i++ )
	{
		if( exits[ i ] )
			exits[ i ]->hardLink();
	}
}


Exit * Room::getExit( int dir )
{
	if( dir >= MAX_DIR )
		return 0;

	return exits[ dir ];
}


Room *Room::toRoom( int dir )
{
	if( dir >= MAX_DIR )
		return 0;

	//Need to finish this
	if( !exits[ dir ] )
		return 0;

	if( !exits[ dir ]->room_ptr )
		error( "Room::toRoom - exit points to NULL room." ); 

	return exits[ dir ]->room_ptr;
}


void Room::addExit( Exit *exit, int dir )
{
	if( dir < MAX_DIR )
		exits[ dir ] = exit; 
}

 
void Room::deleteExit( int dir )
{
	if( dir >= 0 && dir < MAX_DIR )
	{
		delete exits[ dir ];
		exits[ dir ] = 0;
	}
}


Char *Room::getChar( const String & str )
{
	Char *ch;

	chars.reset();
	while( ( ch = chars.peek() ) )
	{
		chars.next();
		
		if( ch->isName( str ) )
			break;
	}

	return ch;
}


PC *Room::getPC( const String & str )
{
	Char *ch;

	chars.reset();
	while( ( ch = chars.peek() ) )
	{
		chars.next();
		if( ch->isNPC )
			continue;

		if( ch->isName( str ) )
			break;
	}

	return (PC*)ch;
}


NPC *Room::getNPC( const String & str )
{
	Char *ch;

	chars.reset();
	while( ( ch = chars.peek() ) )
	{
		chars.next();
		if( ch->isPC )
			continue;

		if( ch->isName( str ) )
			return (NPC*)ch;
	} 

	return 0;
}


Object * Room::getObj( const String & str )
{
	Object *obj;
	inv.reset();
	while( ( obj = inv.peek() ) )
	{
		inv.next();

		if( obj->isName( str ) )
			return obj;
	} 

	return 0;
}



void Exit::reset( )
{
	flags = original_flags;
}


void Exit::hardLink( )
{
	room_ptr = lookupRoom( room_index );
}