/****************************************************************************\
 *									      *
 *			C R E A T O R    O F   L E G E N D S		      *
 *				(AberMud Version 5)			      *
 *									      *
 *  The Creator Of Legends System is (C) Copyright 1989 Alan Cox, All Rights  *
 *  Reserved.		  						      *
 *									      *
 \****************************************************************************/

/*
 *	System Functions
 *
 *	1.00	Original Version
 *	1.04	Added Pointer Validation Facilities
 *	1.05	Made Alloc clear the structure allocated
 *	1.10	Added Memory Statistics On Boot
 *	1.11	Fixed Link to null bug
 *	1.12	Added INHERIT to FindSub
 *	1.13	Added Comments
 *	1.14	Fixed Texts >32766 times
 *	1.15	Made it FreeText item names and also free text block when does
 *		a FreeText.
 *	1.16	Added Register Optimisations
 *	1.17	75 way hashed text list
 *	1.18	Added Save/Restore on NextInByClass for DOCLASS nesting
 *	1.19	Made FindInByClass etc do mask 0 correctly.
 *	1.20	Added QuickAllocText feature
 *	1.21	Tidied up Error crash recovery
 *	1.22	Bug fixes for 5.06
 *	1.23	Extended Error Logging
 *	1.24	Fixed SHARE and Save/Load
 *	1.25	Changed to reduce string load, due to unix malloc overhead
 *	1.26	Flushes log writes
 *	1.27	Deleting items now checks for tables and deletes them.
 *	1.28	Removed pointless uid logging
 *	1.29	Cleaned up for strict ANSI C
 */

#include "System.h"
#include "User.h"

extern USER UserList[];

Module  "System";
Version "1.29";
Author  "----*(A)";


/*
 *	Display An Error Message, called via the Error() macro
 */

void ErrFunc(er,mod,ver,line,file)
char *er,*mod,*ver,*file;
int line;
{
	short ct=0;
	static int err_yes=0;
	printf("MODULE: %s  VERSION: %s\n",mod,ver);
	printf("FILE: %s  LINE: %d\n",file,line);
	printf("ERROR LOGGED WAS\n");
	printf("%s.\n",er);
	Log("MODULE: %s  VERSION: %s",mod,ver);
	Log("FILE: %s  LINE: %d",file,line);
	Log("ERROR LOGGED WAS");
	Log("%s",er);
	if(err_yes==0&&post_boot)
	{
		err_yes++;
		printf("\n\nAttempting to rescue universe\n");
		while(ct<MAXUSER)
		{
			if(UserList[ct].us_Item)
				ExitUser(ct);
			ct++;
		}
		DisintegrateAll();
		DisintegrateAll();
		if(SaveSystem("gonebang.uni")==0)
		{
printf("\n\n-----------------------------------------------\n\n");
printf("System successfully saved as 'gonebang.uni'\n");
printf("CAUTION: Ensure this rescued game works correctly before replacing the \
older\nversions with it.\n");
		}
		else
			printf("Attempt to save failed.\n");
	}
	else
		printf("Cannot rescue game.\n");
	exit(1);
}

/*
 *	Log an error to the log file 
 */

void Log(char *fmt, ...)
{
	char *x;
	long v;
	va_list va;
	static FILE *file=NULL;
	if(file==NULL) file=fopen(LOG_FILE,"a");
	if(file==NULL)
		return;	/* Try to open log file */
		
	va_start(va, fmt);
	time(&v);
	x=ctime(&v);
	*strchr(x,'\n')=0;
	fprintf(file,"%s:",x);
	vfprintf(file, fmt, va);		/* Save error message */
	fprintf(file,"\n");			/* Return to tidy it */
	fflush(file);
	va_end(va);
}
	
/*
 *	Allocate a block of memory via the Allocate macro. The memory is
 *	cleared to 0 and if close to no free memory a warning is displayed
 */
	
char *AllocFunc(int x, char *mod, char *ver, int line, char *file)
{
	register char *a;
	register int n=0;
	a=malloc(x);		/* Request Memory */
	if(a==NULL)
	{
			ErrFunc("Out Of Memory",mod,ver,line,file);	/* Failed */
	}
	while(n<x)
		a[n++]=0;	/* Clear Memory */
	return(a);		/* Return block */
}


TPTR AllocText(char *x)
{
	return((TPTR)strdup(x));
}

TPTR AllocComment(char *x)
{
	return((TPTR)strdup(x));
}

TPTR QuickAllocText(char *x)
{
	return((TPTR)strdup(x));
}	


