/*
 *		BUFFER.C											vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *		This module maintains the review buffer used by SnoopDos to record
 *		the past events that have occurred.
 */		

#define TESTING		0

#include "system.h"
#include "snoopdos.h"
#include "patches.h"

#define INVALID_HUNK		((USHORT)(-1))

#if 0
#define DB(s)	KPrintF(s)
#else
#define DB(s)
#endif

static UBYTE	   *BufferStart;		/* Ptr to start of our buffer		*/
static UBYTE	   *BufferEnd;			/* Ptr to end of our buffer			*/
static UBYTE	   *BufferCur;			/* Ptr to next free loc in buffer	*/
static ULONG		BufferSize;			/* Size of current buffer			*/

/*
 *		This is the mapping that assigns format ID's to the various
 *		fields we can output.
 */
FieldInit FieldTypes[] = {
	EF_ACTION,		'a',	10,		MSG_ACTION_COL,
	EF_CALLADDR,	'c',	8,		MSG_CALL_COL,
	EF_DATE,		'd',	9,		MSG_DATE_COL,
	EF_HUNKOFFSET,	'h',	11,		MSG_HUNK_COL,
	EF_PROCID,		'i',	8,		MSG_PROCESSID_COL,
	EF_FILENAME,	'n',	27,		MSG_FILENAME_COL,
	EF_OPTIONS,		'o',	7,		MSG_OPTIONS_COL,
	EF_PROCNAME,	'p',	18,		MSG_PROCNAME_COL,
	EF_RESULT,		'r',	4,		MSG_RESULT_COL,
	EF_SEGNAME,		's',	20,		MSG_SEGNAME_COL,
	EF_TIME,		't',	8,		MSG_TIME_COL,
	EF_COUNT,		'u',	5,		MSG_COUNT_COL,
	EF_END,			0,		0,		0
};

/*
 *		InitBuffers()
 *
 *		Initialises certain variables associated with the buffer routines.
 *		Must be called before any of the buffer functions are called.
 */
void InitBuffers(void)
{
	InitSemaphore(&BufSem);
	NewList(&EventList);
}

/*
 *		SetTotalBufferSize(size, alloctype)
 *
 *		This function is used to change the size of the buffer. If no buffer
 *		has yet been created, one is allocated. If there is not enough room
 *		to create a new buffer without freeing the existing buffer first,
 *		then FALSE is returned, _unless_ the alloctype parameter is
 *		SETBUF_FORCENEW, in which case it simply frees the existing buffer
 *		first, losing any existing history info.
 *
 *		In general, the old buffer's contents are copied across to the
 *		new buffer so that the user's history isn't destroyed.
 *
 *		The intention is that the caller tries to use this call with
 *		the tryalways flag set to SETBUF_KEEPOLD first; if that fails,
 *		then it can display a requester to the user saying that this will
 *		cause the existing buffer info to be lost permanently if you go
 *		ahead--Yes/No?  and then do a retry with alloctype set to
 *		SETBUF_FORCENEW if the user so chooses.
 *
 *		In any case, when allocating a new buffer, the routine will always
 *		allocate a buffer if possible, even if it can't allocate the full
 *		size requested.
 *
 *		Returns FALSE if there wasn't enough room to allocate the new buffer
 *		(the minimum buffer size is something like 4K.)
 *
 *		Note that if the size of the buffer is being reduced, the caller
 *		is responsible for updating the display to reflect the new status.
 *
 *		Note also that the whole idea of only freeing the buffer if there's
 *		no other option is great in principle; in practise, it turned out to
 *		be too much work to support copying buffers for such an infrequent
 *		operation, so we don't do it after all.
 */
