/*****************************************************************************
*																			 *
*  HOTSPOT.C																 *
*																			 *
*  Copyright (C) Microsoft Corporation 1989-1994							 *
*  All Rights reserved. 													 *
*																			 *
*****************************************************************************/

#include "stdafx.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/***************************************************************************\
*
- Function: 	CbTranslateHotspot( szHotspot, phspt, phpj )
-
* Purpose:		Translate a possible hotspot binding into a command
*				to be put into the command table.
*
*				Valid binding strings are:
*
*				Macro:
*				  !string [or $string??????] REVIEW
*
*				Jump or glossary:
*				  context_string
*				  context_string @ file
*				  context_string > member
*				  context_string @ file > member
*				  context_string > member @ file
*
*				Any binding may be preceded with a % to suppress
*				special formatting (invisible hotspot).
*
* ASSUMES
*	args IN:	szHotspot - buffer containing binding string
*				phspt	  - hotspot type (hsptJump or hsptDefine)
*				phpj	  - hpj struct (we use the err and drgwsmag)
*
* PROMISES
*	returns:	size of command in bytes if valid, 0 if invalid
*	args OUT:	szHotspot - command copied here
*				phspt	  - changed to hsptMacro if it's a macro
*
* Side Effects: may display error messages
*
* Notes:
*
* Bugs:
*
* +++
*
* Method:
*
* Notes:
*
\***************************************************************************/