long TextNumber(register TPTR t)
{
	Error("TextNumber: Text not listed");
	return(0L);	/* Compiler happiness */
}

/*
 *	Return a string pointer given a TXT pointer
 */

char *TextOf(TPTR x)
{
	return((char *)x);
}

void FreeText(TPTR x)
{
	free((char *)x);
}


void FreeComment(TPTR x)
{
	free((char *)x);
}

/*	
 *	Fundamental Item Controllers
 *
 *	The item list works on a similar structure to the texts, a one way linked
 *	list. However to handle containment the system is far more complex in its
 *	potential twinings. Each item has a pointer to its parent (container),
 *	a pointer to the first thing in a linked list it contains, and a pointer
 *	to the next item in the linked list of items contained by its parent.
 */	

/*
 *	Disconnect An Item From Any Parent Objects
 */

int UnlinkItem(register ITEM *x)
{
	register ITEM *a;
#ifdef CHECK_ITEM
	CheckItem(x);
#endif
	if(O_FREE(x))		/* Already disconnected */
		return(0);
	if(O_CHILDREN(O_PARENT(x))==x)	/* First item special case */
	{
		O_CHILDREN(O_PARENT(x))=O_NEXT(x);	/* Move pointer on */
		O_PARENT(x)=NULL;		/* Clear our pointers */
		O_NEXT(x)=NULL;
		return(0);
	}
	a=O_CHILDREN(O_PARENT(x));
	if(a==NULL)				/* More cockup checks */
		Error("UnlinkItem: Parent Empty");
	while(O_NEXT(a))
	{
		if(O_NEXT(a)==x)		/* Unlink from list */
		{
			O_NEXT(a)=O_NEXT(x);
			O_PARENT(x)=NULL;
			O_NEXT(x)=NULL;
			return(0);
		}
		a=O_NEXT(a);
	}
	Error("UnlinkItem: Parent Does Not Contain Child");
}

/*
 *	Place an UnLinked item into a container. Note an UNLINKED item.
 */

int LinkItem(ITEM *a, ITEM *b)
{
#ifdef CHECK_ITEM
	CheckItem(a);
	if(b)
		CheckItem(b);
#endif
	if(!O_FREE(a))
		return(-1);	/* item not free yet */
	O_PARENT(a)=b;
	if(b)
	{
		O_NEXT(a)=O_CHILDREN(b);	/* Hook into chain */
		O_CHILDREN(b)=a;
	}
	else
		O_NEXT(a)=NULL;		/* Moving into nothingness */
	return(0);
}


/*
 *		ITEM CONTROLLERS
 */

ITEM *ItemList=NULL;		/* Global list of items, see above */

/*
 *	Create an item, setting up its name adj and noun fields. We have to set
 *	adj and noun so you can refer to it. If you set up an item with no 
 *	adjective and noun, you have a problem!
 */

ITEM *CreateItem(char *name, int ad, int no)
{
	register ITEM *a=Allocate(ITEM);/* Memory for the item  */
	O_PARENT(a)=NULL;		/* Starts in the void   */
	O_NEXT(a)=NULL;			/* Connected to nothing */
	O_CHILDREN(a)=NULL;		/* No children either   */
	O_PROPERTIES(a)=NULL;		/* No properties        */
	O_ADJECTIVE(a)=ad;		/* Adjective as asked   */
	O_NOUN(a)=no;			/* Noun as was asked    */
	a->it_Users=0;			/* Not yet locked into  */
	O_STATE(a)=0;			/* Set its state to zero*/
	a->it_MasterNext=ItemList;	/* Link into lists */
	ItemList=a;
	a->it_ActorTable=0;		/* Start with tables 0  */
	a->it_ActionTable=0;
	a->it_Class=0;			/* Clear class maskings */
	a->it_Perception=0;		/* Generally visiblish  */
	a->it_Name=AllocText(name);	/* allocate the text string */
	return(a);
}

/*
 *	Delete an object, very similar in many ways to freeing a text but with
 *	more requirements: Item must be EMPTY, IN VOID, UNLOCKED, and with NO
 *	properties
 */