int SetTotalBufferSize(ULONG newsize, int alloctype)
{
	UBYTE *oldbuf = BufferStart;
	ULONG oldsize = BufferSize;
	UBYTE *newbuf;

	ObtainSemaphore(&BufSem);
	ClearBuffer();		/* Clear the existing buffer first */

	/*
	 *		First, work out if we have enough memory to satisfy the request
	 *		without freeing our old buffer first. If we don't, and if
	 *		alloctype is SETBUF_KEEPOLD, then we fail immediately.
	 */
	if (newsize < MIN_BUF_SIZE)
		newsize = MIN_BUF_SIZE;

	newbuf = AllocMem(newsize, MEMF_ANY);
	if (!newbuf) {
		/*
		 *		No memory available -- we _may_ need to bomb out at this
		 *		point. If we didn't have a buffer already, then we
		 *		always go ahead and try and allocate some, regardless
		 *		of alloctype.
		 */
		if (oldbuf && alloctype == SETBUF_KEEPOLD) {
			ReleaseSemaphore(&BufSem);
			return (FALSE);
		}

		/*
		 *		Okay, try and allocate the memory even though it means
		 *		freeing up the old buffer first.
		 */
		if (oldbuf) {
			FreeMem(oldbuf, oldsize);
			oldbuf = NULL;
		}
		newbuf = AllocMem(newsize, MEMF_ANY);
		if (!newbuf) {
			/*
			 *		Couldn't allocate memory on second attempt. Finally,
			 *		we just grab the largest chunk we can and use that
			 *		instead.
			 */
			ULONG freesize;

			Forbid();
			freesize = AvailMem(MEMF_ANY | MEMF_LARGEST);
			if (freesize < MIN_BUF_SIZE) {
				Permit();
				ReleaseSemaphore(&BufSem);
				return (FALSE);
			}
			if (freesize < newsize)	/* freesize > newsize _is_ possible! */
				newsize = freesize;
			newbuf  = AllocMem(newsize, MEMF_ANY);
			Permit();
			if (!newbuf) {
				/*
				 *		Should never happen, but better safe than sorry
				 */
				ReleaseSemaphore(&BufSem);
				return (FALSE);
			}
		}
	}
	/*
	 *		Okay, we finally have some memory so initialise the global
	 *		buffer variables for use by everyone else.
	 */
	BufferStart = newbuf;
	BufferSize  = newsize;
	BufferEnd   = newbuf + newsize;
	BufferCur   = BufferStart;

	if (oldbuf != NULL) {
		// CopyEvents(newbuf, newsize, oldbuf, oldsize);
		FreeMem(oldbuf, oldsize);
	}
	NewList(&EventList);		/* Probably need to delete this eventually */
	ReleaseSemaphore(&BufSem);
	return (TRUE);
}

// void CopyEvents(UBYTE *newbuf, ULONG newsize,
//				UBYTE *oldbuf, ULONG oldsize) {}

/*
 *		ClearBuffer()
 *
 *		Clears the current buffer by a very simple process -- it simply
 *		erases the current list of linked items, and resets RealFirstSeq etc.
 *		to be the next available event number. This means that any events
 *		that were partially in progress will spot, on their completition,
 *		that their associated event number is now < RealFirstSeq, and so
 *		won't try and write into the now-cleared buffer, thus avoiding
 *		corruption.
 *
 *		BaseSeq is used to allow the new buffer count to commence from
 *		1 again, rather than keeping the old number (which might be
 *		into the hundreds by this stage!)
 *
 *		Note: we are a little bit naughty in some of the patches in that
 *		after allocating a brand new event, we sometimes write into it
 *		immediately _without_ semaphore protection (to fill in the
 *		initial details etc.) This is not terribly wise, but since there
 *		is never anything to deliberately cause a task switch, it's
 *		reasonably safe. Adding semaphore protection to all these cases
 *		would complicate things quite a bit.
 */
void ClearBuffer(void)
{
	ObtainSemaphore(&BufSem);
	Delay(5);	/* Just to be safe, let everyone else run for 0.1 seconds */

	NewList(&EventList);
	BufferCur = BufferStart;

	BaseSeq			=
	FirstSeq		=
	RealFirstSeq	=
	TopSeq			=
	LastDrawnTopSeq	=
	BottomSeq		=
	MaxScannedSeq	=
	EndSeq			=
	EndCompleteSeq	= NextSeq;

	ReleaseSemaphore(&BufSem);
}