int STDCALL CbTranslateHotspot(PSTR pszHotspot, HSPT* phspt)
{
	WORD cb; // size IS important for this! Must be 16 bits
	BOOL fInvisible = FALSE;
	BOOL fUnderlined = FALSE;

	PSTR psz = FirstNonSpace(pszHotspot, options.fDBCS);

	if (*psz == '%') {
		fInvisible = TRUE;
		psz = FirstNonSpace(psz + 1, options.fDBCS);
	}

	if (*psz == '*') {
		fInvisible = fUnderlined = TRUE;
		psz = FirstNonSpace(psz + 1, options.fDBCS);
	}

	PSTR pszMacro = SzMacroFromSz(psz);

	// check for macro

	if (pszMacro != NULL) {
		if (*pszMacro == '\0') {
			VReportError(HCERR_EMPTY_MACRO, &errHpj);
			return 0;
		}

		if (Execute(pszMacro) == RET_MACRO_EXPANSION)
			pszMacro = (PSTR) GetMacroExpansion();

		// Add the macro even if there is an error

		if (fUnderlined)
			*phspt = hsptULMacro;
		else
			*phspt = hsptMacro;

		pszHotspot[0] = (BYTE) (fInvisible ? bLongMacroInv : bLongMacro);
		cb = strlen(pszMacro) + 1;

		// Use memmove, because pszMacro == pszHotspot + 1

		memmove(pszHotspot + sizeof(BYTE) + sizeof(WORD), pszMacro, cb);
		*(WORD UNALIGNED *) (pszHotspot + sizeof(BYTE)) = cb;
		return sizeof(BYTE) + sizeof(WORD) + cb;
	}

	PSTR pszFile	= StrChr(psz, '@', fDBCSSystem);
	PSTR pszMember	= StrChr(psz, '>', fDBCSSystem);
	PSTR pszContext = psz;

	if (pszFile)
		*pszFile++ = '\0';

	if (pszMember) {
		*pszMember++ = '\0';

		// member with note is meaningless

		if (*phspt == hsptDefine) {
			VReportError(HCERR_NOTE_JUMP_WND, &errHpj, pszContext);
			pszMember = NULL;
		}
		else
			pszMember = SzSkipBlanksSz(pszMember);

		// We'll verify the window name later
	}

	if (pszFile)
		pszFile = SzTrimSz(pszFile);

	RemoveTrailingSpaces(pszContext);

	if (*pszContext == '\0') {
		VReportError(HCERR_EMPTY_HOT_CTX, &errHpj, pszContext);
		return 0;
	}
	else if(!FValidContextSz(pszContext)) {
		VReportError(HCERR_INVALID_HOT_CTX, &errHpj, pszContext);
		return 0;
	}

	HASH hash = HashFromSz(pszContext);

	if (pszFile == NULL) {

	  /*
	   * Record context string info for error processing. If it's an
	   * alias, record also the context string it's aliased to.
	   */

	  FRecordContext(hash, psz, SzTranslateHash(&hash), FALSE, &errHpj);

	  PBYTE pb = (PBYTE) pszHotspot;

	  if (!pszMember) {

		// use short hotspot format

		if (*phspt == hsptJump)
			*pb = (char) (fInvisible ? bShortInvHashJump : bShortHashJump);
		else {
			ASSERT(*phspt == hsptDefine);
			*pb = (char) (fInvisible ? bShortInvHashNote : bShortHashNote);
		}
		++pb;

		if (fUnderlined)
		  *phspt = ULHsptFromHspt(*phspt);

		*(HASH *) pb = hash;
		return sizeof(char) + sizeof(HASH);
	  }
	  else {	  // pszMember != NULL
		DWORD iwsmag;

		if ((iwsmag = VerifyWindowName(pszMember)) == INDEX_BAD)
		  return 0;

		// store hotspot type

		pb = (PBYTE) pszHotspot;

		if (*phspt == hsptJump)
		  *pb = (char) (fInvisible ? bLongInvHashJump : bLongHashJump);
		else {
		  ASSERT(*phspt == hsptDefine);
		  *pb = (char) (fInvisible ? bLongInvHashNote : bLongHashNote);
		}
		++pb;

		if (fUnderlined)
		  *phspt = ULHsptFromHspt(*phspt);

		// store size of data in bytes (flags + HASH + member index)

		cb = sizeof(BYTE) + sizeof(HASH) + sizeof(BYTE);
		*(WORD *) pb = cb;
		pb += sizeof(WORD);

		// store flags

		((QJI) pb) ->bFlags = fIMember;

		// store hash value

		((QJI) pb) ->hash = hash;

		// store member index

		((QJI) pb) ->uf.iMember = (BYTE) iwsmag;

		return sizeof(char) + sizeof(WORD) + cb;
	  }
	}
	else {		  // pszFile != NULL

		// copy so we can overwrite buffer

		pszFile = lcStrDup(pszFile);

		PBYTE pb = (PBYTE) pszHotspot;

		// store hotspot type

		if (*phspt == hsptJump)
			*pb = (char) (fInvisible ? bLongInvHashJump : bLongHashJump);

		else {
			ASSERT(*phspt == hsptDefine);
			*pb = (char) (fInvisible ? bLongInvHashNote : bLongHashNote);
		}
		++pb;

		if (pszMember == NULL) {

			// store size of data in bytes (flags + HASH + filename + '\0')

			cb = sizeof(BYTE) + sizeof(HASH) + strlen(pszFile) + 1;
			*(WORD *) pb = cb;
			pb += sizeof(WORD);

			// store flags

			((QJI) pb) ->bFlags = fSzFile;

			// store hash

			((QJI) pb) ->hash = hash;

			// store file name

			lstrcpy((PSTR) ((QJI) pb)->uf.szFileOnly, pszFile);
		}
		else {		// pszMember != NULL

			// copy so we can overwrite buffer

			pszMember = lcStrDup(pszMember);

			// store size of data in bytes (flags + HASH + filename\0 + memname\0)

			cb = sizeof(BYTE) + sizeof(HASH) + strlen(pszFile) + 1
											   + strlen(pszMember) + 1;
			*(WORD *) pb = cb;
			pb += sizeof(WORD);

			// store flags

			((QJI) pb) ->bFlags = fSzFile | fSzMember;

			// store hash

			((QJI) pb) ->hash = hash;

			// store member name

			lstrcpy((PSTR) ((QJI) pb)->uf.szMemberAndFile, pszMember);

			// store file name

			lstrcpy((PSTR) ((QJI) pb)->uf.szMemberAndFile + strlen(pszMember) + 1,
				pszFile);
		  lcFree(pszMember);
		}

		if (fUnderlined)
			*phspt = ULHsptFromHspt(*phspt);

		lcFree(pszFile);
		return sizeof(char) + sizeof(WORD) + cb;
	}
}