int FreeItem(register ITEM *x)
{
	register ITEM *a;
#ifdef CHECK_ITEM
	CheckItem(x);
#endif
	/* Free tables first of all */
	if(x->it_ObjectTable!=NULL)
	{
		DeleteTable(x->it_ObjectTable);
		FreeTableHeader(x->it_ObjectTable);
		x->it_ObjectTable=NULL;
	}
	if(x->it_SubjectTable!=NULL)
	{
		DeleteTable(x->it_SubjectTable);
		FreeTableHeader(x->it_SubjectTable);
		x->it_SubjectTable=NULL;
	}
	if(x->it_DaemonTable!=NULL)
	{
		DeleteTable(x->it_DaemonTable);
		FreeTableHeader(x->it_DaemonTable);
		x->it_DaemonTable=NULL;
	}
	/* We must delete tables first - we may have a lock into ourself */
	if(x->it_Users)		/* Still in use */
		return(-1);
	if(!O_EMPTY(x))		/* Not empty */
		return(-2);
	if(O_PROPERTIES(x))
		return(-3);	/* Delete ALL Props First */
	if(!O_FREE(x))
		return(-4);	/* Still linked */
	if(x->it_Superclass)	/* Dump superclasses */
	{
		UnlockItem(x->it_Superclass);
	}
	if(ItemList==x)
	{
		ItemList=x->it_MasterNext;	/* Now delete the entry */
		FreeText(x->it_Name);
		free((char *)x);
		return(0);
	}
	a=ItemList;
	if(!a)
		Error("FreeItem: Empty Item List");
	while(a->it_MasterNext)
	{
		if(a->it_MasterNext==x)
		{
			a->it_MasterNext=x->it_MasterNext;
			FreeText(x->it_Name);
			free((char *)x);
			return(0);
		}
		a=a->it_MasterNext;
	}
	Error("FreeItem: Invalid Item Handle");
}

void LockItem(ITEM *x)	/* Mark an item 'in use' */
{
#ifdef CHECK_ITEM
	CheckItem(x);
#endif
	x->it_Users++;
}

void UnlockItem(ITEM *x)	/* Mark an item 'out of use' */
{
#ifdef CHECK_ITEM
	CheckItem(x);
#endif
	x->it_Users--;
	if((x->it_Users==0)&&(x->it_Perception==-1))	/* Pending deletion */
	{
		FreeItem(x);
		return;
	}
	if(x->it_Users<0)
	{
		Log("Can't unlock %s",NameOf(x));
		Error("Unlock: Item already free");
	}
}

void SetState(ITEM *x, short v)
{
#ifdef CHECK_ITEM
	CheckItem(x);
#endif
	x->it_State=v;
}

/*
 *	Change the vocabulary on an item NEVER set to -1,-1
 */

void SetVocab(ITEM *item, short ad, short no)
{
#ifdef CHECK_ITEM
	CheckItem(item);
#endif
	O_NOUN(item)=no;
	O_ADJECTIVE(item)=ad;
}

/*
 *	Test for a word matching
 */

int WordMatch(ITEM *i, short a, short n)
{
#ifdef CHECK_ITEM
/*	CheckItem(i);	*/	/* Removed because it made testing TOO slow */
#endif
	if((a==-1)&&(n==O_NOUN(i)))
		return(1);
	if((a==O_ADJECTIVE(i))&&(n==O_NOUN(i)))
		return(1);
	return(0);
}

/*
 *	Test if item is visible
 */

int CanSee(short pe, ITEM *it)
{
#ifdef CHECK_ITEM
/*	CheckItem(it);	*/	/* Removed because it made testing too slow */
#endif
	if(it->it_Perception>pe)
		return(0);
	return(1);
}

/*
 *	Find a matching item, anywhere in the game
 */

ITEM *FindMaster(short pe, short a, short n)
{
	register ITEM *i=ItemList;	/* Walk the entire list of items */
	while(i)
	{
		if((WordMatch(i,a,n))&&(CanSee(pe,i)))
			return(i);
		i=i->it_MasterNext;
	}
	return(NULL);
}

/*
 *	Find the next item in the game matching the words
 */

ITEM *NextMaster(short pe,register ITEM *i, short a, short n)
{
#ifdef CHECK_ITEM
	CheckItem(i);
#endif
	i=i->it_MasterNext;
	while(i)
	{
		if((WordMatch(i,a,n))&&(CanSee(pe,i)))
			return(i);
		i=i->it_MasterNext;
	}
	return(NULL);
}

/*
 *	Find the first item in container matching the words
 */

ITEM *FindIn(short pe, ITEM *i, short a, short n)
{
	if(i==NULL)
		return(NULL);
#ifdef CHECK_ITEM
	CheckItem(i);
#endif
	i=O_CHILDREN(i);
	while(i)
	{
		if((WordMatch(i,a,n))&&(CanSee(pe,i)))
			return(i);
		i=O_NEXT(i);
	}
	return(NULL);
}

