/*
 *	@doc INTERNAL
 *
 *	@module	RTFLOG.CPP - RichEdit RTF log
 *
 *		Contains the code for the RTFLog class which can be used 
 *		to log the number of times RTF tags are read by the RTF reader
 *		for use in coverage testing. TODO: Implement RTF tag logging for the Mac
 *
 *	Authors:<nl>
 *		Created for RichEdit 2.0:	Brad Olenick
 *
 *	Copyright (c) 1995-2000, Microsoft Corporation. All rights reserved.
 */

#include "_common.h"
#include "_rtflog.h"
#include "tokens.h"

extern INT cKeywords;
extern const KEYWORD rgKeyword[];


#if defined(DEBUG) && !defined(NOFULLDEBUG)
/*
 *	CRTFRead::TestParserCoverage()
 *
 *	@mfunc
 *		A debug routine used to test the coverage of HandleToken.  The routine
 *		puts the routine into a debug mode and then determines:
 *
 *			1.  Dead tokens - (T & !S & !P)
 *				Here, token:
 *					a) is defined in token.h  (T)
 *					b) does not have a corresponding keyword (not scanned)  (!S)
 *					c) is not processed by HandleToken  (!P)
 *			2.  Tokens that are parsed but not scanned - (T & !S & P)
 *				Here, token:
 *					a) is defined in token.h  (T)
 *					b) does not have a corresponding keyword (not scanned)  (!S}
 *					c) is processed by HandleToken  (P)
 *			3.  Tokens that are scanned but not parsed - (T & S & !P)
 *				Here, token:
 *					a) is defined in token.h  (T)
 *					b) does have a corresponding keyword (is scanned)  (S)
 *					c) is not processed by HandleToken  (!P)
 */
void CRTFRead::TestParserCoverage()
{
	int i;
	char *rgpszKeyword[tokenMax - tokenMin];
	BOOL rgfParsed[tokenMax - tokenMin];
	char szBuf[256];

	// Put HandleToken in debug mode
	_fTestingParserCoverage = TRUE;

	// Gather info about tokens/keywords
	for(i = 0; i < tokenMax - tokenMin; i++)
	{
		_token = (TOKEN)(i + tokenMin);
		rgpszKeyword[i] = PszKeywordFromToken(_token);
		rgfParsed[i] = HandleToken() == ecNoError;
	}

	// Reset HandleToken to non-debug mode
	_fTestingParserCoverage = FALSE;

	// Should coverage check include those we know will fail test, but
	// which we've examined and know why they fail?
	BOOL fExcuseCheckedToks = TRUE;

	if(GetProfileIntA("RICHEDIT DEBUG", "RTFCOVERAGESTRICT", 0))
		fExcuseCheckedToks = FALSE;

	// (T & !S & !P)  (1. above)
	for(i = 0; i < tokenMax - tokenMin; i++)
	{
	  	if(rgpszKeyword[i] || rgfParsed[i]) 
			continue;

		TOKEN tok = (TOKEN)(i + tokenMin);

		// Token does not correspond to a keyword, but still may be scanned
		// check list of individual symbols which are scanned
		if(FTokIsSymbol(tok))
			continue;

		// Check list of tokens which have been checked and fail
		// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
		if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
			continue;

		sprintf(szBuf, "CRTFRead::TestParserCoverage():  Token neither scanned nor parsed - token = %d", tok);
		AssertSz(0, szBuf);
	}

	// (T & !S & P)  (2. above)
	for(i = 0; i < tokenMax - tokenMin; i++)
	{
		if(rgpszKeyword[i] || !rgfParsed[i])
			continue;

		TOKEN tok = (TOKEN)(i + tokenMin);

		// Token does not correspond to a keyword, but still may be scanned
		// check list of individual symbols which are scanned
		if(FTokIsSymbol(tok))
			continue;

		// Check list of tokens which have been checked and fail
		// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
		if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
			continue;

		sprintf(szBuf, "CRTFRead::TestParserCoverage():  Token parsed but not scanned - token = %d", tok);
		AssertSz(0, szBuf);
	}

	// (T & S & !P)  (3. above)
	for(i = 0; i < tokenMax - tokenMin; i++)
	{
		if(!rgpszKeyword[i] || rgfParsed[i])
			continue;

		TOKEN tok = (TOKEN)(i + tokenMin);

		// Check list of tokens which have been checked and fail
		// the sanity check for some known reason (see FTokFailsCoverageTest def'n)
		if(fExcuseCheckedToks && FTokFailsCoverageTest(tok))
			continue;

		sprintf(szBuf, "CRTFRead::TestParserCoverage():  Token scanned but not parsed - token = %d, tag = \\%s", tok, rgpszKeyword[i]);
		AssertSz(0, szBuf);
	}
}

