/*************************************************************************
*                                                                        *
*  STOP.C                                                                *
*                                                                        *
*  Copyright (C) Microsoft Corporation 1990-1994                         *
*  All Rights reserved.                                                  *
*                                                                        *
**************************************************************************
*                                                                        *
*  Module Intent                                                         *
*   Stop list indexing and retrieval                                     *
*                                                                        *
**************************************************************************
*                                                                        *
*  Written By   : Binh Nguyen                                            *
*  Current Owner: Binh Nguyen                                            *
*                                                                        *
*************************************************************************/
#include <mvopsys.h>
#include <orkin.h>
#include <mem.h>
#include <memory.h>
#include <io.h>
#include <mvsearch.h>
#include "common.h"

#ifdef _DEBUG
static BYTE NEAR s_aszModule[] = __FILE__;	/* Used by error return functions.*/
#endif

#define cbSTOP_BUF  ((CB)512)   // Number of bytes read at a time
                                //  from the stop-word file.
/*************************************************************************
 *
 *                       API FUNCTIONS
 *  Those functions should be exported in a .DEF file
 *************************************************************************/
PUBLIC  HRESULT EXPORT_API FAR PASCAL MVStopListAddWord(LPSIPB, LST);
PUBLIC  HRESULT EXPORT_API FAR PASCAL MVStopListIndexLoad (HFPB, LPSIPB, LSZ);
PUBLIC  LPSIPB EXPORT_API FAR PASCAL MVStopListInitiate(WORD, PHRESULT);
PUBLIC  void EXPORT_API FAR PASCAL MVStopListDispose(LPSIPB);

PUBLIC  HRESULT EXPORT_API FAR PASCAL MVStopListLoad(HFPB, LPSIPB, LSZ,
													BREAKER_FUNC, LPV);
PUBLIC  HRESULT EXPORT_API PASCAL FAR MVStopFileBuild (HFPB, LPSIPB, LSZ);
PUBLIC LPCHAIN EXPORT_API FAR PASCAL MVStopListFind(_LPSIPB lpsipb, LST lstWord);

/*************************************************************************
 *
 *                    INTERNAL PRIVATE FUNCTIONS
 *  All of them should be declared near
 *************************************************************************/

PRIVATE WORD NEAR PASCAL GetHashKey (WORD, LST);

/*************************************************************************
 *
 *                    INTERNAL PUBLIC FUNCTIONS
 *  All of them should be declared far, and included in some include file
 *************************************************************************/

PUBLIC HRESULT FAR PASCAL FStopCallback(LST, LST, LFO, LPV);

/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func LPSIPB FAR PASCAL | MVStopListInitiate |
 *      Create and initiate a stop-word information structure
 *
 *  @parm PHRESULT | phr |
 *      Pointer to error buffer.
 *
 *	@parm WORD | wTabSize |
 *      Table size in DWORD. The process of stop word checking will
 *      be faster with larger values of dwTabSize.
 *
 *  @rdesc  the pointer to the stop-list structure if succeeded,
 *      NULL if failed. The error buffer will contain descriptions about
 *      the cause of the failure
 *************************************************************************/

PUBLIC  LPSIPB EXPORT_API FAR PASCAL MVStopListInitiate(WORD wTabSize,
    PHRESULT phr)
{
    _LPSIPB lpsipb;

    if (wTabSize < HASH_SIZE)    
        wTabSize = HASH_SIZE;
        
    /* Allocate a StopInfo structure */
    if ((lpsipb = (_LPSIPB)GLOBALLOCKEDSTRUCTMEMALLOC(sizeof(SIPB) +
        wTabSize * sizeof(LPB))) == NULL)
    {
exit00:
        SetErrCode(phr, E_OUTOFMEMORY);
        return NULL;
    }

    lpsipb->HashTab = (LPCHAIN FAR *)((LPB)lpsipb + sizeof(SIPB));
    
    /* Allocate a word block buffer */
    if ((lpsipb->lpBlkMgr = BlockInitiate (WORDBUF_SIZE, 0, 0, 0)) == NULL)
    {
        GlobalLockedStructMemFree((LPV)lpsipb);
        goto exit00;
    }

    lpsipb->wTabSize = wTabSize;      /* Size of hash table */
    lpsipb->lpfnStopListLookup = MVStopListLookup;
    return (LPSIPB)lpsipb;
}