/*
 *		CleanupBuffers()
 *
 *		Frees up the buffer on exit, along with any other pertinent
 *		resources that were allocated by this module.
 */
void CleanupBuffers(void)
{
	if (BufferStart) {
		ObtainSemaphore(&BufSem);
		FreeMem(BufferStart, BufferSize);
		if (BufferSize == 0)
			ShowError("Warning! BufferSize is zero for some reason!");
		BufferStart = NULL;
		BufferSize  = 0;
		NewList(&EventList);
		ReleaseSemaphore(&BufSem);
	}
}

/*
 *		GetNewEvent(stringsize)
 *
 *		Allocates a new event structure from the list from the buffer
 *		and returns a pointer to it. If necessary, one of the existing
 *		buffers will be flushed to make room for the new event.
 *
 *		The stringsize is the maximum amount of extra space to allocate
 *		after the end of the event to accomodate string storage. A pointer
 *		to this data area can be obtained by accessing the first location
 *		after the event itself. For example, GetNewEvent(20) would return
 *		an event with 20 additional data bytes after it. Note that this
 *		_includes_ any terminating zeros that may be present in the
 *		strings.
 */
Event *GetNewEvent(int stringsize)
{
	UBYTE *startev;
	ULONG totalsize;
	Event *ev;

	/* Calculate total size and ensure it's a longword multiple */
	totalsize = (sizeof(Event) + stringsize + 3) & ~3;

	if (BufferStart == NULL || totalsize > (MIN_BUF_SIZE/2))
		return (NULL);	/* Should never happen but doesn't hurt to check */

	DB(" [GNE-Go] ");
	ObtainSemaphore(&BufSem);
#if 0
	ev = (Event *)BufferStart;
	ReleaseSemaphore(&BufSem);
	return (ev);
#endif

	startev = HeadNode(&EventList);
	if (IsListEmpty(&EventList)) {
		/*
		 *		First time being called, so simply begin at the
		 *		start of the buffer.
		 */
		BufferCur = BufferStart;
	} else {
		/*
		 *		There are already some nodes. Try and allocate a new node,
		 *		flushing nodes from the start of the list as necessary.
		 */
		for (;;) {
			if (startev < BufferCur) {
				/*
				 *		From BufferCur to the end of the buffer is free space,
				 *		so we can check for a quick fit. If we get one, then
				 *		we go for it, otherwise we move BufferCur back to the
				 *		start of the buffer and start flushing events from
				 *		there.
				 */
				if (totalsize <= (BufferEnd - BufferCur))
					break;	/* Got it */
				BufferCur = BufferStart;	/* Else wrap to start of list */
			}
			/*
			 *		Now delete events from here to the end of the buffer until
			 *		we finally have enough room in the buffer. If we run off
			 *		the end of the buffer, let the main loop go around again
			 *		to reset everything.
			 */
			while (startev >= BufferCur && (startev - BufferCur) < totalsize) {
				RemHead(&EventList);
				if (IsListEmpty(&EventList)) {
					KPrintF("Uhoh --- list is empty and shouldn't be!\n");
					Delay(50);
					startev = BufferCur = BufferStart;
					break;
				}
				startev = HeadNode(&EventList);
			}
			if (startev > BufferCur)
				break;	/* Got it */
		}
	}
	ev		   = (Event *)BufferCur;
	BufferCur += totalsize;

	/*
	 *		Allocate our new event from the current buffer block (which
	 *		we are now assured will have enough space in it)
	 */
	ev->seqnum	= ++NextSeq;			/* Give it a unique sequence number */
	ev->status	= ES_CREATING;			/* Mark event as not-to-be-touched	*/
	ev->flags   = EFLG_NONE;			/* No date/segment lookup as yet	*/
	AddTail(&EventList, (Node *)ev);
	/*
	 *		Update current first seq num so patch code knows when an
	 *		event currently being created has become invalid
	 */
	RealFirstSeq = ((Event *)HeadNode(&EventList))->seqnum;
	DateStamp(&ev->datestamp);			/* Store current date				*/
	DB(" [GNEEnd] ");
	ReleaseSemaphore(&BufSem);
	return (ev);
}