/*
 *	Find the next item matching the words
 */

ITEM *NextIn(short pe, register ITEM *i, short a, short n)
{
#ifdef CHECK_ITEM
	CheckItem(i);
#endif
	i=O_NEXT(i);
	while(i)
	{
		if((WordMatch(i,a,n))&&(CanSee(pe,i)))
			return(i);
		i=O_NEXT(i);
	}
	return(NULL);
}

/*
 *	Properties: The substructure handlers.
 *
 *	Every substructure has a SUB entry at the start of the data (see 
 *	system.h), this is all that is used by these routines
 */

/*
 *	Find the first substructure of type 'key' in item.
 */

SUB *FindSub(ITEM *item, register short key)
{
	ITEM *b=NULL;
	register SUB *a=item->it_Properties;	/* Start of property list */
#ifdef CHECK_ITEM
	CheckItem(item);
#endif
	while(a)			/* Walk list */
	{
		if(a->pr_Key==key)
			return(a);	/* Found it */
		if(a->pr_Key==KEY_INHERIT)
			b=((INHERIT *)(a))->in_Master;	/* Note inheritance */
		a=a->pr_Next;
	}
	if(!post_boot)
		return(NULL);	/* Don't share while booting */
	if(b)
	{
		a=b->it_Properties;	/* Do properties if any	inherited */
		while(a)
		{
			if(a->pr_Key==key)
				return(a);
			a=a->pr_Next;
		}
	}
	return(NULL);
}

/*
 *	Find the next substructure of type 'key' in item.
 *	NOTE: This does not walk across two items. Thus an inherited chain
 *	and a non-inherited chain will not both be found. This is a 'feature'
 */

SUB *NextSub(SUB *sub, register short key)
{
	register SUB *a=sub->pr_Next;
	while(a)
	{
		if(a->pr_Key==key)
			return(a);
		a=a->pr_Next;
	}
	return(NULL);
}	

/*
 *	Allocate a substructure of type 'key' and size 'size', then add it to
 *	ITEM. Note the entry is added first and thus will be found first.
 *
 *	Both of these routines are almost always used indirectly by other setup
 *	code. (See SubHandler.c mainly)
 */

SUB *AllocSub(ITEM *item, short key, short size)
{
	SUB *a=(SUB *)malloc(size);
#ifdef CHECK_ITEM
	CheckItem(item);
#endif
	if(a==NULL)
		Error("Out Of Memory");
	a->pr_Next=item->it_Properties;		/* Link into list */
	item->it_Properties=a;
	a->pr_Key=key;
	return(a);
}

/*
 *	Free a substructure from an item, pretty much like freeing text
 */

void FreeSub(ITEM *item, register SUB *sub)
{
	register SUB *a=item->it_Properties;
#ifdef CHECK_ITEM
	CheckItem(item);
#endif
	if(a==NULL)
		Error("FreeSub: Item has no properties");
	if(a==sub)
	{
		item->it_Properties=sub->pr_Next;
		free((char *)sub);
		return;
	}
	while(a->pr_Next)
	{
		if(a->pr_Next==sub)
		{
			a->pr_Next=sub->pr_Next;
			free((char *)sub);
			return;
		}
		a=a->pr_Next;
	}
	Error("FreeProp: Property not in item given");
}

/*
 *	Test if item a contains item b to any depth (non recursive)
 */

int Contains(register ITEM *a, register ITEM *b)
/* true if a cont b */
{
	int ct=32;
#ifdef CHECK_ITEM
	CheckItem(a);
	CheckItem(b);
#endif
	while(O_PARENT(b)&& ct-- )
	{
		if(O_PARENT(b)==a)
			return(1);
		b=O_PARENT(b);		/* Go up a level */
	}
	return(0);
}

/*
 *	Find the first item contained in i matching words
 */

ITEM *FindContains(short pe, ITEM *i, short a, short n)
{
	register ITEM *b=FindMaster(pe,a,n);
#ifdef CHECK_ITEM
	CheckItem(i);
#endif
	while(b)
	{
		if(Contains(i,b))
			return(b);
		b=NextMaster(pe,b,a,n);
	}
	return(NULL);
}

/*
 *	Find the next item contained in i, after j
 */

ITEM *NextContains(short pe, ITEM *i, ITEM *j, short a, short n)
{
	register ITEM *b=NextMaster(pe,j,a,n);
#ifdef CHECK_ITEM
	CheckItem(i);
	CheckItem(j);
#endif
	while(b)
	{
		if(Contains(i,b))
			return(b);
		b=NextMaster(pe,b,a,n);
	}
	return(NULL);
}	