/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func HRESULT FAR PASCAL | MVStopListAddWord |
 *      Add a word to a stop list
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *
 *  @parm LST | lstWord |
 *      Pointer to 2-byte length preceded Pascal word to be added
 *      into the stop-word list
 *
 *  @rdesc  S_OK if succeeded
 *************************************************************************/

PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListAddWord(_LPSIPB lpsipb, LST lstWord)
{
    WORD    wHash;
    LPCHAIN lpChain;
    WORD    wByteUsed;

    // Sanity check
    if (lpsipb == NULL || lstWord == NULL)    
        return(E_INVALIDARG);
        
    /* Look for the word. If it is already there then just
     * return S_OK, don't add it into the list
     */
    if (lpChain = MVStopListFind (lpsipb, lstWord))
    {
        // Don't add if already there.
        lpChain->dwCount++;
        return S_OK;
    }

    wByteUsed = *(LPUW)lstWord + 2;
    
#ifndef _32BIT
    if (lpsipb->cbTextUsed + wByteUsed > MAX_STOPWORD_BUFSIZE) {
        /* There are too many stop words */
        return ERR_TOOMANYSTOPS;
    }
#endif

    lpsipb->cbTextUsed += wByteUsed ;

    /* Copy the word into the word buffer block */
    if ((lpChain = (LPCHAIN)BlockCopy (lpsipb->lpBlkMgr, lstWord, wByteUsed,
        sizeof(CHAIN) - 1)) == NULL)
        return E_OUTOFMEMORY;

	lpChain->dwCount = 0;

    /* Compute hash key */
    wHash = GetHashKey(lpsipb->wTabSize, lstWord);

    /* Add the word to the hash table  */
    CH_NEXT(lpChain) = lpsipb->HashTab[wHash];  
    lpsipb->HashTab[wHash] = lpChain;
 
    return S_OK;             // Function worked.
}

/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func void FAR PASCAL | MVStopListDispose |
 *      Frees memory associated with a stop list.
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *************************************************************************/

PUBLIC void EXPORT_API FAR PASCAL MVStopListDispose (_LPSIPB lpsipb)
{
    if (lpsipb == NULL)
        return;

    /* Free the word buffer */
    BlockFree(lpsipb->lpBlkMgr);

    /* Free the stop info structure */
    GlobalLockedStructMemFree((LPV)lpsipb);
}


/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func HRESULT FAR PASCAL | MVStopListIndexLoad |
 *      Read a stop-word list stored in the subfile/dos file.
 *
 *  @parm HFPB | hfpb |
 *      Handle to input file.  Can be mvfs subfile or separate dos file.
 *  
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *  
 *  @parm LPIDX | lpidx |
 *      Pointer to index structure
 *  
 *  @parm LSZ | lszWordBreaker |
 *      Word breaker to be used
 *  
 *  @rdesc  S_OK if succeeded, other errors if failed.
 *************************************************************************/
/*
 The strings are stored in the file in a sequence of pascal strings
 */

PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListIndexLoad (HFPB hfpbSysFile,
    _LPSIPB lpsipb, LSZ lszStopFile)
{
    BYTE    argbInBuf[CB_STOP_BUF];
    FILEOFFSET lfo;
	FILEOFFSET foStart;
    HFPB    hfpbSubFile;
	BOOL	fOpenedFile;
    HRESULT     fRet = S_OK;
    WORD    cbRead;
    int     fLast;
    LPSTOP  lpStopHdr;
    LPB     lpWord;
    WORD    wOffsetInBuf;
    WORD    wLen;
    ERRB    errb;
    
    /* Sanity check */
    if (lpsipb == NULL)
        return SetErrCode (NULL, E_INVALIDARG);

    /* Open the subfile */
    if ((fOpenedFile =
			FsTypeFromHfpb(hfpbSubFile = hfpbSysFile) != FS_SUBFILE) &&
		(hfpbSubFile = FileOpen
			(hfpbSysFile, lszStopFile, hfpbSysFile ? FS_SUBFILE : REGULAR_FILE,
			READ, &errb)) == NULL) 
    {
        return errb;
    }

	// If we didn't open the file, we need to find out where the file seek
	// pointer is initially so that we only seek relative to that starting
	// position (i.e. the caller owns the part of the file that comes before).
	foStart = (fOpenedFile ? MakeFo(0,0) :
				FileSeek (hfpbSubFile, MakeFo (0, 0), wFSSeekCur, &fRet));

    /* Read and check the file validity */
    if (FAILED(fRet) ||
		(cbRead = (WORD)FileSeekRead
			(hfpbSubFile, (LPV)(lpStopHdr = (LPSTOP)argbInBuf),
			FoAddFo(foStart, MakeFo(0, 0)), sizeof(STOP_HDR), &fRet))
												!= sizeof(STOP_HDR)) 
    {
exit01:
		// Close file only if we were the one's who opened it.
		if (fOpenedFile)     
			(void)FileClose(hfpbSubFile);       // Return value not checked
												// because the file is open
												//  for read-only.
        return fRet;
    } 

    /* MAC codes. They will be eliminated through optimization */
    
    lpStopHdr->FileStamp = SWAPWORD(lpStopHdr->FileStamp);
    lpStopHdr->version = SWAPWORD(lpStopHdr->version);
    lpStopHdr->dwFileSize = SWAPLONG(lpStopHdr->dwFileSize);


    if (lpStopHdr->FileStamp != STOP_STAMP ||
        lpStopHdr->version != VERCURRENT)
    {
        fRet = SetErrCode(&errb, E_BADVERSION);
        goto exit01;
    }

    /* Start at the beginning of the buffer */
    wOffsetInBuf = 0;

    for (lfo = FoAddFo(foStart, MakeFo(STOP_HDR_SIZE, 0));;)
    {
		LPB		lpbCur;
		WORD	cbReadOurs = 0;

        if ((cbRead = (WORD)FileSeekRead(hfpbSubFile,
            lpbCur = ((LPB)argbInBuf + wOffsetInBuf), lfo,
            CB_STOP_BUF - wOffsetInBuf, &errb)) == cbIO_ERROR) 
        {
            SetErrCode(&errb, fRet = E_FILEREAD);
            goto exit01;
        } 

        lfo = FoAddDw(lfo, (DWORD)cbRead);

		while (cbRead - cbReadOurs++ >= sizeof(WORD))
		{
			if (*((WORD UNALIGNED * UNALIGNED)lpbCur) == 0)
			{
				FILEOFFSET	foCur;

				// Get our current seek position.
				foCur = FileSeek (hfpbSubFile, MakeFo (0, 0), wFSSeekCur, &fRet);

				// We already advanced cbReadOurs by one in the loop
				// condition; advance it by one more to account for
				// the second byte of the NULL word.  Then we move
				// the seek pointer back by the difference so that we
				// don't leave it past the end of our data.
				FileSeek (hfpbSubFile, 
							FoSubFo(foCur, MakeFo(cbRead - ++cbReadOurs, 0)),
															wFSSeekSet, &fRet);
				ITASSERT(SUCCEEDED(fRet));
				cbRead = cbReadOurs;
				fLast = TRUE;
			}
			else
				lpbCur++;
		}

        cbRead += wOffsetInBuf; // Catch what's left from previous scan
        wOffsetInBuf = 0;

        /* Add the word into the stop word list */
        for (lpWord = argbInBuf; cbRead > 0;) 
        {

            /* If the whole word has been read in, just add it to the
            stop list, else we have to "reconstruct" it
            */
            // erinfox: we have to byte-swap on Mac
            *(WORD UNALIGNED * UNALIGNED)lpWord = SWAPWORD(*(WORD UNALIGNED * UNALIGNED)lpWord);

            wLen = *(LPUW)(lpWord) + 2;
            if (wLen <= cbRead)
            {
                
                /* Everything fits */
                if ((fRet = MVStopListAddWord(lpsipb, lpWord)) != S_OK)
                    goto exit01;
                cbRead -= wLen;
                lpWord += wLen; /* Move to next word */
            }
            else
            {
                /* Copy the word to the beginning of the buffer */
                MEMCPY(argbInBuf, lpWord, cbRead);
                wOffsetInBuf = cbRead;
                break;
            }
        }

        if (fLast)
            break;
    }
    fRet = S_OK; // Succeeded
    goto exit01;
}