/*
 *		ParseFormatString(formatstr, evformat, maxfields)
 *
 *		Parses a text format string containing a list of format identifiers
 *		(%d, %s, etc.) with optional field widths (%20s, %10d) into an
 *		internal list of formatting codes that can be quickly processed
 *		when displaying output.
 *
 *		formatstr points to the format string. evformat points to the
 *		EventFormat[] array to hold the output.
 *
 *		maxfields is the maximum number of format fields that will
 *		be recognised -- any more than this will be ignored. evformat[]
 *		must be able to hold at least maxfields elements. If a field is
 *		duplicated, only the first occurrance is recognised.
 *
 *		Returns the length any output produced using this format array will
 *		have (including separating spaces)
 */
int ParseFormatString(char *formatstr, EventFormat *evformat, int maxfields)
{
	int evindex = 0;
	int totlen  = 0;
	char *p     = formatstr;

	while (*p) {
		int width = 0;
		int i;
		char type;
		char ch;

		if (*p++ != '%')
			continue;

		/*
		 *		Got a format specifier, so now parse it.
		 *
		 *		We ignore any negative prefix -- in the real printf,
		 *		it means left-align this field, and all our fields are
		 *		left-aligned by default anyway.
		 */
		if (*p == '-')
			p++;
		while (*p >= '0' && *p <= '9') {
			width = (width * 10) + *p - '0';
			p++;
		}
		if (!*p)
			break;

		ch = *p++;
		if (ch >= 'A' && ch <= 'Z')
			ch |= 0x20;

		for (i = 0; FieldTypes[i].type != EF_END; i++) {
			if (FieldTypes[i].idchar == ch)
				break;
		}
		type = FieldTypes[i].type;
		if (type != EF_END) {
			/*
			 *		Matched a field. Now try and insert it. But first,
			 *		make sure we don't have a duplicate.
			 */
			int j;

			for (j = 0; j < evindex; j++)
				if (evformat[j].type == type)	/* Got a duplicate */
					break;

			if (j < evindex)		/* Skip over duplicates */
				continue;	

			if (width == 0)
				width = FieldTypes[i].defwidth;

			if (width < 1)				width = 1;
			if (width > MAX_FIELD_LEN)	width = MAX_FIELD_LEN;

			evformat[evindex].type		 = type;
			evformat[evindex].width		 = width;
			evformat[evindex].titlemsgid = FieldTypes[i].titlemsgid;
			totlen += width + 1;
			evindex++;
			if (evindex >= (maxfields-1))
				break;
		}
	}
	evformat[evindex].type = EF_END;
	if (totlen)				/* Compensate for the extra 'space' we counted */
		totlen--;

	return (totlen);
}

/*
 *		BuildFormatString(evformat, formatstr, maxlen)
 *
 *		Converts an existing event format array into an ascii string
 *		corresponding to the format that can be parsed by ParseFormatString.
 *		The string is guaranteed to be at most maxlen chars in length
 *		(including terminating \0);
 */
void BuildFormatString(EventFormat *evformat, char *formatstr, int maxlen)
{
	char *p	  = formatstr;
	char *end = formatstr + maxlen - 6;	/* Max length of format element is 5 */

	while (evformat->type != EF_END) {
		int j;

		if (p >= end)	/* Never exceed string length */
			break;
		/*
		 *		Find the event type in our mapping array so that we can
		 *		determine the default width. This lets us choose not
		 *		to include the width in the ascii field, for neatness.
		 */
		for (j = 0; FieldTypes[j].type != evformat->type; j++)
			;
		*p++ = '%';
		if (FieldTypes[j].defwidth != evformat->width) {
			mysprintf(p, "%ld", evformat->width);
			p += strlen(p);
		}
		*p++ = FieldTypes[j].idchar;
		*p++ = ' ';	/* Space out entries for neatness */
		evformat++;
	}
	p[-1] = '\0';	/* Replace final space with a terminator instead. */
}

