/*
 * Name list operations
 */

#include	<stdio.h>

#include	"etm.h"
#include	"memmgr.h"

#include	"troffcvt.h"

static void	RetargetAliases (Object *opNew, Object *opOld);
static void	ReleaseName (Name *np, short idx);

/*
 * Pointers to lists of request, macro/string, and register names.
 * Lookups are done so often on names that it's worthwhile to bust
 * the names up into separate little lists, which speeds things up
 * by a fair amount.
 */

#define	maxNameList	64

static Name	*nameList[maxNameList];

#define NameListIdx(namehash) ((namehash<0?-namehash:namehash)%maxNameList)

/*
 * Pointers to list of * special characters.
 */

static SpChar	*spCharList = (SpChar *) NULL;

static long lookups = 0;
static long tests = 0;
static long compares = 0;
static long fails = 0;

void
DumpNameInfo (void)
{
int	i, j;
Name	*np;

/*
	for (i = 0; i < maxNameList; i++)
	{
		j = 0;
		np = nameList[i];
		while (np)
		{
			j++;
			np = np->nNext;
		}
		printf ("%d	%d\n", i, j);
	}
*/
	printf ("lookups %ld\ttests %ld\tcompares %ld\tfails %ld\n",
			lookups, tests, compares, fails);
}


/*
 * Compute hash value of an XChar string
 */

static long
Hash (XChar *s)
{
long	c;
long	val = 0;

	while ((c = *s++) != '\0')
		val += c;
	return (val);
}


/*
 * Create a new name structure and add it to the list of known names.
 * Removes any currently existing instance of the name.  If an object
 * is passed in, point the name to it and increment the object's
 * reference count.  If no object is passed in, allocate a new one,
 * point the name at it, set the type, and set the reference count to 1.
 * When no object is passed in, the caller must initialize the type-specific
 * fields on the allocated object when NewName() returns.
 *
 * The major problems here are:
 * - If the name already exists, it is being redefined and the name should
 *   be detached from the existing underlying object.
 * - In addition, if other names also point to the existing object, they
 *   need to be retargeted to the new object.  (These will be aliases, and
 *   redefining any of the aliased names redefines them all.)
 * - The old object can't just be deleted, because it may be a macro that's
 *   currently executing.  In that case, wait until the macro terminates
 *   to remove it.
 */

Name *
NewName (XChar *name, Object *op, short type)
{
Name	*np, *npOld;
Object	*opOld;
short	typeMask;
short	aliasCount;
short	nlIdx = NameListIdx (Hash(name));

	if (type == regDef)
		typeMask = regDef;
	else
		typeMask = reqDef|macDef;

	npOld = LookupName (name, typeMask);
	if (npOld == (Name *) NULL)	/* no object with name exists */
		aliasCount = 0;
	else				/* object with name already exists */
	{
		/*
		 * Determine how many other aliases there are to this object,
		 * other than the given name.  Then release the object
		 * (decrement the reference count, deallocate if the count
		 * goes to zero and release the name structure (deallocate it).
		 */
		opOld = npOld->nObject;
		aliasCount = opOld->oRefCount - 1;
		ReleaseObject (opOld);
		ReleaseName (npOld, nlIdx);
	}

	np = New (Name);		/* allocate new name struct */
	np->nName = XStrAlloc (name);	/* fill in name fields */
	np->nHash = Hash (name);

	np->nNext = nameList[nlIdx];	/* attach name to name list */
	nameList[nlIdx] = np;

	/*
	 * If no object was passed in, create a new one, and set the type and
	 * the reference count.  (If an object was passed in, we're creating
	 * an alias; just increment the count.)  In all cases, point the name
	 * to the object.
	 */
	if (op == (Object *) NULL)
	{
		op = New (Object);
		op->oType = type;
		op->oRefCount = 0;
	}
	GrabObject (op);
	np->nObject = op;

	/*
	 * Retarget any aliases that used to point to the old object.
	 * This will transfer names from old underlying object to new, and
	 * remove old object.  (It's possible for this to be called for
	 * a macro with a single name if it's currently executing, since
	 * invocations count in the reference count; in that case the call
	 * won't retarget any other names, but it won't do any harm, either.
	 */
	if (aliasCount > 0)
		RetargetAliases (np->nObject, opOld);
	return (np);
}


/*
 * Look through the namelist for any names that currently point to opOld.
 * Make them point to opNew instead.  This causes the reference count on
 * opOld to go to zero, which results in its being deleted.
 */