/*************************************************************************
 *  @doc API INDEX RETRIEVAL
 *
 *  @func HRESULT FAR PASCAL | MVStopListLoad |
 *      Read a stop-word list from an external file. The file must have
 *      only one stop word per line, or else there is potential loss
 *      of stop words.
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *
 *  @parm LSZ | lszStopFile |
 *      Stop word filename. This is a simple ASCII text file
 *
 *  @parm BREAKER_FUNC | lpfnBreakFunc |
 *      Word breaker to be used
 *
 *  @parm PHRESULT | phr |
 *      Pointer to error buffer.
 *
 *  @rdesc  S_OK if succeeded, other errors failed. 
 *************************************************************************/
PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListLoad(HFPB hfpbIn, _LPSIPB lpsipb,
    LSZ lszStopFile, BREAKER_FUNC lpfnBreakFunc,
    LPCHARTAB lpCharTab)
{
    BYTE    argbInBuf[cbSTOP_BUF];  // IO buffer
    HFPB    hfpb;					// File handle
	BOOL	fOpenedFile;
    _LPIBI  lpibi;                  // Pointer to internal breaker info
    HANDLE  hbi;                    // Handle to internal brekaer info
    HRESULT     fRet;               // Returned value
    BRK_PARMS brkParms;             // Breaker parameters structure
    LPB     lpStart;                // Beginning of strings to be parsed
    LPB     lpEnd;                  // End of strings to be parsed
    WORD    wStrLength;             // Bytes in string
    CB      cbTobeRead;             // Bytes to be read
    CB      cbRead;                 // Bytes actually read
    int     fLast;                  // TRUE if this is the last read
    int     fGetWord;               // TRUE if we get a whole word

    /* Sanity check */
    if (lpsipb == NULL || (lszStopFile == NULL && hfpbIn == NULL)
			|| lpfnBreakFunc == NULL)
        return E_INVALIDARG;

	if ((fOpenedFile = FsTypeFromHfpb(hfpb = hfpbIn) != FS_SUBFILE) &&
		(hfpb = (HANDLE)FileOpen
			(hfpbIn, lszStopFile, hfpbIn ? FS_SUBFILE : REGULAR_FILE,
			READ, &fRet)) == 0)
    {
        return (fRet);
    }

    /*  Allocate a breaker info block */

    if ((hbi = _GLOBALALLOC(DLLGMEM_ZEROINIT, (LCB)sizeof(IBI))) == NULL)
    {
        return E_OUTOFMEMORY;
    }
    lpibi = (_LPIBI)_GLOBALLOCK(hbi);

    /* Initialize variables */
    brkParms.lcbBufOffset = 0L;
    brkParms.lpInternalBreakInfo = lpibi;
    brkParms.lpvUser = lpsipb;
    brkParms.lpfnOutWord = (FWORDCB)FStopCallback;
    brkParms.lpStopInfoBlock = NULL;
    brkParms.lpCharTab = lpCharTab;

    cbTobeRead = cbSTOP_BUF;            // Read in a buffer whole
    lpStart = lpEnd = (LPB)argbInBuf;   // Start & End of string
    fGetWord = FALSE;                   // We didn't get any word yet
    wStrLength = 0;

    /* The idea is to break the file into sequences of lines, and pass
     * each line to the word breaker. The assumption made is that we
     * should only have one word per line, since various type breakers
     * can only handle one word a type.
     */

    for (;;)
    {
		cbRead = (WORD)FileRead(hfpb, lpEnd, cbTobeRead, &fRet);
        if (FAILED(fRet))
        {
exit01:
            /* Free breaker info block */
            _GLOBALUNLOCK(hbi);
            _GLOBALFREE(hbi);

            /* Close the file */
			if (fOpenedFile)
				FileClose(hfpb);
            return fRet;
        }
        else
            fLast = (cbRead != cbTobeRead);

        lpEnd = lpStart;
        cbRead += wStrLength;   // Get what left in buffer
        wStrLength = 0;

        while (cbRead != (CB)-1)
        {
            /* Break the buffer into lines */

            if (*lpEnd == '\r' || *lpEnd == '\n' || !cbRead)
            {
                if (wStrLength)
                {

                    /* Process the word we got */
                    brkParms.lpbBuf = lpStart;
                    brkParms.cbBufCount = wStrLength;

                    if ((fRet = (*lpfnBreakFunc)((LPBRK_PARMS)&brkParms))
                        != S_OK)
                        goto exit01;

                    /* Flush the breaker buffer */
                    brkParms.lpbBuf = NULL;
                    brkParms.cbBufCount = 0;
                    if ((fRet = (*lpfnBreakFunc)((LPBRK_PARMS)&brkParms))
                        != S_OK)
                        goto exit01;

                    wStrLength = 0;
                }
            }
            else
            {
                /* Update the pointer to the new word */
                if (wStrLength == 0)
                    lpStart = lpEnd;
                wStrLength++;   // Increase string's length
            }

            cbRead--;
            lpEnd++;
        }


        if (fLast)
            break;

        /* Now copy the partial string to the beginning of the buffer */
        MEMCPY(argbInBuf, lpStart, wStrLength);
        lpEnd = (lpStart = argbInBuf) + wStrLength;
        cbTobeRead = cbSTOP_BUF - wStrLength;   // Read in a buffer whole
    }

    if (wStrLength)
    {
        /* Flush the breaker buffer */
        brkParms.lpbBuf = NULL;
        brkParms.cbBufCount = 0;
        if ((fRet = (*lpfnBreakFunc)((LPBRK_PARMS)&brkParms)) != S_OK)
            goto exit01;
    }
    fRet = S_OK; // Succeeded
    goto exit01;
}