/***************************************************************************
 *
 -	Name:		 VerifyShedBinding
 -
 *	Purpose:
 *	  This function gets called with shed binding strings, which may
 *	contain macros or context strings that need to be checked for
 *	errors.
 *
 *	Arguments:
 *	  bBindType:		Bind character.
 *	  szBinding:		Binding string.
 *
 *	Returns:
 *	  nothing.
 *
 *	Globals:
 *	  Gets warning level and error log file from err.
 *
 *	+++
 *
 *	Notes:
 *
 ***************************************************************************/

void STDCALL VerifyShedBinding(BYTE bBindType, PSTR szBinding,
	PSTR pchFile)
{
	ERR err;
	HASH hash;
	PSTR pszMember, pszContext;

	if (bBindType == COLDSPOT)
		return;

	err.lpszFile = pchFile;
	err.ep = epTopic;	// REVIEW
	err.iTopic = 0;
	err.iWarningLevel = errHpj.iWarningLevel;

	CStr pszBinding(szBinding);

	if (FMacroHotspot(bBindType)) {

		// We don't care about the actual macro, we just want to report any
		// problems with it.

		Execute(SzTrimSz(pszBinding));
	}
	else {

		// Don't check interfile jumps

		if (StrChr(pszBinding, '@', fDBCSSystem))
			return;

		pszMember  = StrChr(pszBinding, '>', fDBCSSystem);
		if (pszMember != NULL) {
			*pszMember++ = '\0';
			pszMember = SzTrimSz(pszMember);
			if (VerifyWindowName(pszMember) == INDEX_BAD)
				return;
		}

		pszContext = SzTrimSz(pszBinding);
		if (*pszContext == '\0') {
			VReportError(HCERR_MISS_SHED_CTX, &errHpj);
			return;
		}
		else if (!FValidContextSz(pszContext)) {
			VReportError(HCERR_INV_SHED_CTX, &errHpj, pszContext);
			return;
		}
		hash = HashFromSz(pszContext);
		FRecordContext(hash, pszContext, SzTranslateHash(&hash), FALSE, &err);
	}
}

/***************************************************************************
 *
 -	Name:		 FValidContextSz
 -
 *	Purpose:
 *	  This function determines whether the given string may be
 *	used as a context string.
 *
 *	Arguments:
 *	  SZ:  String to validate.
 *
 *	Returns:
 *	  TRUE if the string is a valid context string, FALSE otherwise.
 *
 ***************************************************************************/

BOOL STDCALL FValidContextSz(PCSTR pszContext)
{
	/*
	 * To avoid confusion with macro strings, context strings may not begin
	 * with an exclamation point.
	 */

	if (*pszContext == CH_MACRO || *pszContext == '\0')
		return FALSE;

	// Version 4.x help files have almost no limitations on context strings

	if (version >= 4) {
		if (strpbrk(pszContext, "#=>@%"))
			return FALSE;
		else
			return TRUE;
	}

	for (; *pszContext != '\0'; pszContext++) {
		if (!IsCharAlphaNumeric(*pszContext) && *pszContext != '!' &&
				*pszContext != '.' && *pszContext != '_')
			return FALSE;
	}
	return TRUE;
}

/***************************************************************************
 *
 -	Name:		 HashFromSz
 -
 *	Purpose:
 *	  This function returns a hash value from the given context string.
 *	The string is assumed to contain only valid context string characters.
 *
 *	Arguments:
 *	  SZ:	Null terminated string to compute the hash value from.
 *
 *	Returns:
 *	  The hash value for the given string.
 *
 ***************************************************************************/