static void
RetargetAliases (Object *opNew, Object *opOld)
{
Name	*np;
int	i;

	for (i = 0; i < maxNameList; i++)
	{
	    for (np = nameList[i]; np != (Name *) NULL; np = np->nNext)
	    {
		if (np->nObject == opOld)
		{
			np->nObject = opNew;	/* retarget name to new obj */
			GrabObject (opNew);
			/*
			 * Decrement the refcount; deallocate if the count
			 * goes to zero.
			 */
			ReleaseObject (opOld);
		}
	    }
	}
}


/*
 * Rename a name definition (request, macro, or string only), replacing both
 * the name and the hash value.  This is implemented by creating the new name
 * as an alias to the existing name, then removing the existing name.  This
 * leaves the underlying object alone.
 *
 * np = named object to be renamed
 * name = new name to assign to np
 *
 * If the old name and the new name are the same, do nothing.  If something
 * already exists with the given name as its name, remove it first.
 */

void
RenameName (Name *np, XChar *name)
{
	if (XStrCmp (np->nName, name) != 0)
	{
		/*
		 * Create new name as alias (removes anything it points to now)
		 */
		NewName (name, np->nObject, np->nObject->oType);
		/*
		 * Release the old name
		 */
		ReleaseName (np, NameListIdx (Hash (np->nName)));
	}
}


/*
 * Allocate a new request.  The action list isn't initialized because
 * the actions might not be known yet.
 */

Request *
NewRequest (XChar *name)
{
Name	*np;
Request	*rp;

	np = NewName (name, (Object *) NULL, reqDef);
	rp = np->nRequest = New (Request);
	rp->reqPActions = (Action *) NULL;
	rp->reqPPActions = (Action *) NULL;
	return (rp);
}


/*
 * Allocate a new macro.  Allocate an initial block of space for the body
 * but set the body length to zero because nothing's been added to the
 * body yet.
 */

Macro *
NewMacro (XChar *name)
{
Name	*np;
Macro	*mp;

	np = NewName (name, (Object *) NULL, macDef);
	mp = np->nMacro = New (Macro);
	mp->macBuf = (XChar *) VAlloc (macroAllocSize, sizeof (XChar));
	mp->macMaxSiz = macroAllocSize;
	mp->macSiz = 0;
	return (mp);
}


/*
 * Allocate a new register and initialize its values.  The hard part
 * is determining the format type.  The default is numeric, with width
 * zero (which means "as wide as necessary").  Width is only consulted
 * for numeric format.  If the format isn't recognized, numeric is used.
 */

Register *
NewRegister (XChar *name, Param initial, Param increment, XChar *format,
		short readonly)
{
Name	*np;
Register	*rp;

	np = NewName (name, (Object *) NULL, regDef);
	rp = np->nRegister = New (Register);
	rp->regValue = initial;
	rp->regIncrement = increment;
	rp->regReadOnly = readonly;
	SetRegisterFormat (rp, format);
	return (rp);
}


/*
 * Version of NewRegister() that can be used when you KNOW the register
 * name and format types are straight ASCII.
 */

Register *
StrNewRegister (char *name, Param initial, Param increment, char *format,
		short readonly)
{
XChar	nbuf[bufSiz];
XChar	fbuf[bufSiz];

	(void) XStrCpy (nbuf, StrToXStr (name));
	(void) XStrCpy (fbuf, StrToXStr (format));
	return (NewRegister (nbuf, initial, increment, fbuf, readonly));
}


/*
 * Look up a name that has one of the given types.  "typeMask" is a bitmask
 * of all the types that are acceptable.
 */

Name *
LookupName (XChar *name, short typeMask)
{
Name	*np;
long	hash = Hash (name);
short	nlIdx = NameListIdx (hash);

lookups++;
	for (np = nameList[nlIdx]; np != (Name *) NULL; np = np->nNext)
	{
tests++;
/*
		if ((np->nObject->oType & typeMask)
				&& np->nHash == hash
				&& XStrCmp (np->nName, name) == 0)
			return (np);
*/
		if ((np->nObject->oType & typeMask)
				&& np->nHash == hash)
		{
compares++;
			if (XStrCmp (np->nName, name) == 0)
				return (np);
		}
	}
fails++;
	return ((Name *) NULL);
}


Request *
LookupRequest (XChar *name)
{
Name	*np;

	if ((np = LookupName (name, reqDef)) == (Name *) NULL)
		return ((Request *) NULL);
	return (np->nRequest);
}


Macro *
LookupMacro (XChar *name)
{
Name	*np;

	if ((np = LookupName (name, macDef)) == (Name *) NULL)
		return ((Macro *) NULL);
	return (np->nMacro);
}


Register *
LookupRegister (XChar *name)
{
Name	*np;

	if ((np = LookupName (name, regDef)) == (Name *) NULL)
		return ((Register *) NULL);
	return (np->nRegister);
}