/*************************************************************************
 *  @doc    INTERNAL
 *
 *  @func   WORD NEAR PASCAL | GetHashKey |
 *      Compute the hash key of a string. This key is used for indexing
 *      into the stop word hash table
 *  
 *  @parm   LST | lstWord |
 *      Pointer to a 2-byte length preceded Pascal-type string
 *
 *  @rdesc
 *      Return the index into the stop words hash table
 *************************************************************************/

PRIVATE WORD NEAR PASCAL GetHashKey (WORD hashSize, LST lstWord)
{
    register unsigned int wHash;
    register unsigned int nLength;

    wHash = 0;
    nLength = *(LPUW)lstWord;
    lstWord += sizeof(WORD);
    for (; nLength; nLength--)
    {
        wHash = (wHash << 1) | (wHash >> 15);
        wHash ^= *lstWord++;
    }
    wHash %= hashSize;
    return ((WORD)wHash);
}

/*************************************************************************
 *  @doc    API RETRIEVAL INDEX
 *
 *  @func   LPCHAIN FAR PASCAL | MVStopListFind |
 *      This looks for a word (lstWord) in a stop-word (lpsipb) 
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word list structure
 *
 *  @parm LST | lstWord |
 *      Pointer to string to be looked for
 *
 *  @rdesc  Pointer to the node if found, NULL otherwise
 *************************************************************************/