/*
 *		ltoa(buffer, val, numdigits)
 *		ltoap(buffer, val, numdigits)
 *
 *		Converts the given value to an ascii hex string of up to numdigits
 *		(with leading zeros). No zero termination is performed. The output
 *		is stored in the first numdigits characters of buffer.
 *
 *		ltoap is similar, except that it pads leading zeros with spaces.
 *
 *		Returns a pointer to the buffer.
 */
char *ltoa(char *buffer, unsigned long val, int numdigits)
{
	static char HexDigits[] = "0123456789ABCDEF";
	char *p = buffer + numdigits - 1;

	while (p >= buffer) {
		*p-- = HexDigits[val & 0x0F];
		val >>= 4;
	}
	return (buffer);
}

char *ltoap(char *buffer, unsigned long val, int numdigits)
{
	static char HexDigits[] = "0123456789ABCDEF";
	char *p = buffer + numdigits - 1;

	do {
		*p-- = HexDigits[val & 0x0F];
		val >>= 4;
	} while (val && p >= buffer);

	while (p >= buffer)
		*p-- = ' ';

	return (buffer);
}

/*
 *		SetEventDateStr(event)
 *
 *		This function converts the datestamp in the passed event into a date
 *		and time string, also stored in the event.
 */
void SetEventDateStr(Event *ev)
{
	struct DateTime datetime;

	datetime.dat_Stamp		= ev->datestamp;
	datetime.dat_Format		= FORMAT_DOS;
	datetime.dat_Flags		= 0;
	datetime.dat_StrDay		= NULL;
	datetime.dat_StrDate	= ev->date;
	datetime.dat_StrTime	= ev->time;
	DateToStr(&datetime);
}

/*
 *		CheckSegTracker()
 *
 *		Checks to see if SegTracker is loaded and sets the SegTracker
 *		global variable accordingly. Should be called every now and
 *		again to keep us up to date (e.g. whenever a new window is
 *		opened).
 */
void CheckSegTracker(void)
{
	SegTrackerActive = (FindSemaphore("SegTracker") != NULL);
}

/*
 *		SetEventSegmentStr(event)
 *
 *		This function converts the calladdr in the passed event into a segment
 *		name and hunk/offset pair if SegTracker is loaded, or into an invalid
 *		string if SegTracker is not loaded.
 *
 *		See the Enforcer/Segtracker documentation (from Michael Sinz) for
 *		more info on how this works.
 *
 *		Note that if the passed-in event->segname is NULL, then it will
 *		be reset to an "unknown module" string and no attempt to locate
 *		the module will be made. It is thus imperative that the _caller_
 *		only calls this function once for each event. The easiest way
 *		to ensure this is to use the EFLG_DONESEG flag in the ev->flags
 *		register to track whether the call has been made or not.
 *
 *		New: we now detect ROM addresses and search the resident module
 *			 list ourselves looking for the module that contains it.
 */
void SetEventSegmentStr(Event *ev)
{
	typedef char (*__asm SegTrack(reg_a0 ULONG,reg_a1 ULONG *,reg_a2 ULONG *));

	static struct {
		Semaphore	seg_Semaphore;
		SegTrack 	*seg_Find;
	} *segsem;

	ULONG hunk;
	ULONG offset;
	char *segname = NULL;

	ev->hunk = INVALID_HUNK;
	if (ev->segname == NULL) {
		ev->segname	= "Unknown module";
		return;
	}

	Forbid();
	if (!segsem)
		segsem = (void *)FindSemaphore("SegTracker");

	if (segsem)
		segname = segsem->seg_Find(ev->calladdr, &hunk, &offset);

	if (segname) {
		strncpy(ev->segname, segname, MAX_SEGTRACKER_LEN);
		ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
		ev->hunk   = hunk;
		ev->offset = offset;
	} else {
		if (ev->calladdr >= RomStart && ev->calladdr <= RomEnd) {
			/*
			 *		Let's search the resident list for it ourselves.
			 *		and see if we can't do any better.
			 */
			struct Resident *res;
			ULONG *pres = (ULONG *)SysBase->ResModules;
			UBYTE *p;

			while (*pres) {
				if (*pres & 0x80000000) {
					pres = (ULONG *)(*pres & 0x7FFFFFFF);
					continue;
				}
				res = (struct Resident *)*pres++;
				if (ev->calladdr >= (ULONG)res
								&& ev->calladdr <= (ULONG)res->rt_EndSkip) {
					ev->hunk    = 0;
					ev->offset  = ev->calladdr - (ULONG)res;
					strcpy(ev->segname, "ROM: ");
					strncat(ev->segname + 5, res->rt_IdString,
										     MAX_SEGTRACKER_LEN - 6);
					ev->segname[MAX_SEGTRACKER_LEN-1] = 0;
					/*
					 *		Now filter out any remaining carriage returns
					 *		and linefeeds, since they look ugly when printed
					 *		We also convert any open brackets into '\0's
					 *		since these are just dates which we don't care
					 *		about -- it looks neater to just display the
					 *		module name and version.
					 */
					for (p = ev->segname; *p; p++) {
						if (*p < ' ')
							*p = ' ';
						if (*p == '(')
							*p = '\0';
					}
					break;
				}
			}
		}
		if (ev->hunk == INVALID_HUNK)
			ev->segname = MSG(MSG_SEG_MODULE_NOT_FOUND);
	}
	Permit();
}