// REVIEW: 26-Jul-1993	[ralphw] Why 43 when we only use 38 characters? Will
//	this allow for the addition of the SPACE character?

// This constant defines the alphabet size for our hash function.

static const HASH MAX_CHARS = 43L;

HASH STDCALL HashFromSz(PSTR pszKey)
{
	int ich, cch;
	HASH  hash = 0L;

	cch = strlen(pszKey);

	// REVIEW: 14-Oct-1993 [ralphw] -- Note lack of check for a hash collision.

	for (ich = 0; ich < cch; ++ich) {
		if (pszKey[ich] == '!')
			hash = (hash * MAX_CHARS) + 11;
		else if (pszKey[ich] == '.')
			hash = (hash * MAX_CHARS) + 12;
		else if (pszKey[ich] == '_')
			hash = (hash * MAX_CHARS) + 13;
		else if (pszKey[ich] == '0')
			hash = (hash * MAX_CHARS) + 10;

		/*
		 * REVIEW: 26-Jul-1993	[ralphw] -- I'll bet this is an
		 * international issue. Find out what they did in 3.1 in
		 * adt\hash.c.
		 */

		else if (pszKey[ich] <= 'Z')
			hash = (hash * MAX_CHARS) + (pszKey[ich] - '0');
		else
			hash = (hash * MAX_CHARS) + (pszKey[ich] - '0' - ('a' - 'A'));
	}

	/*
	 * Since the value 0 is reserved as a nil value, if any context
	 * string actually hashes to this value, we just move it.
	 */

	return (hash == 0 ? 0 + 1 : hash);
}


/***************************************************************************

	FUNCTION:	VerifyWindowName

	PURPOSE:	Called when an interfile jump specifies a window -- verifies
				that the window exists

	PARAMETERS:
		pszWindow

	RETURNS:	TRUE if the window is valid


	COMMENTS:

	MODIFICATION DATES:
		15-Mar-1994 [ralphw]

***************************************************************************/

UINT STDCALL VerifyWindowName(PCSTR pszWindow)
{
	if (!pdrgWsmag || pdrgWsmag->Count() == 0) {
		VReportError(HCERR_NO_WINDOW_SECTION, &errHpj, pszWindow);
		return (UINT) INDEX_BAD;
	}

	PWSMAG	qwsmag;
	int   iwsmag;

	for (qwsmag = (PWSMAG) pdrgWsmag->GetBasePtr(), iwsmag = 0;
			iwsmag < pdrgWsmag->Count();
			qwsmag++, iwsmag++) {
		if (StrICmp(qwsmag->rgchMember, pszWindow) == 0)
			return (UINT) iwsmag;  // found the window
	}

	// Check for "main" window class.

	if (iwsmag == pdrgWsmag->Count() &&
			StrICmp(pszWindow, txtMainWindow) != 0) {
		VReportError(HCERR_NO_SUCH_WINDOW, &errHpj, pszWindow);
		return (UINT) INDEX_BAD;
	}
	return (UINT) INDEX_MAIN;
}

/***************************************************************************

	FUNCTION:	StrICmp

	PURPOSE:	Use this whenever a string comparison that is sensitive
				to NLS considerations needs to be used.

	PARAMETERS:
		psz1
		psz2

	RETURNS:

	COMMENTS:

	MODIFICATION DATES:
		12-Jun-1994 [ralphw]

***************************************************************************/

int STDCALL StrICmp(PCSTR psz1, PCSTR psz2)
{
	// We do this for speed and because JChicago build 122 gives incorrect
	// results for CompareStringA

	if (!lcid || LANGIDFROMLCID(lcid) == 0x0409) // American locale
		return _stricmp(psz1, psz2);
	else
		return CompareStringA(lcid, fsCompareI | NORM_IGNORECASE,
			psz1, -1, psz2, -1) - 2;
}