PUBLIC LPCHAIN EXPORT_API FAR PASCAL MVStopListFind(_LPSIPB lpsipb, LST lstWord)
{
    WORD    wHash;      // Hash key
    LPCHAIN lpChain;    // Pointer to the word chain

    // Sanity check
    if (lpsipb == NULL || lstWord == NULL)    
        return(NULL);

    /* Compute hash key */
    wHash = GetHashKey(lpsipb->wTabSize, lstWord);
    lpChain = lpsipb->HashTab[wHash];
 
    while (lpChain)
    {
        if (!StringDiff2 (&CH_WORD(lpChain), lstWord))
            return (lpChain);
        lpChain = CH_NEXT(lpChain);
    }
    return (NULL);
}

 /*************************************************************************
 *  @doc    API RETRIEVAL INDEX
 *
 *  @func   HRESULT FAR PASCAL | MVStopListLookup |
 *      This looks for a word (lstWord) in a stop-word (lpsipb) 
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word list structure
 *
 *  @parm LST | lstWord |
 *      Pointer to string to be looked for
 *
 *  @rdesc  S_OK if found, E_FAIL if not, or other errors
 *************************************************************************/

PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListLookup(_LPSIPB lpsipb, LST lstWord)
{
    WORD    wHash;      // Hash key
    LPCHAIN lpChain;        // Pointer to the word chain


    // Sanity check
    if (lpsipb == NULL || lstWord == NULL)    
        return(E_INVALIDARG);
        
    /* Compute hash key */
    wHash = GetHashKey(lpsipb->wTabSize, lstWord);
    lpChain = lpsipb->HashTab[wHash];

    while (lpChain)
    {
        if (!StringDiff2 (&CH_WORD(lpChain), lstWord))
            return (S_OK);
        lpChain = CH_NEXT(lpChain);
    }
    return (E_FAIL);
}

/*************************************************************************
 *  @doc    API INDEX
 *
 *  @func HRESULT PASCAL FAR | MVStopFileBuild |
 *      Incorporate the stop word list into the system file
 *
 *  @parm   HFPB | hpfbSysFile |
 *      If non-zero, handle to an opened system file.
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *
 *  @parm LSZ | lszFilename |
 *      If hpfbSysFile is non-zero, this is the name of the stop's subfile
 *      else this is a regular DOS file
 *
 *  @rdesc S_OK if succeeded, E_FAIL if tehre is nothing to build
 *      or other errors
 *************************************************************************/