/*
 *		FormatEvent(eventformat, event, outputstr, start, end)
 *
 *		Formats the specified event according to the passed in format
 *		array. Only that portion of the fully formatted event which
 *		lies from the 'start' to 'end' characters is produced, and
 *		output always starts at the beginning of the outputstr.
 *
 *		For example, a start of 4 and end of 8 will produce a formatted
 *		string of exactly 5 characters.
 *
 *		Each entry in the eventformat array is decoded, and text which
 *		corresponds to the entry type is copied to the output string. If
 *		the text is longer than the entry width setting, it is truncated;
 *		if shorter, it is padded with spaces.
 *
 *		Each event is separated by one space character.
 *
 *		If the event pointer is NULL, then the title headings are returned
 *		instead of the event contents.
 */
void FormatEvent(EventFormat *eventformat, Event *event,
				 char *outstr, int start, int end)
{
	static char hexbuf8[]  = "00000000";
	static char hexbuf13[] = "0000:00000000";
	static char countbuf[] = "             ";

	EventFormat *ef = eventformat;
	int savechar = 0;
	char *savep;
	char *p;
	int col = 0;

	end++;		/* Make exclusive rather than inclusive */

	while (ef->type != EF_END && col < end) {
		int width = ef->width;

		/*
		 *		First skip over any entries that fall before desired section
		 */
		if ((col + width) < start) {
			col += width + 1;
			if (col > start)
				*outstr++ = ' ';	/* Add leading space */
			ef++;
			continue;
		}

		/*
		 *		Now parse our current entry
		 */
		if (event) {
			switch (ef->type) {

			case EF_PROCNAME: 	p = event->procname;					break;
			case EF_ACTION:		p = event->action;						break;
			case EF_OPTIONS:	p = event->options;						break;
			case EF_RESULT:		p = event->result;						break;
			case EF_CALLADDR:
				p = ltoap(hexbuf8, event->calladdr, 8);
				if (*p == ' ')
					p++;
				break;

			case EF_PROCID:
				p = ltoap(hexbuf8, event->processid,8);
				if (*p == ' ')
					p++;
				break;

  			case EF_FILENAME:
			{
				/*
				 *		This is a little trickier since we allow the user
				 *		to view it left-aligned or right-aligned. For
				 *		right-aligned, we temporarily replace the first char
				 *		with a « to indicate there are additional characters
				 *		to the left, then restore it after we've copied the
				 *		string data later on. This is much quicker (and
				 *		safer) than copying the entire string to a temporary
				 *		just so we can patch a single character of it.
				 */
				int len;

				p = event->filename;
				if (RightAligned) {
					len = strlen(p);
					if (len > width) {
						p += len - width;
						if (width > 1) {
							savep    = p;
							savechar = *p;
							*p       = '«';
						}
					}
				}
				break;
			}

			case EF_DATE:
				if ((event->flags & EFLG_DONEDATE) == 0) {
					SetEventDateStr(event);
					event->flags |= EFLG_DONEDATE;
				}
				p = event->date;
				break;

			case EF_TIME:
				if ((event->flags & EFLG_DONEDATE) == 0) {
					SetEventDateStr(event);
					event->flags |= EFLG_DONEDATE;
				}
				p = event->time;
				break;

			case EF_SEGNAME:
				if ((event->flags & EFLG_DONESEG) == 0) {
					SetEventSegmentStr(event);
					event->flags |= EFLG_DONESEG;
				}
				p = event->segname;
				break;

			case EF_COUNT:
				/*
				 *		We subtract BaseSeq from the event number when
				 *		displaying it so that we have a convenient way of
				 *		making the event number appear to return to 1,
				 *		while internally still ensuring that the event
				 *		number is ALWAYS monotonically increasing, even
				 *		across buffer clears and the like.
				 */
				mysprintf(countbuf, "%ld", event->seqnum - BaseSeq);
				p = countbuf;
				break;

			case EF_HUNKOFFSET:
				if ((event->flags & EFLG_DONESEG) == 0) {
					SetEventSegmentStr(event);
					event->flags |= EFLG_DONESEG;
				}
				if (event->hunk == INVALID_HUNK) {
					/*
					 *		If we couldn't locate the module address,
					 *		then don't bother printing the hunk/offset
					 *		info since it will only be zero anyway.
					 */
					p = "";	/* Couldn't find module so don't print hunk addr */
				} else {
					/*
					 *		Only output 6 digits of offset and 2 digits
					 *		of hunk, but arrange that we can handle a
					 *		full 8 / 4 combination if necessary
					 */
					p = ltoa(hexbuf13+7, event->offset, 6);
					*--p = ':';
					p = ltoap(p-2, event->hunk, 2);
				}
				break;

			default:
				p = "<Invalid>";
				break;
			}
		} else {
			/*
			 *		Format column headings instead
			 */
			p = MSG(ef->titlemsgid);
		}

		/*
		 *		Okay, so now we have p pointing to our string. We now need to
		 *		copy a sufficient number of characters into the string to
		 *		fulfill our need.
		 */
		if (col < start) {
			/*
			 *		Our first special case is where we have to skip over some of
			 *		the early characters, i.e. chars to left are clipped.
			 */
			while (col < start && *p) {
				col++;
				width--;
				p++;
			}
			if (col < start) {
				/*
				 *		Ran out of characters in our string, so pretend we
				 *		have a null string and let the following code pad
				 *		the remaining width with spaces.
				 */
				width -= (start - col);
				col    = start;
			}
		}

		/*
		 *		Now copy characters intact into string
		 */
		if ((col + width) > end) {
			/*
			 *		This field needs to be clipped. We do this quite simply
			 *		by adjusting the width to be the remaining number of
			 *		chars in the string.
			 */
			width = end - col;
		}

		/*
		 *		Now copy width chars from our string to the output buffer,
		 *		padding with spaces as necessary.
		 */
		while (width && *p) {
			*outstr++ = *p++;
			width--;
			col++;
		}
		if (width) {
			memset(outstr, ' ', width);
			outstr += width;
			col    += width;
		}
		*outstr++ = ' ';			/* Space in between output fields	*/
		col++;						/* onto next column					*/
		ef++;						/* Onto the next format field		*/
		/*
		 *		Finally, restore the character we replaced with « earlier
		 *		(if any)
		 */
		if (savechar) {
			*savep   = savechar;
			savechar = 0;
		}
	}

	/*
	 *		Okay, we've generated the string as requested. Now check to see if
	 *		we need to pad the remainder of the string with spaces (i.e. the
	 *		number of fields listed wasn't as wide as the whole window).
	 *
	 *		Ideally, we will never be passed in a start/end pair wider than
	 *		the width of the formatted output, but you never know...
	 */
	if (col <= end) {
		memset(outstr, ' ', end-col+1);
		outstr += end-col+1;
	}
	outstr[-1] = '\0';		/* Remove final terminating space */
}