/*
 *	CRTFRead::PszKeywordFromToken()
 *
 *	@mfunc
 *		Searches the array of keywords and returns the keyword
 *		string corresponding to the token supplied
 *
 *	@rdesc
 *		returns a pointer to the keyword string if one exists
 *		and NULL otherwise
 */
CHAR *CRTFRead::PszKeywordFromToken(TOKEN token)
{
	for(int i = 0; i < cKeywords; i++)
	{
		if(rgKeyword[i].token == token) 
			return rgKeyword[i].szKeyword;
	}
	return NULL;
}

/*
 *	CRTFRead::FTokIsSymbol(TOKEN tok)
 *
 *	@mfunc
 *		Returns a BOOL indicating whether the token, tok, corresponds to an RTF symbol
 *		(that is, one of a list of single characters that are scanned in the
 *		RTF reader)
 *
 *	@rdesc
 *		BOOL - 	indicates whether the token corresponds to an RTF symbol
 */
BOOL CRTFRead::FTokIsSymbol(TOKEN tok)
{
	const BYTE *pbSymbol = NULL;

	extern const BYTE szSymbolKeywords[];
	extern const TOKEN tokenSymbol[];

	// check list of individual symbols which are scanned
	for(pbSymbol = szSymbolKeywords; *pbSymbol; pbSymbol++)
	{
		if(tokenSymbol[pbSymbol - szSymbolKeywords] == tok)
			return TRUE;
	}
	return FALSE;
}

/*
 *	CRTFRead::FTokFailsCoverageTest(TOKEN tok)
 *
 *	@mfunc
 *		Returns a BOOL indicating whether the token, tok, is known to fail the
 *		RTF parser coverage test.  These tokens are those that have been checked 
 *		and either:
 *			1) have been implemented correctly, but just elude the coverage test
 *			2) have yet to be implemented, and have been recognized as such
 *
 *	@rdesc
 *		BOOL - 	indicates whether the token has been checked and fails the
 *				the parser coverage test for some known reason
 */
BOOL CRTFRead::FTokFailsCoverageTest(TOKEN tok)
{
	switch(tok)
	{
	// (T & !S & !P)  (1. in TestParserCoverage)
		// these really aren't tokens per se, but signal ending conditions for the parse
		case tokenError:
		case tokenEOF:

	// (T & !S & P)  (2. in TestParserCoverage)
		// emitted by scanner, but don't correspond to recognized RTF keyword
		case tokenUnknownKeyword:
		case tokenText:
		case tokenASCIIText:

		// recognized directly (before the scanner is called)
		case tokenStartGroup:
		case tokenEndGroup:

		// recognized using context information (before the scanner is called)
		case tokenObjectDataValue:
		case tokenPictureDataValue:

	// (T & S & !P)  (3. in TestParserCoverage)
		// None

			return TRUE;
	}

	return FALSE;
}
#endif // DEBUG

/*
 *	CRTFLog::CRTFLog()
 *	
 *	@mfunc
 *		Constructor - 
 *			1.  Opens a file mapping to log hit counts, creating
 *					the backing file if neccessary
 *			2.  Map a view of the file mapping into memory
 *			3.  Register a windows message for change notifications
 *
 */
CRTFLog::CRTFLog() : _rgdwHits(NULL), _hfm(NULL), _hfile(NULL)
{
#ifndef NOFULLDEBUG
	const char cstrMappingName[] = "RTFLOG";
	const char cstrWM[] = "RTFLOGWM";
	const int cbMappingSize = sizeof(ELEMENT) * ISize();

	BOOL fNewFile = FALSE;

	// Check for existing file mapping
	if(!(_hfm = OpenFileMappingA(FILE_MAP_ALL_ACCESS,
								TRUE,
								cstrMappingName)))
	{
		// No existing file mapping
		// Get the file with which to create the file mapping
		// first, attempt to open an existing file
		if(!(_hfile = CreateFileA(LpcstrLogFilename(),
								GENERIC_READ | GENERIC_WRITE,
								0,
								NULL,
								OPEN_EXISTING,
								FILE_ATTRIBUTE_NORMAL,
								NULL)))
		{
			// No existing file, attempt to create new
			if(!(_hfile = CreateFileA(LpcstrLogFilename(),
										GENERIC_READ | GENERIC_WRITE,
										0,
										NULL,
										OPEN_ALWAYS,
										FILE_ATTRIBUTE_NORMAL,
										NULL)))
			{
				return;
			}
			fNewFile = TRUE;
		}

		_hfm = CreateFileMappingA(_hfile, NULL, PAGE_READWRITE,	0,
								  cbMappingSize, cstrMappingName);
		if(!_hfm)
			return;
	}

	LPVOID lpv = MapViewOfFile(_hfm, FILE_MAP_ALL_ACCESS, 0, 0,	cbMappingSize);
	if(!lpv)
		return;

	// Register windows message for change notifications
	SideAssert(_uMsg = RegisterWindowMessageA(cstrWM));

	// Memory-mapped file is now mapped to _rgdwHits
	_rgdwHits = (PELEMENT)lpv;

	// Zero the memory-mapped file if we created it new
	// (Win95 gives us a new file w/ garbage in it for some reason)
	if(fNewFile)
		Reset();
#endif	
}