PUBLIC HRESULT EXPORT_API PASCAL FAR MVStopFileBuild (HFPB hfpbSysFile,
    _LPSIPB lpsipb, LSZ lszFilename)
{
    HFPB hfpbStop;          // Pointer to final index file info.
    HRESULT  fRet = S_OK;
    STOP_HDR Stop_hdr;
    HFPB hfpb = 0;
	BOOL fCreatedFile;
    BYTE Dummy[STOP_HDR_SIZE]; // Dummy buffer to write 0
    int i;
    LPCHAIN lpChain;
    LST lstWord;
    WORD wLen;
    CB cbByteLeft;
    GHANDLE hBuf;
    LPB lpbBuf;
    LPB lpbStart;
    LPB lpbLimit;
    ERRB errb;
    FILEOFFSET fo;
	FILEOFFSET foStart;

    /* Sanity check */
    if (lpsipb == NULL || (lszFilename == NULL && hfpbSysFile == NULL))
        return E_INVALIDARG;

    if (lpsipb->cbTextUsed == 0)
        return E_FAIL; /* Nothing to build */

    if ((fCreatedFile =
			FsTypeFromHfpb(hfpbStop = hfpbSysFile) != FS_SUBFILE) &&
		(hfpbStop = FileCreate(hfpbSysFile, lszFilename,
			hfpbSysFile ? FS_SUBFILE: REGULAR_FILE, &errb)) == 0)
        return errb;

	// If we didn't open the file, we need to find out where the file seek
	// pointer is initially so that we only seek relative to that starting
	// position (i.e. the caller owns the part of the file that comes before).
	foStart = (fCreatedFile ? MakeFo(0,0) :
				FileSeek (hfpbStop, MakeFo (0, 0), wFSSeekCur, &fRet));

	if (FAILED(fRet))
		goto exit01;

    /* Write out the stop file header */
    Stop_hdr.FileStamp = STOP_STAMP;
    Stop_hdr.version = VERCURRENT;
    Stop_hdr.dwFileSize = lpsipb->cbTextUsed;

    MEMSET(Dummy, 0, STOP_HDR_SIZE);

    /* Write all zeroes to the header area, which is larger than the
	 * STOP_HDR structure.
	 */
    if (FileSeekWrite (hfpbStop, Dummy, FoAddFo(foStart, MakeFo (0, 0)),
        STOP_HDR_SIZE, &errb) != STOP_HDR_SIZE) 
    {
        fRet = errb;
exit01:
		if (fCreatedFile)
	        FileClose (hfpbStop);
        return(fRet);
        
    }

    if (FileSeekWrite (hfpbStop,  &Stop_hdr, FoAddFo(foStart, MakeFo (0, 0)),
        sizeof (STOP_HDR), &errb) != sizeof (STOP_HDR)) 
    {
        fRet = errb;
        goto exit01;
    }


    /* Allocate a buffer to flush the data */
    if ((hBuf = _GLOBALALLOC (DLLGMEM, cbByteLeft = CB_HUGE_BUF)) == NULL)
    {
        SetErrCode (&errb, fRet = E_OUTOFMEMORY);
        goto exit01;
    }

    lpbBuf = lpbStart = (LPB)_GLOBALLOCK(hBuf);
    lpbLimit = lpbStart + CB_HUGE_BUF - CB_MAX_WORD_LEN;

    /* Seek the file to the correct offset */
    fo  = FoAddFo(foStart, MakeFo (STOP_HDR_SIZE, 0));
    
    if (!FoEquals (FileSeek (hfpbStop, fo, 0, &errb),  fo))
    {
        fRet = E_FILESEEK;
exit02:
        _GLOBALUNLOCK(hBuf);
        _GLOBALFREE(hBuf);
        goto exit01;
    }

    /* Write out the buffer */
    for (i = lpsipb->wTabSize - 1; i >= 0; i--)
    {
        for (lpChain = lpsipb->HashTab[i]; lpChain;
            lpChain = CH_NEXT(lpChain))
        {
            lstWord = &CH_WORD (lpChain);
            MEMCPY (lpbBuf, lstWord, wLen = *(WORD FAR *)lstWord + 2);
            lpbBuf += wLen;
            if (lpbBuf >= lpbLimit)
            {
                /* No more room, just flush the buffer */

                FileWrite(hfpbStop, lpbStart, (DWORD)(lpbBuf - lpbStart), &errb);
                if ((fRet = errb) != S_OK)
                    goto exit02;

                lpbBuf = lpbStart;
            }
        }
    }


    /*  Flush the buffer */
    FileWrite (hfpbStop, lpbStart, (DWORD)(lpbBuf - lpbStart), &errb);

    if ((fRet = errb) == S_OK)
	{
		/* Write a trailing 0 word (i.e. a NULL st) to mark
		 * the end of the word list.
		 */
		*((WORD *)lpbStart) = 0;
		FileWrite (hfpbStop, lpbStart, sizeof(WORD), &errb);
		fRet = errb;
	}

    goto exit02;
}

PUBLIC HRESULT FAR PASCAL FStopCallback(
    LST lstRawWord,
    LST lstNormWord,
    LFO lfoWordOffset,
    _LPSIPB lpsipb)
{
    return MVStopListAddWord(lpsipb, lstNormWord);
}