/*
 *	Return the position of an item in the master lists
 */

long MasterNumber(ITEM *x)
{
	register ITEM *a=ItemList;
	register int b=0;
	while(a)
	{
		if(a==x)
			return(b);
		b++;
		a=a->it_MasterNext;
	}
	Error("Invalid Item Handle");
}

/*
 *	Test if an item pointer is valid
 */

int ValidItem(ITEM *x)
{
	register ITEM *a=ItemList;
	while(a)
	{
		if(a==x)
			return(1);
		a=a->it_MasterNext;
	}
	return(0);
}

/*
 *	Count the number of items in the master lists.
 */

long CountItems(void)
{
	register ITEM *a=ItemList;
	register long b=0;
	while(a)
	{
		b++;
		a=a->it_MasterNext;
	}
	return(b);
}

static ITEM *Lfnd_NextPtr=NULL;		/* We have to cache one here */

/*
 *	Remember the next pointer, used by tables for context changing
 */

ITEM *GetNextPointer(void)
{
	return(Lfnd_NextPtr);
}

/*
 *	Restore the pointer
 */

void SetNextPointer(ITEM *x)
{
	Lfnd_NextPtr=x;
}

/*
 *	Find an item in an item, by classmask not by words
 */

ITEM *FindInByClass(short per, register ITEM *i, register short m)
{
	i=O_CHILDREN(i);
	while(i)
	{
		if((i->it_Class&m)&&(CanSee(per,i)))
		{
			Lfnd_NextPtr=O_NEXT(i);
			return(i);
		}
		if((m==0)&&(CanSee(per,i)))
		{
			Lfnd_NextPtr=O_NEXT(i);
			return(i);
		}
		i=O_NEXT(i);
	}
	return(NULL);
}

/*
 *	Find the next item in a container by classmask
 */

ITEM *NextInByClass(short per, register ITEM *i, register short m)
{
/*
 *	We don't use item->next, since user may have moved item. We DO NOT define
 *	what happens if the user moves item->next itself. (There must be a better
 *	way to do this, but I can't think of one right now...)
 */
	i=Lfnd_NextPtr;
	while(i)
	{
		if((i->it_Class&m)&&(CanSee(per,i)))
		{
			Lfnd_NextPtr=O_NEXT(i);
			return(i);
		}
		if((m==0)&&(CanSee(per,i)))
		{
			Lfnd_NextPtr=O_NEXT(i);
			return(i);
		}
		i=O_NEXT(i);
	}
	return(NULL);
}

static char *Valt(ITEM *x)
{
	if(ValidItem(x))
		return("");
	else
		return("<**INVALID**>");
}

void LineDump(void)
{
	extern ITEM *Item1,*Item2;
	static short called=0;	/* If we fail we skip second time */
	if(called==1)
		return;
	called=1;
	fprintf(stderr,"Verb %d  Adj1 %d  Noun1 %d  Prep %d  Adj2 %d  Noun2 %d\
\n",Verb,Adj1,Noun1,Prep,Adj2,Noun2);
	fprintf(stderr,"$ME is %lx %s\n",(unsigned long)Me(),Valt(Me()));
	fprintf(stderr,"$1  is %lx %s\n",(unsigned long)Item1,Valt(Item1));
	fprintf(stderr,"$2  is %lx %s\n",(unsigned long)Item1,Valt(Item2));
	fprintf(stderr,"$AC is %lx %s\n",(unsigned long)Actor(),Valt(Actor()));
	if(CurrentLine)
	{
		FPCurrentLine();
	}
}

#ifdef CHECK_ITEM

/*
 *	Check an item pointer to trap system errors
 */

int ICheck(register ITEM *item, int line, char *file)
{
	register ITEM *x=ItemList;
	extern ITEM DummyItem;
	while(x)
	{
		if(x==item)
			return(1);
		x=x->it_MasterNext;
	}
	if(item==&DummyItem)
		return(1);
	fprintf(stderr,"Pointer=%ld!\n", (unsigned long)item);
	LineDump();
	ErrFunc("Invalid ITEM *","<VALIDATOR>","1.00",line,file);
}

#else
int ICheck(register ITEM *item, int line, char *file){return(1);}
#endif

#ifdef CHECK_TXT

/*
 *	Check a text pointer to trap system errors
 */

int TCheck(register TPTR txt, int line, char *file)
{
	return(1);
}

#endif