/*
 *		UnderlineTitles(eventformat, outstr, underchar)
 *
 *		This function creates a string of underlines which matches the
 *		event headings in the passed event format, such that if the
 *		headings were printed out in full, the underline string would
 *		line up properly. underchar is the char used for underlining.
 *
 *		The reason we can't just take the obvious approach and generate
 *		a title string, then convert all non-space chars to minus, is
 *		because we can have titles like "Process Name" and we want the
 *		underline to be continuous, not break between process and name.
 *
 *		Returns a pointer to the newly formatted string (outstr). Outstr
 *		must have room to store the entire width, as defined by the
 *		event format.
 */
char *UnderlineTitles(EventFormat *ef, char *outstr, char underchar)
{
	char *p = outstr;

	while (ef->type != EF_END) {
		int width    = ef->width;
		int titleid  = ef->titlemsgid;
		int titlelen = strlen(MSG(titleid));

		if (titlelen > width)
			titlelen = width;

		memset(p, underchar, titlelen);
		p     += titlelen;
		width -= titlelen-1;		/* Include an extra space between cols */
		if (width) {
			memset(p, ' ', width);
			p += width;
		}
		ef++;
	}
	*--p = 0;						/* Replace final space with a null */
	return (outstr);
}

#if TESTING
/*
 *		TestEventFields()
 *
 *		Simple test function to check out our format conversion routines
 */