/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func HRESULT FAR PASCAL | MVStopListEnumWords |
 *      Enumerate the words in a stop list, getting a pointer to each.
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *
 *  @parm LST* | plstWord |
 *      Indirect Pointer to 2-byte length preceded Pascal word that is
 *      the next word identified by *pdwWordInfo and *ppvWordInfo.
 *
 *	@parm LONG* | plWordInfo |
 *		Pointer to information used to determine what the next word is
 *		in the stop word list.  Passing -1 along with NULL for *ppvWordInfo
 *		means start at the beginning.  On exit, this contains an appropriate
 *		value that can be passed in again to get the next word, provided
 *		that no intervening calls have been made to MVStopListAddWord.
 *
 *	@parm LPVOID* | ppvWordInfo |
 *		Indirect pointer to information used to determine what the next word is
 *		in the stop word list.  Passing NULL along with -1 for *plWordInfo
 *		means start at the beginning.  On exit, this contains an appropriate
 *		value that can be passed in again to get the next word, provided
 *		that no intervening calls have been made to MVStopListAddWord.
 *
 *  @rdesc  S_OK if succeeded
 *	@rdesc	E_OUTOFRANGE if there are no more words in the stop list.
 *************************************************************************/
PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListEnumWords(_LPSIPB lpsipb,
						LST *plstWord, LONG *plWordInfo, LPVOID *ppvWordInfo)
{
	LPCHAIN	lpchain = NULL;
	LONG	iHashChain;

	if (lpsipb == NULL || plstWord == NULL ||
		plWordInfo == NULL || ppvWordInfo == NULL)
		return (SetErrReturn(E_POINTER));

	iHashChain = *plWordInfo;

	// If after the last call to us, we were left sitting on a hash chain
	// element, just advance to the next one (which may be NULL).
	if ((lpchain = (LPCHAIN) *ppvWordInfo) != NULL)
		lpchain = CH_NEXT(lpchain);

	// If we're now sitting on a NULL hash chain (initial condition or we
	// reached the end of a previous chain), we need to find the beginning
	// of the next chain in the hash table.
	while (iHashChain < lpsipb->wTabSize - 1 && lpchain == NULL)
		lpchain = lpsipb->HashTab[++iHashChain];
	
	if (iHashChain >= lpsipb->wTabSize - 1 && lpchain == NULL)
		return (SetErrReturn(E_OUTOFRANGE));

	*plstWord = &CH_WORD(lpchain);
	*ppvWordInfo = (LPVOID)lpchain;
	*plWordInfo = iHashChain;

	return (S_OK);
}


/*************************************************************************
 *  @doc    API RETRIEVAL
 *
 *  @func HRESULT FAR PASCAL | MVStopListFindWordPtr |
 *      Find a word in the stop list and return a pointer to it.
 *
 *  @parm LPSIPB | lpsipb |
 *      Pointer to stop-word information structure
 *
 *  @parm LST | lstWord |
 *      Pointer to a 2-byte length preceded Pascal
 *      string containing the word to find.
 *
 *  @parm LST* | plstWordInList |
 *      On exit, indirect pointer to 2-byte length preceded Pascal
 *      string for the word that was found.
 *
 *  @rdesc  S_OK if succeeded
 *	@rdesc	E_NOTFOUND if the word isn't in the stop list
 *************************************************************************/
PUBLIC HRESULT EXPORT_API FAR PASCAL MVStopListFindWordPtr(_LPSIPB lpsipb,
											LST lstWord, LST *plstWordInList)
{
	HRESULT	hr = S_OK;
	LPCHAIN	lpchain;

	if ((lpchain = MVStopListFind(lpsipb, lstWord)) != NULL)
		*(LST UNALIGNED * UNALIGNED)plstWordInList = &CH_WORD(lpchain);
	else
		hr = E_NOTFOUND;

	return (hr);
}