/*
 *	CRTFLog::Reset()
 *	
 *	@mfunc
 *		Resets the hitcount of each element in the log to 0
 *
 */
void CRTFLog::Reset()
{
	if(!FInit())
		return;

	for(INDEX i = 0; i < ISize(); i++)
		(*this)[i] = 0;

	// notify clients of change
	ChangeNotifyAll();
}

/*
 *	CRTFLog::UGetWindowMsg
 *
 *	@mdesc
 *		Returns the window message id used for change notifications
 *
 *	@rdesc
 *		UINT		window message id
 *
 *	@devnote
 *		This should be inline, but the AssertSz macro doesn't compile
 *		properly on the Mac if its placed in a header file
 *
 */
UINT CRTFLog::UGetWindowMsg() const
{
	AssertSz(FInit(), "CRTFLog::UGetWindowMsg():  CRTFLog not initialized properly");

	return _uMsg;
}

/*
 *	CRTFLog::operator[]
 *
 *	@mdesc
 *		Returns reference to element i of RTF log (l-value)
 *
 *	@rdesc
 *		ELEMENT &			reference to element i of log
 *
 *	@devnote
 *		This should be inline, but the AssertSz macro doesn't compile
 *		properly on the Mac if its placed in a header file
 *
 */
CRTFLog::ELEMENT &CRTFLog::operator[](INDEX i)
{
	AssertSz(i < ISize(), "CRTFLog::operator[]:  index out of range");
	AssertSz(FInit(), "CRTFLog::operator[]:  CRTFLog not initialized properly");

	return _rgdwHits[i]; 
}

/*
 *	CRTFLog::operator[]
 *
 *	@mdesc
 *		Returns reference to element i of RTF log (r-value)
 *
 *	@rdesc
 *		const ELEMENT &	reference to element i of log
 *		
 *	@devnote
 *		This should be inline, but the AssertSz macro doesn't compile
 *		properly on the Mac if its placed in a header file
 *
 */
const CRTFLog::ELEMENT &CRTFLog::operator[](INDEX i) const
{
	AssertSz(i < ISize(), "CRTFLog::operator[]:  index out of range");
	AssertSz(FInit(), "CRTFLog::operator[]:  CRTFLog not initialized properly");

	return _rgdwHits[i]; 
}


/*
 *	CRTFLog::LpcstrLogFilename()
 *	
 *	@mfunc
 *		Returns name of file to be used for log
 *
 *	@rdesc
 *		LPCSTR		pointer to static buffer containing file name
 */
LPCSTR CRTFLog::LpcstrLogFilename() const
{
	static char szBuf[MAX_PATH] = "";
#ifndef NOFULLDEBUG
	const char cstrLogFilename[] = "RTFLOG";
	if(!szBuf[0])
	{
		DWORD cchLength;
		char szBuf2[MAX_PATH];

		SideAssert(cchLength = GetTempPathA(MAX_PATH, szBuf2));

		// append trailing backslash if neccessary
		if(szBuf2[cchLength - 1] != '\\')
		{
			szBuf2[cchLength] = '\\';
			szBuf2[cchLength + 1] = 0;
		}

		wsprintfA(szBuf, "%s%s", szBuf2, cstrLogFilename);
	}
#endif
	return szBuf;
}


/*
 *	CRTFLog::IIndexOfKeyword(LPCSTR lpcstrKeyword, PINDEX piIndex)
 *	
 *	@mfunc
 *		Returns the index of the log element which corresponds to
 *		the RTF keyword, lpcstrKeyword
 *
 *	@rdesc
 *		BOOL		flag indicating whether index was found
 */
BOOL CRTFLog::IIndexOfKeyword(LPCSTR lpcstrKeyword, PINDEX piIndex) const
{
	INDEX i;

	for(i = 0; i < ISize(); i++)
	{
		if(strcmp(lpcstrKeyword, rgKeyword[i].szKeyword) == 0)
			break;
	}

	if(i == ISize())
		return FALSE;

	if(piIndex)
		*piIndex = i;

	return TRUE;
}


/*
 *	CRTFLog::IIndexOfToken(TOKEN token, PINDEX piIndex)
 *	
 *	@mfunc
 *		Returns the index of the log element which corresponds to
 *		the RTF token, token
 *
 *	@rdesc
 *		BOOL		flag indicating whether index was found
 */
BOOL CRTFLog::IIndexOfToken(TOKEN token, PINDEX piIndex) const
{
	INDEX i;

	for(i = 0; i < ISize(); i++)
	{
		if(token == rgKeyword[i].token)
			break;
	}

	if(i == ISize())
		return FALSE;

	if(piIndex)
		*piIndex = i;

	return TRUE;
}