void TestEventFields(void)
{
	static char inline[200];
	static char formatstr[200];
	static EventFormat evformat[50];

	while (gets(inline) != NULL) {
		int len;

		if (*inline == 'q')
			break;

		len = ParseFormatString(inline, evformat, 50);
		BuildFormatString(evformat, formatstr, 200);

		printf("Converted string: >>%s<<, length = %d\n", formatstr, len);
	}
}

/*
 *		TestFormat
 *
 *		Test our text formatting routines
 */
void TestFormat()
{
	static char inline[200];
	static char formatstr[200];
	static EventFormat evformat[50];
	static char outstr[500];
	Event *ev;
	int showtitles = 0;

	for (;;) {
		int len;

		printf("Enter a format string (q to quit): ");
		gets(inline);
		if (*inline == 'q')
			return;

		len = ParseFormatString(inline, evformat, 50);
		BuildFormatString(evformat, formatstr, 200);
		printf("Actual string is >> %s << (%d chars)\n", formatstr, len);

		/*
		 *		Get ourselves a new event
		 */
		ev = GetNewEvent(0);
		if (!ev) {
			printf("Uhoh! Couldn't allocate event!\n");
			continue;
		}
		ev->procname  = "[ 1] SAS/C compiler";
		ev->filename  = "s:startup-sequence";
		ev->action    = "Open";
		ev->result    = "Okay";
		ev->calladdr  = 0x12345678;
		ev->processid = 0xDEADBEEF;
		DateStamp(&ev->datestamp);

		for (;;) {
			int start, end;

			printf("Now enter start,end values (-1 to quit, s to toggle): ");
			gets(inline);
			if (*inline == 'q')
				return;
			if (*inline == 's') {
				showtitles = !showtitles;
				printf("Now printing %s\n", showtitles ? "titles" : "contents");
				continue;
			}
			sscanf(inline, "%d,%d", &start, &end);
			if (start == -1)
				break;

			if (showtitles)
				FormatEvent(evformat, NULL, outstr, start, end);
			else
				FormatEvent(evformat, ev, outstr, start, end);
			printf("|%s|\n", outstr);
		}
	}
}

/*
 *		TestEvents
 *
 *		Creates some random events, then prints them out
 */
void TestEvents(void)
{
	int howmany = 0;
	int i;

	printf("Generate how many events? ");
	scanf("%d", &howmany);

	for (i = 0; i < howmany; i++) {
		Event *ev = GetNewEvent((rand() & 63));

		printf("Event head: %8x   Event new: %8x\n",
				(UBYTE *)HeadNode(&EventList) - BufferStart,
				(UBYTE *)ev - BufferStart);
	}
}
#endif