/*
 * Remove a name from the name list and the underlying object to which
 * the name points.  numReg is true if the name to be removed refers
 * to a number register, false if it is a request, macro or string.
 * Request, register, macro, and string names are all maintained in
 * a single list, but registers are considered to lie in a different
 * namespace than the others.  (Thus, there can be two entries on the
 * list with the same name but different types.)
 */

void
RemoveNamedObject (XChar *name, short typeMask)
{
Name	*np;
Macro	*mp;
long	hash = Hash (name);
short	nlIdx = NameListIdx (hash);

	for (np = nameList[nlIdx]; np != (Name *) NULL; np = np->nNext)
	{
		if ((np->nObject->oType & typeMask)
				&& np->nHash == hash
				&& XStrCmp (np->nName, name) == 0)
			break;
	}
	if (np != (Name *) NULL)
	{
		ReleaseObject (np->nObject);
		ReleaseName (np, nlIdx);
	}
}


/*
 * Put a "grab" on an object by incrementing its use count to indicate
 * the object is in use.  This is called when a name is made to point
 * to the object or when the object becomes an input source (e.g., when
 * a request or macro is executed).
 */

void
GrabObject (Object *op)
{
	++op->oRefCount;
}


/*
 * Release a use of an object by decrementing its reference count.
 * If the reference count goes to zero, deallocate the storage devoted
 * to the object.
 *
 * For macro objects, the reference count includes not only the names
 * that point to the object but also any current invocations of the macro.
 * This means a macro will never be deallocated while it's executing (this
 * is A Good Thing).
 */

void
ReleaseObject (Object *op)
{
	if (--op->oRefCount > 0)
		return;
	if (op->oRefCount < 0)
		ETMPanic ("ReleaseObject: logic error 1");
	switch (op->oType)	/* deallocate name-type specific stuff */
	{
	case reqDef:
		FreeActions (op->oRequest->reqPActions);
		FreeActions (op->oRequest->reqPPActions);
		Free ((char *) op->oRequest);
		break;
	case macDef:
		Free ((char *) op->oMacro->macBuf);
		Free ((char *) op->oMacro);
		break;
	case regDef:
		Free ((char *) op->oRegister);
		break;
	default:
		ETMPanic ("ReleaseObject: logic error 2");
	}
	Free ((char *) op);
}


/*
 * Release a given name from the given name list
 */

static void
ReleaseName (Name *np, short idx)
{
Name	*np2;

	/*
	 * Remove np from name list and free it
	 */

	if (nameList[idx] == np)		/* first element in list */
		nameList[idx] = np->nNext;	/* skip to next */
	else				/* remove from middle of list */
	{
		np2 = nameList[idx];
		while (np2->nNext != (Name *) NULL)
		{
			if (np2->nNext == np)
			{
				np2->nNext = np->nNext;
				break;
			}
			np2 = np2->nNext;
		}
	}
	XStrFree (np->nName);
	Free ((char *) np);
}


/*
 * Define a special character.  If the character is already defined,
 * just replace the value.  Otherwise add it to the list.
 */

SpChar *
NewSpChar (XChar *name, XChar *value)
{
static XChar	code = spCharBase;	/* special chars start at spCharBase */
SpChar	*sp;
char	*p;

	if (!AsciiStr (name))
		ETMPanic ("special character name (%s) is not plain text",
							XStrToStr (name));
	p = StrAlloc (XStrToStr (value));
	if (!AsciiStr (value))
		ETMPanic ("special character value (%s) is not plain text", p);
	if ((sp = LookupSpCharByName (name)) != (SpChar *) NULL)
	{
		Free (sp->spValue);	/* replace value in existing struct */
		sp->spValue = p;
	}
	else				/* create new struct */
	{
		sp = New (SpChar);
		sp->spName = XStrAlloc (name);
		sp->spHash = Hash (name);
		sp->spCode = code++;
		sp->spValue = p;
		sp->spNext = spCharList;
		spCharList = sp;
	}
	return (sp);
}


/*
 * Look up a special character definition by its name
 */

SpChar *
LookupSpCharByName (XChar *name)
{
SpChar	*sp;
long	hash;

	hash = Hash (name);
	for (sp = spCharList; sp != (SpChar *) NULL; sp = sp->spNext)
	{
		if (hash == sp->spHash && XStrCmp (name, sp->spName) == 0)
			break;
	}
	return (sp);	/* NULL if not found */
}


/*
 * Look up a special character definition by code
 */

SpChar *
LookupSpCharByCode (XChar code)
{
SpChar	*sp;

	for (sp = spCharList; sp != (SpChar *) NULL; sp = sp->spNext)
	{
		if (sp->spCode == code)
			break;
	}
	return (sp);	/* NULL if not found */
}
