/* ================================================================================== */
/* Copyright (c) 1998-1999 3Com Corporation or its subsidiaries. All rights reserved. */
/* ================================================================================== */

#include "EmulatorCommon.h"
#include "Logging.h"

#include "Byteswapping.h"		// Canonical
#include "Gremlins.h"			// Gremlins_IsOn, Gremlins_EventCounter
#include "Platform.h"			// GetMilliseconds
#include "Strings.r.h"			// kStr_LogFileSize
#include "UAE_Utils.h"			// uae_memcpy


// ---------------------------------------------------------------------------
//		¥ CLASS LogStream
// ---------------------------------------------------------------------------

const long	kDefaultBufferSize = 1024 * 1024L;
const long	kFindUniqueFile = -1;
const long	kInvalidTimestamp = -1;
const long	kInvalidGremlinCounter = -2;
const long	kEventTextMaxLen = 255;

LogStream	gStdLog ("Log");


/***********************************************************************
 *
 * FUNCTION:	LogStream::LogStream
 *
 * DESCRIPTION:	Constructor.
 *
 * PARAMETERS:	baseName - base name to use for the file the data gets
 *					written to.  This base name will be prepended with
 *					the path of the directory to write the file to, and
 *					appended with "%04d.txt", where %04d will be
 *					replaced with a number to make sure the file's
 *					name is unique.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

LogStream::LogStream (const char* baseName) :
	fBaseName (baseName),
	fFileIndex (kFindUniqueFile),
	fBuffer (kDefaultBufferSize),
	fBufferSize (kDefaultBufferSize),
	fBufferOffset (0),
	fLastGremlinEventCounter (kInvalidGremlinCounter),
	fLastTimestampTime (kInvalidTimestamp),
	fBaseTimestampTime (kInvalidTimestamp)
{
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::~LogStream
 *
 * DESCRIPTION:	Destructor.  Writes any buffered text to the file and
 *				closes the file.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

LogStream::~LogStream (void)
{
	this->DumpToFile ();
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::Printf
 *
 * DESCRIPTION:	A printf-like function for adding text to the log file.
 *				The text is preceded by a timestamp, and is suffixed
 *				with a newline.
 *
 * PARAMETERS:	fmt - a printf-like string for formatting the output
 *				text.
 *
 *				... - additional printf-like parameters.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

int LogStream::Printf (const char* fmt, ...)
{
	int		n;
	va_list	arg;

	va_start (arg, fmt);

	n = this->VPrintf (fmt, arg);

	va_end (arg);

	return n;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::DataPrintf
 *
 * DESCRIPTION:	A printf-like function for adding text to the log file.
 *				The text is preceded by a timestamp, and is suffixed
 *				with a newline.
 *
 * PARAMETERS:	data - binary data to be included in the output
 *
 *				dataLen - length of binary data
 *
 *				fmt - a printf-like string for formatting the output
 *				text.
 *
 *				... - additional printf-like parameters.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

int LogStream::DataPrintf (const void* data, long dataLen, const char* fmt, ...)
{
	int		n;
	va_list	arg;

	va_start (arg, fmt);

	n = this->VPrintf (fmt, arg);

	// Dump the data nicely formatted

	const uae_u8*	dataP = (const uae_u8*) data;

	if (dataP && dataLen)
	{
		for (long ii = 0; ii < dataLen; ii += 16)
		{
			char	text[16 * 4 + 4];	// 16 bytes * (3 for hex + 1 for ASCII) + 2 tabs + 1 space + 1 NULL
			char*	p = text;

			*p++ = '\t';

			// Print up to 16 bytes of hex on the left
			long	jj;
			for (jj = ii; jj < ii + 16 ; ++jj)
			{
				if (jj < dataLen)
					p += sprintf (p, "%02X ", dataP[jj]);
				else
					p += sprintf (p, "   ");

				if (jj == ii + 7)
					p += sprintf (p, " ");

				assert (p - text < sizeof (text));
			}

			// Print the ascii on the right
			*p++ = '\t';
			for (jj = ii; jj < ii + 16 && jj < dataLen; ++jj)
			{
				char	c = dataP[jj];
				if (!isprint(c))
					c = '.';
				*p++ = c;

				assert (p - text < sizeof (text));
			}

			assert (p - text <= sizeof (text));

			this->Write (text, p - text);
		}
	}	

	va_end (arg);

	return n;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::VPrintf
 *
 * DESCRIPTION:	A vprintf-like function for adding text to the log file.
 *
 * PARAMETERS:	fmt - a vprintf-like string for formatting the output
 *				text.
 *
 *				args - additional vprintf-like parameters.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

int LogStream::VPrintf (const char* fmt, va_list args)
{
	char	buffer[2000];

	int n = vsprintf (buffer, fmt, args);

	// debug check, watch for buffer overflows here
	if (n < 0 || n >= sizeof (buffer))
	{
		Platform::Debugger();
	}

	this->Write (buffer, n);

	return n;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::Write
 *
 * DESCRIPTION:	
 *
 * PARAMETERS:	
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

int LogStream::Write (const void* buffer, long size)
{
	this->Timestamp ();
	this->Append ((const char*) buffer, size);
	this->NewLine ();

	return size;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::Clear
 *
 * DESCRIPTION:	Clear any currently logged data
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::Clear (void)
{
	fBufferOffset = 0;
	fBaseTimestampTime = kInvalidTimestamp;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::GetLogSize
 *
 * DESCRIPTION:	Returns the maximum amount of text to be written to
 *				the log file.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	The maximum size.
 *
 ***********************************************************************/

long LogStream::GetLogSize (void)
{
	return fBufferSize;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::SetLogSize
 *
 * DESCRIPTION:	Sets the maximum amount of text to be written to the
 *				log file.  Any currently logged data is lost.
 *
 * PARAMETERS:	size - the new maximum value.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::SetLogSize (long size)
{
	char*	newBuffer = (char*) Platform::AllocateMemory (size);

	if (newBuffer)
	{
		fBuffer.Adopt (newBuffer);

		fBufferOffset = 0;
		fBufferSize = size;
	}
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::EnsureNewFile
 *
 * DESCRIPTION:	Ensure that the logged data is written to a new file the
 *				next time DumpToFile is called.  Otherwise, the data
 *				will be written to the same file it was written to the
 *				previous time DumpToFile was called.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::EnsureNewFile (void)
{
	fFileIndex = kFindUniqueFile;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::DumpToFile
 *
 * DESCRIPTION:	Dumps any buffered text to the log file, prepending
 *				a message saying that only the last <mumble> bytes
 *				of text are buffered.
 *
 *				If no data has been logged (or has been discarded with
 *				a call to Clear), nothing is written out and no file is
 *				created.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::DumpToFile (void)
{
	if (fBufferOffset == 0)
		return;

	FileReference	fileRef = this->CreateFileReference ();
	FileHandle		fileHnd (fileRef, kCreateAlways | kOpenWrite, 'CWIE', 'TEXT');

	if (fileHnd.IsOpen ())
	{
		// If we didn't wrap, do a simple dump: no header text saying
		// that the text was truncated, and no attempt to dump the
		// text in two parts.

		if (fBufferOffset <= fBufferSize)
		{
			this->DumpToFile (fileHnd, fBuffer, fBufferOffset);
		}
		else

		// If we wrapped, print a message saying that only the last portion
		// of the text is begin saved/dumped.

		{
			char	buffer[200];
			string	templ = Platform::GetString (kStr_LogFileSize);
			sprintf (buffer, templ.c_str (), fBufferSize / 1024L);
			this->DumpToFile (fileHnd, buffer, strlen (buffer));

			// Dump the text.

			long	startingOffset = fBufferOffset % fBufferSize;
			long	firstPart = fBufferSize - startingOffset;
			long	secondPart = fBufferSize - firstPart;

			this->DumpToFile (fileHnd, fBuffer + startingOffset, firstPart);
			this->DumpToFile (fileHnd, fBuffer, secondPart);
		}
	}
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::DumpToFile
 *
 * DESCRIPTION:	Dumps the given text to the log file, converting any
 *				EOL characters along the way.
 *
 * PARAMETERS:	f - open file to write the text to.
 *
 *				s - text to write.
 *
 *				size - number of characters to write (the input text
 *					is not necessarily NULL terminated).
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::DumpToFile (FileHandle& f, const char* s, long size)
{
	StMemory	converted;
	long		convertedLength;
	
	Platform::ToHostEOL (converted, convertedLength, s, size);

	f.Write (convertedLength, converted.Get ());
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::CreateFileReference
 *
 * DESCRIPTION:	Creates a file reference based on the base file name
 *				passed in to the constructor and the current fFileIndex.
 *				If fFileIndex is kFindUniqueFile, this routine attempts
 *				to find a file index that results in a new file being
 *				created.  Otherwise, the current fFileIndex is used to
 *				either open an existing file or create a new one.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	The desired FileReference.
 *
 ***********************************************************************/

FileReference LogStream::CreateFileReference (void)
{
	FileReference	result;
	char			buffer[32];

	if (fFileIndex == kFindUniqueFile)
	{
		// Look for an unused file name.

		fFileIndex = 0;

		do
		{
			++fFileIndex;

			sprintf (buffer, "%s_%04ld.txt", fBaseName, fFileIndex);
			result = Platform::CreateReferenceInEmulatorPath (buffer);
		}
		while (result.IsSpecified () && result.Exists ());
	}
	else
	{
		sprintf (buffer, "%s_%04ld.txt", fBaseName, fFileIndex);
		result = Platform::CreateReferenceInEmulatorPath (buffer);
	}

	return result;
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::Timestamp
 *
 * DESCRIPTION:	Outputs a timestamp to the log stream.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::Timestamp (void)
{
	Bool	reformat = false;
	uae_u32	now = Platform::GetMilliseconds ();

	// This may be a case of pre-optimization, but we try to keep around
	// a formatted timestamp string for as long as possible.  If either
	// the time changes or the Gremlin event number changes, we force
	// the regeneration of the cached timestamp string.

	if (fLastTimestampTime != now)
		reformat = true;

	if (!reformat && Gremlins_IsOn () && fLastGremlinEventCounter != Gremlins_EventCounter ())
		reformat = true;

	if (reformat)
	{
		fLastTimestampTime = now;

		// We try to print out logged data with timestamps that are
		// relative to the first event recorded.

		if (fBaseTimestampTime == kInvalidTimestamp)
			fBaseTimestampTime = now;

		now -= fBaseTimestampTime;

		// If a Gremlin is running, use a formatting string that includes
		// the event number.  Otherwise, use a format string that omits it.

		if (Gremlins_IsOn ())
		{
			fLastGremlinEventCounter = Gremlins_EventCounter ();
			sprintf (fLastTimestampString, "%ld.%03ld (%ld):\t", now / 1000, now % 1000, fLastGremlinEventCounter);
		}
		else
		{
			sprintf (fLastTimestampString, "%ld.%03ld:\t", now / 1000, now % 1000);
		}
	}

	this->Append (fLastTimestampString, strlen (fLastTimestampString));
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::NewLine
 *
 * DESCRIPTION:	Outputs and EOL to the log stream.
 *
 * PARAMETERS:	none
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::NewLine (void)
{
	this->Append ("\n", 1);
}


/***********************************************************************
 *
 * FUNCTION:	LogStream::Append
 *
 * DESCRIPTION:	Generic function for adding text (actually, any kind of
 *				unformatted data) to the output stream.  If the amount
 *				of text in the buffer exceeds the maximum specified
 *				amount, any old text is deleted.  This function is
 *				the bottleneck for all such functions in this class.
 *
 * PARAMETERS:	buffer - pointer to the text to be added.
 *
 *				size - length of the text (in bytes) to be added.
 *
 * RETURNED:	nothing
 *
 ***********************************************************************/

void LogStream::Append (const char* buffer, long size)
{
	// Nothing to do if nothing to copy.

	if (size == 0)
	{
	}
	
	// The amount we are copying in is larger than the size of our buffer;
	// fill in the entire buffer with the last part of the incoming data.

	else if (size >= fBufferSize)
	{
		memcpy (fBuffer, buffer + size - fBufferSize, fBufferSize);
		fBufferOffset = fBufferSize;
	}

	// The amount we are copying is smaller than the size of the buffer.
	// Copy as much of the new data into the buffer as possible before
	// we reach the end.  If we reach the end, wrap around.

	else
	{
		long	startingOffset = fBufferOffset % fBufferSize;
		long	amtToEndOfBuffer = fBufferSize - startingOffset;
		long	amtToCopy = size < amtToEndOfBuffer ? size : amtToEndOfBuffer;

		memcpy (fBuffer + startingOffset, buffer, amtToCopy);

		// See if we wrapped.  If so, copy the rest of the data.

		if (amtToCopy < size)
		{
			amtToCopy = size - amtToCopy;
			memcpy (fBuffer, buffer + amtToEndOfBuffer, amtToCopy);
		}

		fBufferOffset += size;

		// fBufferOffset is used to determine where to insert new characters.
		// Don't let it get too big in order to avoid overflow problems (yes,
		// we can overflow 32-bits when logging Gremlins events).
		//
		// The adjustment below is intended not only to preserve the starting
		// insertion point, but also to remember that we have wrapped (that's
		// what the "+ fBufferSize" is for).

		if (fBufferOffset > 2 * fBufferSize)
		{
			fBufferOffset = (fBufferOffset % fBufferSize) + fBufferSize;
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ StubEmFrmGetTitle
// ---------------------------------------------------------------------------
// Returns a pointer to the title string of a form.  Copied from Form.c.

#if 0
static CharPtr StubEmFrmGetTitle (const FormPtr frm)
{
	Word i;
	CharPtr str;

	for (i = 0; i < frm->numObjects; i++)
		{
		if (frm->objects[i].objectType == frmTitleObj)
			{
			str = frm->objects[i].object.title->text;
			if (*str)
				return (str);
			else
				return "";
			}
		}
	return "Untitled";

}
#endif


// ---------------------------------------------------------------------------
//		¥ StubEmPrintFormID
// ---------------------------------------------------------------------------
// Displays the form resource id associated with the window passed.

static void StubEmPrintFormID (WinHandle winHandle, char* desc, char* eventText)
{
#if 1	// This function (and its subordinate functions) needs to be dissuaded
		// from direct memory accesses before we can use it in the emulator.

	UNUSED_PARAM(winHandle)
	UNUSED_PARAM(desc)
	UNUSED_PARAM(eventText)
#else
	WinPtr winPtr;
	WinPtr exitWinPtr;
	FormPtr frm;
	CharPtr title;
	
	if (winHandle)
		{
		exitWinPtr = WinGetWindowPointer (winHandle);
		
		// Check if the handle is still valid.  If the form has been deleted 
		// then we can't dereference the window pointer.
		
		// Search the window list for the pointer.
		winHandle = WinGetFirstWindow ();
		while (winHandle)
			{
			winPtr = WinGetWindowPointer (winHandle);
			if (winPtr == exitWinPtr)
				break;
			
			winHandle = winPtr->nextWindow;		
			}
		
		
		if (winHandle && winPtr->windowFlags.dialog)
			{
			frm = (FormPtr) winPtr;
			title = StubEmFrmGetTitle(frm);
			if (*title != nullChr)
				sprintf (&eventText[strlen(eventText)],"%s: \"%s\"", desc, title);
			else
				sprintf (&eventText[strlen(eventText)],"%s ID: %d %s", desc, frm->formId);
			}
		}
#endif
}


// ---------------------------------------------------------------------------
//		¥ VirtualKeyDescriptions
// ---------------------------------------------------------------------------
// Return a key description

const char* kVirtualKeyDescriptions [] =
{
	" (lowBatteryChr)",
	" (enterDebuggerChr)",
	" (nextFieldChr)",
	" (startConsoleChr)",
	" (menuChr)",
	" (commandChr)",
	" (confirmChr)",
	" (launchChr)",
	" (keyboardChr)",
	" (findChr)",
	" (calcChr)",
	" (prevFieldChr)",
	" (alarmChr)",
	" (ronamaticChr)",
	" (graffitiReferenceChr)",
	" (keyboardAlphaChr)",
	" (keyboardNumericChr)",
	" (lockChr)",
	" (backlightChr)",
	" (autoOffChr)",
	" (irScanningChr)",
	" (sendDataChr)"
	" (irReceiveChr)",
	" (radioCoverageOKChr)",
	" (radioCoverageFailChr)",
	" (powerOffChr)"
};



#define irGotDataChr 0x01FC		// to initiate NotifyReceive
#define irInitLibChr 0x01FD		// Used when intializing the library
#define irSerialChr 0x01FE		// Switches to Rs232 line driver


const char* kHardKeyDescriptions [] =
{
	" (irGotDataChr)",	// 0x01FC
	" (irInitLibChr)",	// 0x01FD
	" (irSerialChr)",	// 0x01FE
	"",					// 0x01FF
	"",					// 0x0200
	"",					// 0x0201
	"",					// 0x0202
	"",					// 0x0203
	" (hard1Chr)",		// 0x0204
	" (hard2Chr)",
	" (hard3Chr)",
	" (hard4Chr)",
	" (hardPowerChr)",
	" (hardCradleChr)",
	" (hardCradle2Chr)",
	" (hardContrastChr)",
	" (hardAntennaChr)"
};

static const char* StubEmKeyDescription (Int key)
{
	if (key >= lowBatteryChr && key <= powerOffChr)
		return kVirtualKeyDescriptions [key - lowBatteryChr];

	if (key >= irGotDataChr && key <= hardAntennaChr)
		return kHardKeyDescriptions [key - irGotDataChr];

	return "";
}


// ---------------------------------------------------------------------------
//		¥ PrvGetEventText
// ---------------------------------------------------------------------------
// Displays the passed event in the emulator's event tracewindow if it is
// active.

static Bool PrvGetEventText(EventPtr eventP, char* eventText)
{
	long curLen = strlen (eventText);
	eventText += curLen;

	switch (eventP->eType)
	{
		case nilEvent:
			return false;
		
		case penDownEvent:
			sprintf(eventText,"penDownEvent    X:%u   Y:%u", 
					eventP->screenX, eventP->screenY);
			break;

		case penUpEvent:
			strcpy(eventText,"penUpEvent");
			sprintf(eventText,"penUpEvent      X:%u   Y:%u", 
					eventP->screenX, eventP->screenY);
			break;

		case penMoveEvent:
			strcpy(eventText,"penMoveEvent");
			sprintf(eventText,"penMoveEvent    X:%u   Y:%u", 
					eventP->screenX, eventP->screenY);					
			break;

		case keyDownEvent:
			if ((eventP->data.keyDown.chr < 0x0100) && isprint (eventP->data.keyDown.chr))
			{
				sprintf(eventText,"keyDownEvent    Key:'%c' 0x%02X%s,  Modifiers: 0x%04X", 
						(char) eventP->data.keyDown.chr, eventP->data.keyDown.chr,  
						StubEmKeyDescription(eventP->data.keyDown.chr),
						eventP->data.keyDown.modifiers);
			}
			else
			{
				sprintf(eventText,"keyDownEvent    Key:0x%02X%s,  Modifiers: 0x%04X", 
						eventP->data.keyDown.chr,  
						StubEmKeyDescription(eventP->data.keyDown.chr),
						eventP->data.keyDown.modifiers);
			}
			break;

		case winEnterEvent:
			sprintf(eventText,"winEnterEvent   Enter: %p   Exit: %p", 
					eventP->data.winEnter.enterWindow, eventP->data.winEnter.exitWindow);		
			StubEmPrintFormID (eventP->data.winEnter.enterWindow, "  Enter Form", eventText);
			StubEmPrintFormID (eventP->data.winEnter.exitWindow, "  Exit Form", eventText);
			break;

		case winExitEvent:
			sprintf(eventText,"winExitEvent    Enter: %p   Exit: %p", 
					eventP->data.winExit.enterWindow, eventP->data.winExit.exitWindow);
			StubEmPrintFormID (eventP->data.winExit.enterWindow, "  Enter Form", eventText);
			StubEmPrintFormID (eventP->data.winExit.exitWindow, "  Exit Form", eventText);
			break;

		case ctlEnterEvent:
			sprintf(eventText,"ctlEnterEvent   ID: %u", 
					eventP->data.ctlEnter.controlID);
			break;

		case ctlSelectEvent:
			sprintf(eventText,"ctlSelectEvent  ID: %u   On: %u", 
					eventP->data.ctlSelect.controlID, eventP->data.ctlSelect.on);					
			break;

		case ctlRepeatEvent:
			sprintf(eventText,"ctlRepeatEvent  ID: %u   Time: %lu", 
					eventP->data.ctlRepeat.controlID, eventP->data.ctlRepeat.time);					
			break;

		case ctlExitEvent:
			sprintf(eventText,"ctlExitEvent");
			break;

		case lstEnterEvent:
			sprintf(eventText,"lstEnterEvent   ID: %u   Item: %u", 
					eventP->data.lstEnter.listID, eventP->data.lstEnter.selection);			
			break;

		case lstSelectEvent:
			sprintf(eventText,"lstSelectEvent  ID: %u   Item: %u", 
					eventP->data.lstSelect.listID, eventP->data.lstSelect.selection);
			break;

		case lstExitEvent:
			sprintf(eventText,"lstExitEvent    ID: %u", 
					eventP->data.lstExit.listID);
			break;

		case popSelectEvent:
			sprintf(eventText,"popSelectEvent  CtlID: %u  ListID: %u  Item: %u", 
					eventP->data.popSelect.controlID, eventP->data.popSelect.listID,
					eventP->data.popSelect.selection);
			break;

		case fldEnterEvent:
			sprintf(eventText,"fldEnterEvent   ID: %u", 
					eventP->data.fldEnter.fieldID);
			break;

		case fldHeightChangedEvent:
			sprintf(eventText,"fldHeightChangedEvent  ID: %u  Height: %u  Pos: %u", 
					eventP->data.fldHeightChanged.fieldID, 
					eventP->data.fldHeightChanged.newHeight,
					eventP->data.fldHeightChanged.currentPos);
			break;

		case fldChangedEvent:
			sprintf(eventText,"fldChanged  ID: %u", 
					eventP->data.fldChanged.fieldID);
			break;

		case tblEnterEvent:
			sprintf(eventText,"tblEnterEvent   ID: %u   Row: %u  Col: %u",
					eventP->data.tblEnter.tableID,
					eventP->data.tblEnter.row,
					eventP->data.tblEnter.column);
			break;

		case tblSelectEvent:
			sprintf(eventText,"tblSelectEvent  ID: %u   Row: %u  Col: %u", 
					eventP->data.tblSelect.tableID, 
					eventP->data.tblSelect.row, 
					eventP->data.tblSelect.column);					
			break;

		case tblExitEvent:
			sprintf(eventText,"tblExitEvent  ID: %u   Row: %u  Col: %u", 
					eventP->data.tblExit.tableID, 
					eventP->data.tblExit.row, 
					eventP->data.tblExit.column);					
			break;
		
		case daySelectEvent:
			strcpy(eventText,"daySelectEvent");
			break;

		case menuEvent:
			sprintf(eventText,"menuEvent       ItemID: %u", 
					eventP->data.menu.itemID);
			break;

		case appStopEvent:
			strcpy(eventText,"appStopEvent");
			break;

		case frmLoadEvent:
			sprintf(eventText,"frmLoadEvent    ID: %u", 
					eventP->data.frmOpen.formID);
			break;

		case frmOpenEvent:
			sprintf(eventText,"frmOpenEvent    ID: %u", 
					eventP->data.frmOpen.formID);
			break;

		case frmGotoEvent:
			sprintf(eventText,"frmGotoEvent    ID: %u  Record: %u  Field: %u", 
					eventP->data.frmGoto.formID,
					eventP->data.frmGoto.recordNum,
					eventP->data.frmGoto.matchFieldNum);
			break;

		case frmUpdateEvent:
			sprintf(eventText,"frmUpdateEvent    ID: %u", 
					eventP->data.frmUpdate.formID);
			break;

		case frmSaveEvent:
			sprintf(eventText,"frmSaveEvent");
			break;

		case frmCloseEvent:
			sprintf(eventText,"frmCloseEvent   ID: %u", 
					eventP->data.frmClose.formID);
			break;

		case frmTitleEnterEvent:
			sprintf(eventText,"frmTitleEnterEvent   ID: %u", 
					eventP->data.frmTitleEnter.formID);
			break;

		case frmTitleSelectEvent:
			sprintf(eventText,"frmTitleSelectEvent   ID: %u", 
					eventP->data.frmTitleSelect.formID);
			break;

		case sclEnterEvent:
			sprintf(eventText,"sclEnterEvent   ID: %u", 
					eventP->data.sclEnter.scrollBarID);
			break;

		case sclRepeatEvent:
			sprintf(eventText,"sclRepeatEvent   ID: %u  Value: %u,  New value: %u", 
					eventP->data.sclRepeat.scrollBarID,
					eventP->data.sclRepeat.value,
					eventP->data.sclRepeat.newValue);
			break;

		case sclExitEvent:
			sprintf(eventText,"sclExitEvent   ID: %u", 
					eventP->data.sclExit.scrollBarID);
			break;

		case tsmConfirmEvent:
			sprintf(eventText,"tsmConfirmEvent   ID: %u  Text: ", 
					eventP->data.tsmConfirm.formID);
			uae_strncat(eventText, (uaecptr)eventP->data.tsmConfirm.yomiText, kEventTextMaxLen - curLen);
			break;
			
		case tsmFepButtonEvent:
			sprintf(eventText,"tsmFepButtonEvent   ID: %u", 
					eventP->data.tsmFepButton.buttonID);
			break;
		
		default:
			if (eventP->eType >= firstINetLibEvent)
			{
				if (eventP->eType < firstWebLibEvent)
				{
					sprintf(eventText, "NetLib event #%u", eventP->eType);
				}
				else if (eventP->eType < firstWebLibEvent + 0x0100)
				{
					sprintf(eventText, "WebLib event #%u", eventP->eType);
				}
				else if (eventP->eType < firstUserEvent)
				{
					sprintf(eventText,"Unknown Event!   Event->eType #: %u",
							eventP->eType);
				}
				else
				{
					sprintf(eventText, "Application event #%u", eventP->eType);
				}
			}
			else
			{
				sprintf(eventText,"Unknown Event!   Event->eType #: %u",
						eventP->eType);
			}
			break;
	}

	return true;
}

// ---------------------------------------------------------------------------
//		¥ LogEvtAddEventToQueue
// ---------------------------------------------------------------------------

void LogEvtAddEventToQueue (uaecptr eventP)
{
	if (LogEnqueuedEvents ())
	{
		// Get a copy of the event record.  This will be in big-endian
		// format, so byteswap it if necessary..

		EventType	newEvent;
		uae_memcpy ((void*) &newEvent, eventP, sizeof (newEvent));
		Canonical (newEvent);

		// Get the text for this event.  If there is such text, log it.

		char	eventText[kEventTextMaxLen] = " -> EvtAddEventToQueue: ";
		if (PrvGetEventText (&newEvent, eventText))
		{
			LogAppendMsg (eventText);
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtAddUniqueEventToQueue
// ---------------------------------------------------------------------------

void LogEvtAddUniqueEventToQueue (uaecptr eventP, DWord, Boolean)
{
	if (LogEnqueuedEvents ())
	{
		// Get a copy of the event record.  This will be in big-endian
		// format, so byteswap it if necessary..

		EventType	newEvent;
		uae_memcpy ((void*) &newEvent, eventP, sizeof (newEvent));
		Canonical (newEvent);

		// Get the text for this event.  If there is such text, log it.

		char	eventText[kEventTextMaxLen] = " -> EvtAddUniqueEventToQueue: ";
		if (PrvGetEventText (&newEvent, eventText))
		{
			LogAppendMsg (eventText);
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtEnqueuePenPoint
// ---------------------------------------------------------------------------

void LogEvtEnqueuePenPoint (uaecptr ptP)
{
	if (LogEnqueuedEvents ())
	{
		SWord	penX = (SWord) get_word (ptP + 0);
		SWord	penY = (SWord) get_word (ptP + 2);

		LogAppendMsg (" -> EvtEnqueuePenPoint: pen->x=%d, pen->y=%d.", penX, penY);
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtEnqueueKey
// ---------------------------------------------------------------------------

void LogEvtEnqueueKey (UInt ascii, UInt keycode, UInt modifiers)
{
	if (LogEnqueuedEvents ())
	{
		if ((ascii < 0x0100) && isprint (ascii))
		{
			LogAppendMsg (" -> EvtEnqueueKey: ascii = '%c' 0x%04X, keycode = 0x%04X, modifiers = 0x%04X.",
					(char) ascii, ascii, keycode, modifiers);
		}
		else
		{
			LogAppendMsg (" -> EvtEnqueueKey: ascii = 0x%04X, keycode = 0x%04X, modifiers = 0x%04X.",
					ascii, keycode, modifiers);
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtGetEvent
// ---------------------------------------------------------------------------

void LogEvtGetEvent (uaecptr eventP, SDWord timeout)
{
	UNUSED_PARAM(timeout)

	if (LogDequeuedEvents ())
	{
		// Get a copy of the event record.  This will be in big-endian
		// format, so byteswap it if necessary..

		EventType	newEvent;
		uae_memcpy ((void*) &newEvent, eventP, sizeof (newEvent));
		Canonical (newEvent);

		// Get the text for this event.  If there is such text, log it.

		char	eventText[kEventTextMaxLen] = "<-  EvtGetEvent: ";
		if (PrvGetEventText (&newEvent, eventText))
		{
			LogAppendMsg (eventText);
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtGetPen
// ---------------------------------------------------------------------------

void LogEvtGetPen (uaecptr pScreenX, uaecptr pScreenY, uaecptr pPenDown)
{
	if (LogDequeuedEvents ())
	{
		SWord	screenX = (SWord) get_word (pScreenX);
		SWord	screenY = (SWord) get_word (pScreenY);
		Boolean	penDown = (Boolean) get_byte (pPenDown);

		static SWord	lastScreenX = -2;
		static SWord	lastScreenY = -2;
		static Boolean	lastPenDown = false;
		static long		numCollapsedEvents;

		if (screenX != lastScreenX ||
			screenY != lastScreenY ||
			penDown != lastPenDown)
		{
			lastScreenX = screenX;
			lastScreenY = screenY;
			lastPenDown = penDown;

			numCollapsedEvents = 0;

			LogAppendMsg ("<-  EvtGetPen: screenX=%d, screenY=%d, penDown=%d.",
					(int) screenX, (int) screenY, (int) penDown);
		}
		else
		{
			++numCollapsedEvents;
			if (numCollapsedEvents == 1)
				LogAppendMsg ("<-  EvtGetPen: <<<eliding identical events>>>.");
		}
	}
}


// ---------------------------------------------------------------------------
//		¥ LogEvtGetSysEvent
// ---------------------------------------------------------------------------

void LogEvtGetSysEvent (uaecptr eventP, SDWord timeout)
{
	UNUSED_PARAM(timeout)

	if (LogDequeuedEvents ())
	{
		// Get a copy of the event record.  This will be in big-endian
		// format, so byteswap it if necessary..

		EventType	newEvent;
		uae_memcpy ((void*) &newEvent, eventP, sizeof (newEvent));
		Canonical (newEvent);

		// Get the text for this event.  If there is such text, log it.

		char	eventText[kEventTextMaxLen] = "<-  EvtGetSysEvent: ";
		if (PrvGetEventText (&newEvent, eventText))
		{
			LogAppendMsg (eventText);
		}
	}
}

