/*******************************************************************************
;*******************************************************************************
;**                                                                           **
;**                    COPYRIGHT 1998-2012 NUANCE COMMUNICATIONS              **
;**                                                                           **
;**               NUANCE COMMUNICATIONS PROPRIETARY INFORMATION               **
;**                                                                           **
;**     This software is supplied under the terms of a license agreement      **
;**     or non-disclosure agreement with Nuance Communications and may not    **
;**     be copied or disclosed except in accordance with the terms of that    **
;**     agreement.                                                            **
;**                                                                           **
;*******************************************************************************
;**                                                                           **
;**     FileName: et9acdb.c                                                   **
;**                                                                           **
;**  Description: Context data base access routines source file.              **
;**                                                                           **
;*******************************************************************************
;******************************************************************************/

/*! \addtogroup et9acdb Context database for alphabetic
* XT9 alphabetic context database features.
* @{
*/

#include "et9api.h"
#ifdef ET9_ALPHABETIC_MODULE
#include "et9asys.h"
#include "et9adb.h"
#include "et9acdb.h"
#include "et9amisc.h"
#include "et9sym.h"
#include "et9imu.h"
#include "et9aasdb.h"
#include "et9alsasdb.h"


#ifdef ET9_DEBUGLOG2
#ifdef _WIN32
#pragma message ("*** ET9_DEBUGLOG2 ACTIVATED ***")
#endif
#include <stdio.h>
#include <string.h>
#define WLOG2(q) { if (pLogFile2 == NULL) { pLogFile2 = fopen("zzzET9ACDB.txt", "w+"); } {q} fflush(pLogFile2);  }
static FILE *pLogFile2 = NULL;
#else
#define WLOG2(q)
#endif


#ifdef ET9_DEBUGLOG2
/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *               
 *
 *                                                 
 *                                                                       
 *                                                        
 *                                                       
 *                                                               
 *                                                  
 *
 *                                  
 */

static ET9UINT ET9LOCALCALL WLOG2String(FILE *pF, char *pcComment, ET9SYMB const * const psString, const ET9INT snLen, const ET9BOOL bReverse, const ET9BOOL bSilent)
{
    ET9INT snIndex;

    ET9UINT nLenUsed = 0;

    ET9Assert(pF);
    ET9Assert(pcComment);
    ET9Assert(psString);

    if (pcComment && *pcComment) {
        fprintf(pF, "%s: ", pcComment);
    }

    for (snIndex = bReverse ? (snLen - 1): 0; bReverse ? snIndex >= 0: snIndex < snLen; bReverse ? --snIndex : ++snIndex) {

        ET9SYMB sSymb = psString[snIndex];

        if (sSymb >= 0x20 && sSymb <= 0xFF) {
            nLenUsed += fprintf(pF, "%c", (unsigned char)sSymb);
        }
        else {
            nLenUsed += fprintf(pF, "<%x>", (int)sSymb);
        }
    }

    if (!bSilent) {
        fprintf(pF, "  (%d)\n", snLen);
    }

    fflush(pF);

#ifdef ET9_DEBUG
    {
        ET9U16 wCount = (ET9U16)snLen;
        ET9SYMB const *pSymb = psString;

        while (wCount--) {
            ET9Assert(*pSymb && *pSymb != (ET9SYMB)0xcccc);
            ++pSymb;
        }
    }
#endif

    return nLenUsed;
}
#else /* ET9_DEBUGLOG2 */
#define WLOG2String(pF,pcComment,psString,snLen,bReverse,bSilent)
#endif /* ET9_DEBUGLOG2 */

#ifdef ET9_DEBUGLOG2
/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                 
 *
 *                                                                              
 *                                                 
 *
 *             
 */

static void ET9LOCALCALL __LogCDB(ET9AWLingInfo  * const pLingInfo,
                                  FILE                  *pF)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;
    ET9AWCDBInfo ET9FARDATA * const pCDB =  pLingCmnInfo->pCDBInfo;

    if (!pCDB) {
        fprintf(pF, "\nNo CDB\n\n");
        return;
    }

    fprintf(pF, "\nCDB, size %u\n\n", pCDB->wDataSize);

    {
        ET9UINT nIndex;

        for (nIndex = 0; nIndex < ET9NLM_CONTEXT_COUNT; ++nIndex) {

            fprintf(pF, "    Context Word[%u]: %8u ", nIndex, pLingCmnInfo->Private.pdwContextWordIndexes[nIndex]);

            if (!pLingCmnInfo->Private.pContextWords[nIndex].wLen) {
                fprintf(pF, "[NONE]");
            }
            else {
                WLOG2String(pF, "", pLingCmnInfo->Private.pContextWords[nIndex].sString, pLingCmnInfo->Private.pContextWords[nIndex].wLen, 0, 1);
            }

            fprintf(pF, "\n");
        }
    }

    fprintf(pF, "\n");

    {
        ET9UINT nDelimiterCount;

        ET9INT snSearchPosition;
        ET9INT snStopPoint;

        ET9UINT nBufferLen;
        static ET9SYMB psBuffer[100000];

        nBufferLen = 0;
        nDelimiterCount = 0;

        snStopPoint = 0;
        snSearchPosition = (ET9INT)(pCDB->wDataEndOffset - (ET9U16)1 + ET9CDBDataAreaSymbs(pCDB)) % ET9CDBDataAreaSymbs(pCDB);

        for (; snSearchPosition != pCDB->wDataEndOffset;) {

            const ET9SYMB sSymb = *(ET9CDBData(pCDB) + snSearchPosition);

            if (sSymb == CDBDELIMITER) {
                ++nDelimiterCount;
            }
            else {
                nDelimiterCount = 0;
            }

            if (nDelimiterCount == 2) {

                fprintf(pF, "<<>>");
                WLOG2String(pF, "", psBuffer, nBufferLen, 1, 1);
                fprintf(pF, "\n");

                nBufferLen = 0;
            }

            if (nDelimiterCount < 2) {
                psBuffer[nBufferLen++] = sSymb;
            }

            if (!snSearchPosition) {
                snStopPoint = (ET9INT)pCDB->wDataEndOffset;
                snSearchPosition = (ET9INT)ET9CDBDataAreaSymbs(pCDB) - 1;
            }
            else {
                --snSearchPosition;
            }
        }

        if (nBufferLen) {
            WLOG2String(pF, "", psBuffer, nBufferLen, 1, 1);
            fprintf(pF, "\n");
        }

        fprintf(pF, "\n");
    }

    fflush(pF);
}
#else /* ET9_DEBUGLOG2 */
#define __LogCDB(pLingInfo,pF)
#endif /* ET9_DEBUGLOG2 */

/*----------------------------------------------------------------------------
 * Defines
 *----------------------------------------------------------------------------*/

#define BROKEN_CONTEXT_DELIMITER_COUNT  2       /* < -IDR-       */

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                      
 *
 *                                                           
 *                                                    
 *                                               
 *                                    
 *
 *             
 */

static void ET9LOCALCALL __ET9AWWriteCDBData(ET9AWLingInfo *pLingInfo,
                                        void ET9FARDATA    *pTo,
                                  const void ET9FARDATA    *pFrom,
                                             ET9UINT        nSize)
{
    /* Adjust pointers to write in reverse order */

    ET9U8 ET9FARDATA *psTo = (ET9U8 ET9FARDATA *)pTo + (nSize - 1);
    ET9U8 ET9FARDATA *psFrom = (ET9U8 ET9FARDATA *)pFrom + (nSize - 1);

    ET9Assert(pLingInfo);
    ET9Assert(pTo);
    ET9Assert(pFrom);
    ET9Assert(nSize);

    /* Have OEM write the data, if requested */

    if (pLingInfo->pCDBWriteData != NULL) {
        pLingInfo->pCDBWriteData(pLingInfo, psTo, psFrom, nSize);
    }
    /* otherwise do it as if doing RAM transfer */
    else {

#ifdef ET9_DEBUG
        ET9AWCDBInfo ET9FARDATA *pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

        ET9Assert(pCDB);
#endif

        while (nSize--) {

#ifdef ET9_DEBUG
            ET9Assert(psTo >= (ET9U8*)pCDB);
            ET9Assert(psTo <= (ET9U8*)(pCDB + (pCDB->wDataSize - 1)) || !pCDB->wDataSize);
#endif

            *psTo-- = *psFrom--;
        }
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                      
 *
 *                                                           
 *                                                       
 *
 *             
 */

static void ET9LOCALCALL __ET9AWCDBUpdateCounter(ET9AWLingInfo *pLingInfo,
                                                 ET9U8          bValue)
{
    ET9U16 wTemp = 0;

    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9Assert(pLingInfo);
    ET9Assert(pCDB != NULL);

    if (bValue) {
        wTemp = (ET9U16)(pCDB->wUpdateCounter + (ET9U16)bValue);
    }
    /* write in all cases (if 0, this will indicate reset) */

    __ET9AWWriteCDBData(pLingInfo,
                        (void ET9FARDATA *)&pCDB->wUpdateCounter,
                        (const void ET9FARDATA *)&wTemp, sizeof(ET9U16));
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                  
 *
 *                                                           
 *
 *             
 */

static void ET9LOCALCALL __ET9AWFillCDBWithDelims(ET9AWLingInfo *pLingInfo)
{
    ET9SYMB ET9FARDATA *psNext;
    ET9UINT nIndex;
    const ET9SYMB sDelimiter = CDBDELIMITER;

    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9Assert(pLingInfo);
    ET9Assert(pCDB != NULL);

    psNext = ET9CDBData(pCDB);

    /* Loop through all of the data area symbols */

    for (nIndex = 0; nIndex < ET9CDBDataAreaSymbs(pCDB); ++nIndex, ++psNext) {
        __ET9AWWriteCDBData(pLingInfo,
                            (void ET9FARDATA *)psNext,
                            (const void ET9FARDATA *)&sDelimiter,
                            sizeof(ET9SYMB));
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                  
 *
 *                                                               
 *                                      
 *                                         
 *
 *             
 */

static void ET9LOCALCALL __ET9AWFillCDBPortionWithDelims(ET9AWLingInfo *pLingInfo, ET9UINT nStartOffset, ET9UINT nPortionLength)
{
    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9UINT nIndex;

    ET9SYMB ET9FARDATA *psStart;
    ET9SYMB ET9FARDATA *psNext;

    ET9Assert(pCDB);
    ET9Assert(pLingInfo);

    if (nStartOffset >= ET9CDBDataAreaSymbs(pCDB)) {
        return;
    }

    psStart = ET9CDBData(pCDB);

    /* Loop through all of the data area symbols */

    for (nIndex = 0, psNext = psStart + nStartOffset; nIndex < nPortionLength; ++nIndex) {

        const ET9SYMB sSymb = CDBDELIMITER;

        __ET9AWWriteCDBData(pLingInfo,
                            psNext,
                            &sSymb,
                            (ET9UINT)sizeof(ET9SYMB));

        psNext = psStart + (nStartOffset + nIndex + 1) % ET9CDBDataAreaSymbs(pCDB);
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                 
 *
 *                                                          
 *                                                   
 *                                                           
 *
 *             
 */

static void ET9LOCALCALL __ET9AWAddTextToCDB(ET9AWLingInfo          * const pLingInfo,
                                             ET9SYMB          const * const psBuf,
                                             const ET9UINT                  nBufLen)
{
    ET9U16 wTemp;
    ET9U16 wHolder;
    const ET9SYMB sTemp = CDBDELIMITER;
    ET9SYMB ET9FARDATA *psNext;
    ET9SYMB ET9FARDATA *psStart;

    ET9SYMB const *psCurr = psBuf;
    ET9UINT nRemaining = nBufLen;

    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9Assert(pLingInfo);
    ET9Assert(pCDB != NULL);
    ET9Assert(psBuf);
    ET9Assert(nBufLen);
    ET9Assert(nBufLen < ET9CDBDataAreaSymbs(pCDB));

    if (nBufLen == 1 && psBuf[0] == CDBDELIMITER) {
        WLOG2(fprintf(pLogFile2, "__ET9AWAddTextToCDB: <>\n");)
    }
    else {
        WLOG2String(pLogFile2, "__ET9AWAddTextToCDB", psBuf, nBufLen, 0, 1);
        WLOG2(fprintf(pLogFile2, "\n");)
    }

    /* get the end address of write (accounting for possible wrap-around) */

    wTemp = (ET9U16)((pCDB->wDataEndOffset + nBufLen) % ET9CDBDataAreaSymbs(pCDB));

    /* get the start address of the write (end of previous write) */

    psNext = ET9CDBData(pCDB) + pCDB->wDataEndOffset;

    wHolder = (ET9U16)(ET9CDBDataAreaSymbs(pCDB) - pCDB->wDataEndOffset);

    /* if the string requires wrap-around in CDB data area */

    if (nRemaining > wHolder) {

        /* write as much of the string that fits prior to wrap */
        /* decrement the size of the remaining data to be written */

        nRemaining -= wHolder;

        while (wHolder--) {

            ET9Assert(psNext < ET9CDBData(pCDB) + ET9CDBDataAreaSymbs(pCDB));

            __ET9AWWriteCDBData(pLingInfo,
                                psNext++,
                                psCurr++,
                                (ET9UINT)sizeof(ET9SYMB));
        }

        /* and start the next write at the beginning of the data area */

        psNext = ET9CDBData(pCDB);
    }

    /* now write the string (either whole or remaining wrap-around portion) */

    psStart = psNext;

    while (nRemaining--) {

        ET9Assert(psNext < ET9CDBData(pCDB) + ET9CDBDataAreaSymbs(pCDB));

        __ET9AWWriteCDBData(pLingInfo,
                           psNext++,
                           psCurr++,
                           (ET9UINT)sizeof(ET9SYMB));
    }

    /* increment update counter */

    __ET9AWCDBUpdateCounter(pLingInfo, 1);

    /* this write may have overwritten an older CDB entry; check to */
    /* remove any leftover characters from older entry              */
    /* save original address to prevent tight loop */
    /* check for wrap... psNext may be right at the end of the data area */

    if (psNext >= ET9CDBData(pCDB) + ET9CDBDataAreaSymbs(pCDB)) {
        psNext = psNext - ET9CDBDataAreaSymbs(pCDB);
    }

    /* clear the leftover symbols of the older entry (if there are any ) */

    while (*psNext != CDBDELIMITER && psNext != psStart) {

        __ET9AWWriteCDBData(pLingInfo, (void ET9FARDATA *)psNext, &sTemp, (ET9UINT)sizeof(ET9SYMB));

        /* account for possible wrap-around */

        if (++psNext >= ET9CDBData(pCDB) + ET9CDBDataAreaSymbs(pCDB)) {
            psNext = ET9CDBData(pCDB);
        }
    }

    /* write the new data end offset */

    __ET9AWWriteCDBData(pLingInfo, (void ET9FARDATA *)&pCDB->wDataEndOffset, &wTemp, (ET9UINT)sizeof(ET9U16));

    /* increment update counter */

    __ET9AWCDBUpdateCounter(pLingInfo, 1);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                       
 *
 *                                                          
 *                                   
 *                                                        
 *
 *                                           
 */

static ET9BOOL ET9LOCALCALL __IsLastStringEntered(ET9AWLingInfo         * const pLingInfo,
                                                  ET9SYMB         const * const psString,
                                                  const ET9UINT                 nStringLen)
{
    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    const ET9BOOL bIsBilingual = (ET9BOOL)ET9AW_GetBilingualSupported(pLingInfo);

    ET9UINT nLen;
    ET9UINT nOffsetOfSymb;

    ET9Assert(pLingInfo);
    ET9Assert(psString);
    ET9Assert(pCDB);
    ET9Assert(nStringLen);

    WLOG2String(pLogFile2, "__IsLastStringEntered", psString, nStringLen, 0, 1);
    WLOG2(fprintf(pLogFile2, "\n");)

    nOffsetOfSymb = (ET9U16)((pCDB->wDataEndOffset - (ET9U16)1 + ET9CDBDataAreaSymbs(pCDB)) % ET9CDBDataAreaSymbs(pCDB));

    /* loop through, comparing CDB entry with passed word */

    for (nLen = nStringLen; nLen--; ) {

        {
            const ET9SYMB sStringSymb = *(psString + nLen);
            const ET9SYMB sCdbSymb = *(ET9CDBData(pCDB) + nOffsetOfSymb);

            WLOG2(fprintf(pLogFile2, "%03u: sStringSymb %c sCdbSymb %c\n", nLen, (char)sStringSymb, (char)sCdbSymb);)

            /* if the syms don't match, get outta here with failure indication */

            if (sCdbSymb == sStringSymb) {
            }
            else if (bIsBilingual) {

                if ((_ET9SymToLower(sCdbSymb, pLingInfo->pLingCmnInfo->dwFirstLdbNum) != sStringSymb) &&
                    (_ET9SymToLower(sCdbSymb, pLingInfo->pLingCmnInfo->dwSecondLdbNum) != sStringSymb)) {

                    WLOG2(fprintf(pLogFile2, "  no match\n");)
                    return 0;
                }
            }
            else if (_ET9SymToLower(sCdbSymb, pLingInfo->pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale) != sStringSymb) {

                WLOG2(fprintf(pLogFile2, "  no match\n");)
                return 0;
            }

            nOffsetOfSymb = nOffsetOfSymb ? (ET9UINT) (nOffsetOfSymb - 1) : (ET9UINT) (ET9CDBDataAreaSymbs(pCDB) - 1);
        }
    }

    WLOG2(fprintf(pLogFile2, "  match\n");)

    return 1;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                          
 *
 *                                                           
 *                                                 
 *
 *                                                                 
 */

static ET9UINT ET9LOCALCALL __GetNumCDBTrailingDelims(ET9AWLingInfo    * const pLingInfo,
                                                      const ET9UINT            nTargetCount)
{
    ET9U16  wCharPos;
    ET9UINT nNumDelims = 0;

    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9Assert(pLingInfo);
    ET9Assert(pCDB != NULL);

    wCharPos = (ET9U16)((pCDB->wDataEndOffset + ET9CDBDataAreaSymbs(pCDB) - 1) % ET9CDBDataAreaSymbs(pCDB));

    while (nNumDelims < nTargetCount) {

        if (*(ET9CDBData(pCDB) + wCharPos) != CDBDELIMITER) {
            break;
        }

        ++nNumDelims;

        wCharPos = (ET9U16)((wCharPos + ET9CDBDataAreaSymbs(pCDB) - 1) % ET9CDBDataAreaSymbs(pCDB));
    }

    return nNumDelims;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                  
 *
 *                                                           
 *                                      
 *
 *                                                                 
 */

static void ET9LOCALCALL __AssureTrailingDelimCount(ET9AWLingInfo    * const pLingInfo,
                                                    const ET9UINT            nTargetCount)
{
    const ET9SYMB sSpaceHolder = CDBDELIMITER;

    ET9UINT nCurrDelimCount;

    for (nCurrDelimCount = __GetNumCDBTrailingDelims(pLingInfo, nTargetCount); nCurrDelimCount < nTargetCount; ++nCurrDelimCount) {
        __ET9AWAddTextToCDB(pLingInfo, &sSpaceHolder, 1);
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                 
 *
 *                                                          
 *
 *             
 */

static void ET9LOCALCALL __BreakContext(ET9AWLingInfo * const pLingInfo)
{
    ET9Assert(pLingInfo);

    if (!pLingInfo->pLingCmnInfo->pCDBInfo) {
        return;
    }

    WLOG2(fprintf(pLogFile2, "__BreakContext\n");)

    __AssureTrailingDelimCount(pLingInfo, BROKEN_CONTEXT_DELIMITER_COUNT);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *       /                              
 *
 *                                                          
 *
 *             
 */

static ET9BOOL ET9LOCALCALL __IsValidCdbContext(ET9AWLingInfo * const pLingInfo)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

    ET9UINT nLen;
    ET9SYMB psString[ET9NLM_CONTEXT_COUNT * ET9MAXWORDSIZE + (ET9NLM_CONTEXT_COUNT + 1)];

    ET9INT snIndex;

    nLen = 0;

    psString[nLen++] = CDBDELIMITER;

    for (snIndex = ET9NLM_CONTEXT_COUNT - 1; snIndex >= 0; --snIndex) {

        if (pLingCmnInfo->Private.pContextWords[snIndex].wLen) {
            _ET9SymCopy(&psString[nLen], pLingCmnInfo->Private.pContextWords[snIndex].sString, pLingCmnInfo->Private.pContextWords[snIndex].wLen);
            nLen += pLingCmnInfo->Private.pContextWords[snIndex].wLen;
            psString[nLen++] = CDBDELIMITER;
        }
    }

    if (nLen == 1) {
        psString[nLen++] = CDBDELIMITER;
    }

    {
        const ET9BOOL bMatch = __IsLastStringEntered(pLingInfo, psString, nLen);

        WLOG2(fprintf(pLogFile2, "__IsValidCdbContext, %c\n", (bMatch ? 'Y' : 'N'));)

        return bMatch;
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *       /                              
 *
 *                                                          
 *
 *             
 */

static void ET9LOCALCALL __AssureCdbContext(ET9AWLingInfo * const pLingInfo)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

    if (!pLingInfo->pLingCmnInfo->pCDBInfo || !ET9CDBENABLED(pLingInfo->pLingCmnInfo)) {
        return;
    }

    WLOG2(fprintf(pLogFile2, "__AssureCdbContext\n");)

    if (!pLingCmnInfo->Private.pContextWords[0].wLen) {

        __AssureTrailingDelimCount(pLingInfo, BROKEN_CONTEXT_DELIMITER_COUNT);
    }
    else if (!__IsValidCdbContext(pLingInfo)) {

        ET9BOOL bStarted = 0;

        ET9INT snIndex;

        __AssureTrailingDelimCount(pLingInfo, BROKEN_CONTEXT_DELIMITER_COUNT);

        for (snIndex = ET9NLM_CONTEXT_COUNT - 1; snIndex >= 0; --snIndex) {

            if (bStarted) {
                ET9Assert(pLingCmnInfo->Private.pContextWords[snIndex].wLen);
            }

            if (pLingCmnInfo->Private.pContextWords[snIndex].wLen) {

                bStarted = 1;

                __ET9AWAddTextToCDB(pLingInfo, pLingCmnInfo->Private.pContextWords[snIndex].sString, pLingCmnInfo->Private.pContextWords[snIndex].wLen);
                __AssureTrailingDelimCount(pLingInfo, 1);
            }
        }
    }

    __LogCDB(pLingInfo, pLogFile2);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                   
 *
 *                                                          
 *                                                         
 *
 *                                                       
 */

static void ET9LOCALCALL __GetLastWordEntered(ET9AWLingInfo     * const pLingInfo,
                                              ET9AWPrivWordInfo *const pWord)
{
    ET9UINT nOffsetOfSymb;
    ET9U16  i = 0;
    ET9U16  j = 0;
    ET9SYMB sSym;

    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

    ET9Assert(pLingInfo);
    ET9Assert(pWord);
    ET9Assert(pCDB != NULL);

    WLOG2(fprintf(pLogFile2, "__GetLastWordEntered\n");)

    pWord->Base.wWordLen = 0;
    nOffsetOfSymb = (ET9U16)((pCDB->wDataEndOffset - (ET9U16)1 + ET9CDBDataAreaSymbs(pCDB)) % ET9CDBDataAreaSymbs(pCDB));

    if (*(ET9CDBData(pCDB) + nOffsetOfSymb) == CDBDELIMITER) {

        nOffsetOfSymb = nOffsetOfSymb ? (ET9UINT) (nOffsetOfSymb - 1) : (ET9UINT) (ET9CDBDataAreaSymbs(pCDB) - 1);

        /* loop through, comparing CDB entry with passed word */

        while (*(ET9CDBData(pCDB) + nOffsetOfSymb) != CDBDELIMITER) {

            pWord->Base.sWord[i++] = *(ET9CDBData(pCDB) + nOffsetOfSymb);
            nOffsetOfSymb = nOffsetOfSymb ? (ET9UINT) (nOffsetOfSymb - 1) : (ET9UINT) (ET9CDBDataAreaSymbs(pCDB) - 1);
        }

        if (i) {

            pWord->Base.wWordLen = i;
            --i;

            for (; j < i; ++j, --i) {
                sSym = pWord->Base.sWord[j];
                pWord->Base.sWord[j] = pWord->Base.sWord[i];
                pWord->Base.sWord[i] = sSym;
            }
        }
    }

    return;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                       
 *
 *                                                          
 *                                      
 *
 *             
 */

static void ET9LOCALCALL __CDBUpdateContext(ET9AWLingInfo     * const pLingInfo,
                                            ET9AWPrivWordInfo * const pWord)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

    ET9INT snContextIndex;

    ET9Assert(pLingInfo);
    ET9Assert(pLingCmnInfo);
    ET9Assert(pWord);
    ET9Assert(pWord->Base.wWordLen);
    ET9Assert(pWord->Base.wWordLen <= ET9MAXWORDSIZE);

    WLOG2(fprintf(pLogFile2, "__CDBUpdateContext\n");)

    for (snContextIndex = ET9NLM_CONTEXT_COUNT - 1; snContextIndex > 0; --snContextIndex) {
        pLingCmnInfo->Private.pContextWords[snContextIndex] = pLingCmnInfo->Private.pContextWords[snContextIndex - 1];
    }

    pLingCmnInfo->Private.pContextWords[snContextIndex].wLen = pWord->Base.wWordLen;
    _ET9SymCopy(pLingCmnInfo->Private.pContextWords[snContextIndex].sString, pWord->Base.sWord, pLingCmnInfo->Private.pContextWords[snContextIndex].wLen);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                         
 *                                                                                      
 *
 *                                                          
 *                                      
 *
 *             
 */

static void ET9LOCALCALL __AddToContextDatabase(ET9AWLingInfo           * const pLingInfo,
                                                ET9AWPrivWordInfo const * const pWord)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

    ET9AWPrivWordInfo sWord;

    ET9Assert(pLingInfo);
    ET9Assert(pWord);

    WLOG2(fprintf(pLogFile2, "__AddToContextDatabase\n");)

    /* CDB active? */

    if (!pLingInfo->pLingCmnInfo->pCDBInfo || !ET9CDBENABLED(pLingInfo->pLingCmnInfo)) {
        return;
    }

    /* need local copy since the content might get updated */

    sWord = *pWord;

    /* in most cases the cdb context should match the current context - inconsistency relates to shortcuts */

    if (!__IsValidCdbContext(pLingInfo)) {
        WLOG2(fprintf(pLogFile2, "  *** unexpected context\n");)
    }

    /* add the passed word to the CDB */
    /* if word exists as ASDB or LDB-AS entry, get original version */

    if (!_ET9AWFindASDBObject(pLingInfo, sWord.Base.sWord, sWord.Base.wWordLen, 1, 1)) {
        if (!_ET9AWFindLdbASObject(pLingInfo, pLingCmnInfo->dwFirstLdbNum, sWord.Base.sWord, sWord.Base.wWordLen, 1, 1)) {
            if (ET9AW_GetBilingualSupported(pLingInfo)) {
                _ET9AWFindLdbASObject(pLingInfo, pLingCmnInfo->dwSecondLdbNum, sWord.Base.sWord, sWord.Base.wWordLen, 1, 1);
            }
        }
    }

    __ET9AWAddTextToCDB(pLingInfo, sWord.Base.sWord, sWord.Base.wWordLen);

    __AssureTrailingDelimCount(pLingInfo, 1);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                             
 *
 *                                                          
 *                                      
 *
 *             
 */

void ET9FARCALL _ET9AWCDBAddWord(ET9AWLingInfo     * const pLingInfo,
                                 ET9AWPrivWordInfo * const pWord)
{
    ET9Assert(pLingInfo);
    ET9Assert(pWord);
    ET9Assert(pWord->Base.wWordLen);

    WLOG2String(pLogFile2, "_ET9AWCDBAddWord", pWord->Base.sWord, pWord->Base.wWordLen, 0, 1);
    WLOG2(fprintf(pLogFile2, "\n");)

    __AddToContextDatabase(pLingInfo, pWord);
    __CDBUpdateContext(pLingInfo, pWord);

    __LogCDB(pLingInfo, pLogFile2);
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                 
 *
 *                                                          
 *                                      
 *
 *             
 */

void ET9FARCALL _ET9AWCDBAddShortcut(ET9AWLingInfo           * const pLingInfo,
                                     ET9AWPrivWordInfo const * const pWord)
{
    ET9AWPrivWordInfo pWord2;

    ET9Assert(pLingInfo);
    ET9Assert(pWord);
    ET9Assert(pWord->Base.wWordLen);

    if (!pLingInfo->pLingCmnInfo->pCDBInfo || !ET9CDBENABLED(pLingInfo->pLingCmnInfo)) {
        return;
    }

    WLOG2(fprintf(pLogFile2, "_ET9AWCDBAddShortcut\n");)

    __GetLastWordEntered(pLingInfo, &pWord2);

    if (pWord2.Base.wWordLen) {

        WLOG2(fprintf(pLogFile2, "_ET9AWCDBAddShortcut, adding shortcut\n");)

        __AddToContextDatabase(pLingInfo, pWord);
        __BreakContext(pLingInfo);
        __AddToContextDatabase(pLingInfo, &pWord2);

        __LogCDB(pLingInfo, pLogFile2);
    }
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                                                                
 *
 *                                                                
 *                                                                   
 *                                                   
 *                                                                    
 *
 *                                                                    
 */

ET9STATUS ET9FARCALL _ET9AWCDBWordsSearch(ET9AWLingInfo        * const pLingInfo,
                                          const ET9U16                 wIndex,
                                          const ET9U16                 wLength,
                                          const ET9_FREQ_DESIGNATION   bFreqIndicator)
{
    ET9STATUS               wStatus = ET9STATUS_NONE;
    ET9UINT                 nCDBWordLen;
    ET9UINT                 nMatch;
    ET9UINT                 nNextCharPos;
    ET9UINT                 nCDBWordLen2;
    ET9UINT                 nTrigramMatch;
    ET9UINT                 nSize = 0;
    ET9UINT                 nNumTested = 0;
    ET9UINT                 nNumWhite = 0;
    ET9UINT                 nNumTrailingPunct = 0;
    ET9UINT                 nNumLeadingPunct = 0;
    ET9UINT                 nNumNumerics = 0;
    ET9UINT                 nIndex = 0;
    ET9UINT                 nContextWordSize;
    ET9UINT                 nPreviousContextWordSize;
    ET9INT                  snSearchPosition2;
    ET9UINT                 nWordEnd = 0;
    ET9UINT                 nWordStart = 0;
    ET9UINT                 nRHWEnd = 0;
    ET9UINT                 nRHWStart = 0;
    ET9UINT                 nRHWLength;
    ET9INT                  snSearchPosition;
    ET9INT                  snStopPoint = 0;
    ET9U8                   aCharClass;
    ET9SYMB                 sLastWord[ET9MAXWORDSIZE];
    ET9U16                  wLastWordLen = 0;
    ET9SYMB                 *pStr1;
    ET9SYMB                 *pStr2;
    ET9UINT                 i;
    ET9AWPrivWordInfo       sLookedUpWord;

    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;
    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingCmnInfo->pCDBInfo;

    const ET9U16 wCurrMinSourceLength = pLingCmnInfo->Private.wCurrMinSourceLength;

    ET9Assert(pLingInfo);

    WLOG2(fprintf(pLogFile2, "_ET9AWCDBWordsSearch\n");)

    /* if no context database or not active */

    if (!pCDB ||
        !ET9CDBENABLED(pLingInfo->pLingCmnInfo) ||
        !ET9CONTEXTBASEDPREDICTION(pLingInfo->pLingCmnInfo)) {

        return ET9STATUS_NONE;
    }

    nContextWordSize = (ET9UINT)pLingCmnInfo->Private.pContextWords[0].wLen;
    nPreviousContextWordSize = (ET9UINT)pLingCmnInfo->Private.pContextWords[1].wLen;

    /* IF no context word exists OR active word segment doesn't start at */
    /* beginning of the active word, no need to search                   */

    if (!nContextWordSize || wIndex) {
        return ET9STATUS_NONE;
    }

    /* if trying to get prediction and turned off, leave */

    if (!wLength && !ET9NEXTWORDPREDICTION_MODE(pLingCmnInfo)) {
        return ET9STATUS_NONE;
    }

    /* if there is trailing punct in the context word, */
    /* and it doesn't look like an emoticon, return    */

    if (_ET9_IsPunctChar(pLingCmnInfo->Private.pContextWords[0].sString[nContextWordSize - 1])) {
        if (!_ET9StringLikelyEmoticon(pLingCmnInfo->Private.pContextWords[0].sString, nContextWordSize)) {
            return ET9STATUS_NONE;
        }
    }

    snSearchPosition = (ET9INT)(pCDB->wDataEndOffset - (ET9U16)1 + ET9CDBDataAreaSymbs(pCDB)) % ET9CDBDataAreaSymbs(pCDB);

    /* search the entire CDB, working backwards from the most recent entry */

    while (snSearchPosition >= snStopPoint) {

        nCDBWordLen = 0;
        nMatch = 0;
        nTrigramMatch = 0;

        /* if the current symbol is a delimiter, continue */

        if (*(ET9CDBData(pCDB) + snSearchPosition) == CDBDELIMITER) {
            ++nNumWhite;
        }

        /* search to see if this is context word, and hold pointers to beginning and end  */

        else {

            /* assume match if context not broken after word */

            nMatch = (nNumWhite == 1) ? 1: 0;
            nWordEnd = (ET9UINT)snSearchPosition;

            /* this loops through the word that was found to see if it matches current word */

            nIndex = ET9CDBDataAreaSymbs(pCDB);
            while (nIndex--) {

                /* if the symbol is a delimiter... */

                if (*(ET9CDBData(pCDB) + snSearchPosition) == CDBDELIMITER) {

                    /* if the db word and the context word have different sizes */

                    if (nCDBWordLen != nContextWordSize) {

                        /* no possible match */

                        nMatch = 0;
                    }
                    break;
                }
                /* if db word appears to be larger than context word... */

                if (nMatch && (nCDBWordLen >= nContextWordSize)) {

                    /* no possible match */

                    nMatch = 0;
                }
                /* if a symbol mismatch occurs comparing db word and context word */

                if (ET9AW_GetBilingualSupported(pLingInfo)) {
                    if (nMatch) {
                        if (nContextWordSize == (nCDBWordLen + 1)) {
                            if ((_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->dwFirstLdbNum) != _ET9SymToLower(pLingCmnInfo->Private.pContextWords[0].sString[0], pLingCmnInfo->dwFirstLdbNum)) &&
                                (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->dwSecondLdbNum) != _ET9SymToLower(pLingCmnInfo->Private.pContextWords[0].sString[0], pLingCmnInfo->dwSecondLdbNum))) {

                                /* no possible match */

                                nMatch = 0;
                            }
                        }
                        else {
                            if (*(ET9CDBData(pCDB) + snSearchPosition) != pLingCmnInfo->Private.pContextWords[0].sString[nContextWordSize - 1 - nCDBWordLen]) {

                                /* no possible match */

                                nMatch = 0;
                            }
                        }
                    }
                }
                else if (nMatch) {
                    if (nContextWordSize == (nCDBWordLen + 1)) {
                        if (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale) != _ET9SymToLower(pLingCmnInfo->Private.pContextWords[0].sString[0], pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale)) {

                            /* no possible match */

                            nMatch = 0;
                        }
                    }
                    else {
                        if (*(ET9CDBData(pCDB) + snSearchPosition) != pLingCmnInfo->Private.pContextWords[0].sString[nContextWordSize - 1 - nCDBWordLen]) {

                            /* no possible match */

                            nMatch = 0;
                        }
                    }
                }

                /* set wordstart to current position so it will be set correctly when we're done */

                nWordStart = snSearchPosition;

                if (!snSearchPosition) {

                    if (!pCDB->wDataEndOffset) {
                        break;
                    }

                    snStopPoint = (ET9INT)pCDB->wDataEndOffset;
                    snSearchPosition = (ET9INT)ET9CDBDataAreaSymbs(pCDB) - 1;
                }
                else {
                    --snSearchPosition;
                }
                ++nCDBWordLen;
            }

            ++nNumTested;

            /* if context match, check previous context entry */

            if (nMatch && nPreviousContextWordSize) {

                nTrigramMatch = 1;

                /* loops through the word before the one that was found to see if it matches previous context word */

                nIndex = ET9CDBDataAreaSymbs(pCDB);
                nCDBWordLen2 = 0;

                if (!snSearchPosition) {

                   snSearchPosition2 = (ET9INT)ET9CDBDataAreaSymbs(pCDB) - 1;

                   if (!pCDB->wDataEndOffset) {
                        nTrigramMatch = 0;
                        nIndex = 0;
                    }
                }
                else {
                    snSearchPosition2 = snSearchPosition - 1;
                }

                /* search for length of previous context word */

                while (nIndex--) {

                    /* if the symbol is a delimiter... */

                    if (*(ET9CDBData(pCDB) + snSearchPosition2) == CDBDELIMITER) {

                        /* if the db word and the context word have different sizes */

                        if (nCDBWordLen2 != nPreviousContextWordSize) {

                            /* no possible match */

                            nTrigramMatch = 0;
                        }
                        break;
                    }

                    /* if db word appears to be larger than context word... */

                    if (nTrigramMatch && (nCDBWordLen2 >= nPreviousContextWordSize)) {

                        /* no possible match */

                        nTrigramMatch = 0;
                        break;
                    }

                    /* if a symbol mismatch occurs comparing db word and context word */

                    if (ET9AW_GetBilingualSupported(pLingInfo)) {
                        if (nTrigramMatch) {
                            if (nPreviousContextWordSize == (nCDBWordLen2 + 1)) {
                                if ((_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition2), pLingCmnInfo->dwFirstLdbNum) != _ET9SymToLower(pLingCmnInfo->Private.pContextWords[1].sString[0], pLingCmnInfo->dwFirstLdbNum)) &&
                                    (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition2), pLingCmnInfo->dwSecondLdbNum) != _ET9SymToLower(pLingCmnInfo->Private.pContextWords[1].sString[0], pLingCmnInfo->dwSecondLdbNum))) {

                                    /* no possible match */

                                    nTrigramMatch = 0;
                                    break;
                                }
                            }
                            else {
                                if (*(ET9CDBData(pCDB) + snSearchPosition2) != pLingCmnInfo->Private.pContextWords[1].sString[nPreviousContextWordSize - 1 - nCDBWordLen2]) {

                                    /* no possible match */

                                    nTrigramMatch = 0;
                                    break;
                                }
                            }
                        }
                    }
                    else if (nTrigramMatch) {
                        if (nPreviousContextWordSize == (nCDBWordLen2 + 1)) {
                            if (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition2), pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale) !=
                                _ET9SymToLower(pLingCmnInfo->Private.pContextWords[1].sString[0], pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale)) {

                                /* no possible match */

                                nTrigramMatch = 0;
                                break;
                            }
                        }
                        else {
                            if (*(ET9CDBData(pCDB) + snSearchPosition2) != pLingCmnInfo->Private.pContextWords[1].sString[nPreviousContextWordSize - 1 - nCDBWordLen2]) {

                                /* no possible match */

                                nTrigramMatch = 0;
                                break;
                            }
                        }
                    }

                    if (!snSearchPosition2) {

                        if (!pCDB->wDataEndOffset) {
                            break;
                        }

                        snSearchPosition2 = (ET9INT)ET9CDBDataAreaSymbs(pCDB) - 1;
                    }
                    else {
                        --snSearchPosition2;
                    }

                    ++nCDBWordLen2;
                }
            }
        }

        /* okay, we've found a match, lets see if the following word matches active word */

        aCharClass = ET9SYMUNKNMASK;

        if (nMatch) {

            /* check to see if following word matches currently active sequence */

            nRHWLength = (nRHWEnd + 1 + ET9CDBDataAreaSymbs(pCDB) - nRHWStart) % ET9CDBDataAreaSymbs(pCDB);

            if ((nNumTested <= 1) || (nRHWLength < wCurrMinSourceLength)) {
                nMatch = 0;
            }
            else {

                ET9U8 aClass;
                ET9U8 aKey;

                _InitPrivWordInfo(&sLookedUpWord);

                nSize = 0;
                nNumTrailingPunct = nNumLeadingPunct = nNumNumerics = 0;

                while (nSize < ET9MAXWORDSIZE && nSize < nRHWLength) {

                    nNextCharPos = (nRHWStart + nSize) % ET9CDBDataAreaSymbs(pCDB);
                    sLookedUpWord.Base.sWord[nSize] = *(ET9CDBData(pCDB) + nNextCharPos);
                    ++sLookedUpWord.Base.wWordLen;
                    _ET9_GetFullSymbolKeyAndClass(sLookedUpWord.Base.sWord[nSize], &aKey, &aClass);

                    if (nSize >= wLength) { /* this appears outdated */

                        if (aClass == ET9SYMPUNCTMASK) {  /* punct case */
                            if (nNumLeadingPunct == nSize) {
                                ++nNumLeadingPunct;
                            }
                            else {
                                ++nNumTrailingPunct;
                            }
                        }
                        else {
                            if (aClass == ET9SYMNUMBRMASK) {
                                ++nNumNumerics;
                            }
                            nNumTrailingPunct = 0;
                        }
                    }
                    ++nSize;

                    /* make sure we're not checking off the end */

                    if (((nNextCharPos + 1) % ET9CDBDataAreaSymbs(pCDB)) == pCDB->wDataEndOffset) {
                        nMatch = 0;
                        break;
                    }
                    aCharClass = aClass;
                }
            }
        }

        /* if there is a match so far, we still want to do the following processing:

            a) if the word HAS NO leading punct, we will strip trailing punct unless this
                looks like an emoticon (there are more non-alpha than there are alpha
                characters in the word).
            b) if the word HAS leading punct,  we will reject the
                word unless it looks like an emoticon.
        */

#if 0
        WLOG2(fprintf(pLogFile2, "  nMatch %u\n", nMatch);)
#endif

        if (nMatch) {

            /* doesn' look like emoticon */

            if ((2 * (nNumTrailingPunct + nNumLeadingPunct + nNumNumerics)) < nSize) {

                if (nSize != wLength) { /* this appears outdated */

                    if (!nNumLeadingPunct) {
                        nSize -= nNumTrailingPunct;
                        if (nSize < wCurrMinSourceLength) {
                            nMatch = 0;
                        }
                        else {
                            sLookedUpWord.Base.wWordLen = (ET9U16)nSize;
                        }
                    }
                    else {
                        nMatch = 0;
                    }
                }

            }

            /* looks like emoticon. */

            else if (nSize == 1 && aCharClass == ET9SYMPUNCTMASK) {  /* don't allow single character emoticons */
                nMatch = 0;
            }

            /* if completion not on, reject here if not right size */

            if (!ET9WORDCOMPLETION_MODE(pLingCmnInfo) &&
                wLength && nSize != wLength) { /* this appears outdated */
                nMatch = 0;
            }
        }

        /* if key match also, deal with match */

        if (nMatch) {

            sLookedUpWord.Body.bIsCDBTrigram = (ET9BOOL)nTrigramMatch;
            sLookedUpWord.Body.bWordSrc = ET9WORDSRC_CDB;

            if (ET9AW_GetBilingualSupported(pLingInfo)) {
                sLookedUpWord.Body.bLangIndexScoring = ET9AWBOTH_LANGUAGES;
            }
            else {
                sLookedUpWord.Body.bLangIndexScoring = ET9AWFIRST_LANGUAGE;
            }

            sLookedUpWord.Base.bLangIndex = 0xcc;

            sLookedUpWord.Body.bEditDistSpc = 0;
            sLookedUpWord.Body.bEditDistStem = 0;

            /* the following assigns a frequency that keeps the CDB */
            /* entries in the order they are picked from the CDB    */

            sLookedUpWord.Body.xWordFreq = (ET9FREQPART)(ET9MAXCOLLECTSIZE - pLingCmnInfo->Private.sWordC.pCurrC->nTotalWords);

            if (pLingCmnInfo->Private.sWordC.pCurrC->eCurrSelectionListMode == ET9ASLMODE_MIXED) {
                sLookedUpWord.Body.xWordFreq = (ET9FREQPART)(sLookedUpWord.Body.xWordFreq * 100 + ET9_SUPP_DB_BASE_FREQ + (nTrigramMatch ? ET9_SUPP_DB_FREQ_BUMP_COUNT : 0));
            }

            sLookedUpWord.Body.xTapFreq = 1;

#ifdef ET9_DEBUG
            sLookedUpWord.Body.sScoreContext = pLingInfo->pLingCmnInfo->Private.sCurrContext;
#endif

            /* if not a prediction */

            if (wLength) {

                /* if word being compounded, downshift it */

                if (wIndex) {

                    ET9SYMB *pSymb = sLookedUpWord.Base.sWord;
                    ET9UINT i = sLookedUpWord.Base.wWordLen;

                    for (; i; --i, ++pSymb) {
                        *pSymb = _ET9SymToLower(*pSymb, pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale);
                    }
                }

                wStatus = _ET9AWSelLstWordSearch(pLingInfo, &sLookedUpWord, wIndex, wLength, bFreqIndicator);

                if (wStatus != ET9STATUS_NONE) {
                    return wStatus;
                }
            }

            /* otherwise, just add to list */

            else {

                /* make sure word is not a shortcut... shortcuts should never be submitted as NWPs */

                wStatus = ET9AWASDBFindEntry(pLingInfo,
                                             sLookedUpWord.Base.sWord,
                                             sLookedUpWord.Base.wWordLen,
                                             sLookedUpWord.Base.sSubstitution,
                                             ET9MAXWORDSIZE,
                                             &sLookedUpWord.Base.wSubstitutionLen);

                if (wStatus == ET9STATUS_NO_MATCHING_WORDS) {

                    WLOG2String(pLogFile2, "  adding-1", sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen, 0, 1);
                    WLOG2(fprintf(pLogFile2, "\n");)

                    wStatus = _ET9AWSelLstAdd(pLingInfo,
                                              &sLookedUpWord,
                                              wLength,
                                              bFreqIndicator);

                    _ET9SymCopy(sLastWord, sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen);

                    wLastWordLen = sLookedUpWord.Base.wWordLen;

                    /* if first NWP (which is default) and 'downshift default' option is on */
                    /*  and word is uppercase (without shift), add lowercase version        */

                    if (pLingCmnInfo->Private.sWordC.pCurrC->nTotalWords == 1 &&
                        ET9DOWNSHIFTDEFAULTENABLED(pLingCmnInfo) &&
                        !ET9SHIFT_MODE(pLingCmnInfo->Base.pWordSymbInfo->dwStateBits) &&
                        !ET9CAPS_MODE(pLingCmnInfo->Base.pWordSymbInfo->dwStateBits) &&
                        ET9SymIsUpper(sLookedUpWord.Base.sWord[0], 0)) {

                        ET9U8 bIndex;

                        for (bIndex = 0; bIndex < sLookedUpWord.Base.wWordLen; ++bIndex) {
                            sLookedUpWord.Base.sWord[bIndex] = _ET9SymToLower(sLookedUpWord.Base.sWord[bIndex], pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale);
                        }

                        sLookedUpWord.Base.bLangIndex = 0xcc;

                        WLOG2String(pLogFile2, "  adding=2", sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen, 0, 1);
                        WLOG2(fprintf(pLogFile2, "\n");)

                        wStatus = _ET9AWSelLstAdd(pLingInfo, &sLookedUpWord, wLength, bFreqIndicator);
                    }
                }
                else {

                    /* if last word submitted wasn't the substitution for this shortcut, ok to submit */
                    /* if last word longer than substitution, can't be a match */

                    if (wLastWordLen > sLookedUpWord.Base.wSubstitutionLen) {
                        wLastWordLen = 0;
                    }

                    pStr1 = sLookedUpWord.Base.sSubstitution;
                    pStr2 = sLastWord;

                    for (i = 0; i < wLastWordLen; ++i) {
                        if (_ET9SymToLower(*pStr1++, pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale) != _ET9SymToLower(*pStr2++, pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale)) {
                            break;
                        }
                    }

                    /* if no match (or no last word), submit WITHOUT substitution */

                    if (!wLastWordLen || i != wLastWordLen) {

                        sLookedUpWord.Base.wSubstitutionLen = 0;

                        sLookedUpWord.Base.bLangIndex = 0xcc;

                        WLOG2String(pLogFile2, "  adding-3", sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen, 0, 1);
                        WLOG2(fprintf(pLogFile2, "\n");)

                        wStatus = _ET9AWSelLstAdd(pLingInfo, &sLookedUpWord, wLength, bFreqIndicator);

                        _ET9SymCopy(sLastWord, sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen);
                        wLastWordLen = sLookedUpWord.Base.wWordLen;
                    }
                    else {
                        WLOG2(fprintf(pLogFile2, "  not submitting word, shortcut to already submitted substitution\n");)
                        WLOG2String(pLogFile2, "    shortcut", sLookedUpWord.Base.sWord, sLookedUpWord.Base.wWordLen, 0, 1);
                        WLOG2(fprintf(pLogFile2, "\n");)
                    }
                }
            }
        }

        if (nCDBWordLen) {

            /* set right hand word info for next time through */

            nRHWStart = nWordStart;
            nRHWEnd = nWordEnd;
            nNumWhite = 1;
        }

        /* modify while loop condition to search righ side of datbase */

        if (!snSearchPosition) {

            if (!pCDB->wDataEndOffset) {
               WLOG2(fprintf(pLogFile2, "  break - !snSearchPosition && !pCDB->wDataEndOffset\n");)
               break;
            }

            snStopPoint = (ET9INT) pCDB->wDataEndOffset;
            snSearchPosition = (ET9INT) ET9CDBDataAreaSymbs(pCDB) - 1;
        }
        else {
            --snSearchPosition;
        }
    }

    return wStatus;
}

/*---------------------------------------------------------------------------*/
/*  -IDR-    
 *                      
 *
 *                                                                
 *                                         
 *                                                   
 *
 *                                                                    
 */

ET9STATUS ET9FARCALL _ET9AWCDBDeleteWord(ET9AWLingInfo  * const pLingInfo,
                                         ET9SYMB        * const psBuf,
                                         const ET9U16           wWordLen)
{
    ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;
    ET9AWCDBInfo ET9FARDATA * const pCDB = pLingCmnInfo ? pLingCmnInfo->pCDBInfo : NULL;

    ET9UINT nNumWhite = 0;
    ET9INT  snSearchPosition;
    ET9INT  snStopPoint;

    ET9Assert(pLingInfo);
    ET9Assert(pLingInfo->pLingCmnInfo);

    WLOG2(fprintf(pLogFile2, "_ET9AWCDBDeleteWord\n");)

    if (!psBuf) {
        return ET9STATUS_INVALID_MEMORY;
    }
    if (wWordLen < 1 || wWordLen > ET9MAXUDBWORDSIZE) {
        return ET9STATUS_BAD_PARAM;
    }
    if (pLingInfo->pLingCmnInfo->pCDBInfo == NULL) {
        return ET9STATUS_NO_CDB;
    }
    if (!ET9CDBENABLED(pLingInfo->pLingCmnInfo)){
        return ET9STATUS_DB_NOT_ACTIVE;
    }

    ET9Assert(pCDB);

    snStopPoint = 0;
    snSearchPosition = (ET9INT)(pCDB->wDataEndOffset - (ET9U16)1 + ET9CDBDataAreaSymbs(pCDB)) % ET9CDBDataAreaSymbs(pCDB);

    /* search the entire CDB, working backwards from the most recent entry */

    while (snSearchPosition >= snStopPoint) {

        ET9UINT nMatch = 0;
        ET9UINT nCDBWordLen = 0;

        const ET9UINT nWordEnd = (ET9UINT)snSearchPosition;

        /* if the current symbol is a delimiter, continue */

        if (*(ET9CDBData(pCDB) + snSearchPosition) == CDBDELIMITER) {
            ++nNumWhite;
        }

        /* search to see if this is context word, and hold pointers to beginning and end  */

        else {

            ET9UINT nIndex;

            /* assume match if context not broken after word */

            nMatch = (nNumWhite >= 1) ? 1: 0;

            /* this loops through the word that was found to see if it matches current word */

            nIndex = ET9CDBDataAreaSymbs(pCDB);

            while (nIndex--) {

                /* if the symbol is a delimiter... */

                if (*(ET9CDBData(pCDB) + snSearchPosition) == CDBDELIMITER) {

                    /* if the db word and the desired word have different sizes */

                    if (nCDBWordLen != wWordLen) {

                        /* no possible match */

                        nMatch = 0;
                    }
                    break;
                }

                /* if db word appears to be larger than desired word... */

                if (nMatch && (nCDBWordLen >= wWordLen)) {

                    /* no possible match */

                    nMatch = 0;
                }

                /* if a symbol mismatch occurs comparing db word and desired word */

                if (ET9AW_GetBilingualSupported(pLingInfo)) {

                    if (nMatch) {

                        if ((_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->dwFirstLdbNum)  != _ET9SymToLower(psBuf[wWordLen - 1 - nCDBWordLen], pLingCmnInfo->dwFirstLdbNum)) &&
                            (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->dwSecondLdbNum) != _ET9SymToLower(psBuf[wWordLen - 1 - nCDBWordLen], pLingCmnInfo->dwSecondLdbNum))) {

                            /* no possible match */

                            nMatch = 0;
                        }
                    }
                }
                else if (nMatch) {

                    if (_ET9SymToLower(*(ET9CDBData(pCDB) + snSearchPosition), pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale) != _ET9SymToLower(psBuf[wWordLen - 1 - nCDBWordLen], pLingCmnInfo->Base.pWordSymbInfo->Private.dwLocale)) {

                        /* no possible match */

                        nMatch = 0;
                    }
                }

                if (!snSearchPosition) {

                    if (!pCDB->wDataEndOffset) {
                        break;
                    }

                    snStopPoint = (ET9INT)pCDB->wDataEndOffset;
                    snSearchPosition = (ET9INT)ET9CDBDataAreaSymbs(pCDB) - 1;
                }
                else {
                    --snSearchPosition;
                }

                ++nCDBWordLen;
            }
        }

        /* okay, we've found a match, let's delete it */

        if (nMatch) {

            ET9UINT nDeleteWordLength;

            nDeleteWordLength = (nWordEnd + ET9CDBDataAreaSymbs(pCDB) - snSearchPosition) % ET9CDBDataAreaSymbs(pCDB);

            __ET9AWFillCDBPortionWithDelims(pLingInfo, (snSearchPosition + 1) % ET9CDBDataAreaSymbs(pCDB), nDeleteWordLength);
        }

        /* if key match also, deal with match */

        if (nCDBWordLen) {

            nNumWhite = 1;
        }

        /* modify while loop condition to search right side of database */

        if (!snSearchPosition) {

            if (!pCDB->wDataEndOffset) {
                break;
            }

            snStopPoint = (ET9INT) pCDB->wDataEndOffset;
            snSearchPosition = (ET9INT) ET9CDBDataAreaSymbs(pCDB) - 1;
        }
        else {
            --snSearchPosition;
        }
    }

    __LogCDB(pLingInfo, pLogFile2);

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * CDB Init.
 *
 * @param pLingInfo        Pointer to ET9 information structure.
 * @param pCDBInfo         Pointer to allocated CDB buffer.
 * @param wDataSize        Size of the CDB buffer pointed to by pCDBInfo (in bytes).
 * @param pWriteCB         Callback pointer to integration layer write routine (if used).
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9AWCDBInit(ET9AWLingInfo * const             pLingInfo,
                                  ET9AWCDBInfo ET9FARDATA * const   pCDBInfo,
                                  const ET9U16                      wDataSize,
                                  const ET9DBWRITECALLBACK          pWriteCB)
{
    ET9STATUS wStatus;

    WLOG2(fprintf(pLogFile2, "ET9AWCDBInit\n");)

    if ((wStatus = _ET9AWSys_BasicValidityCheck(pLingInfo)) != ET9STATUS_NONE) {
        return wStatus;
    }

    if ((pCDBInfo && !wDataSize) || (!pCDBInfo && wDataSize)) {
        return ET9STATUS_INVALID_MEMORY;
    }

    if (pCDBInfo && ((wDataSize < ET9MINCDBDATABYTES) || (wDataSize & 0x0001))) {
        return ET9STATUS_INVALID_SIZE;
    }

    if (pCDBInfo && pLingInfo->pLingCmnInfo->pDLMInfo) {
        return ET9STATUS_BAD_CONFIG;
    }

    {
        ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

        if ((pLingCmnInfo->pCDBInfo != NULL) && (pCDBInfo != NULL) &&
           ((pLingCmnInfo->pCDBInfo != pCDBInfo) ||
            (pCDBInfo->wDataSize != wDataSize))) {
            wStatus = ET9STATUS_ALREADY_INITIALIZED;
        }

        pLingCmnInfo->pCDBInfo = pCDBInfo;
        pLingInfo->pCDBWriteData = pWriteCB;

        /* Reset the CDB if internal size doesn't match indicated size */

        if ((pCDBInfo) && ((pCDBInfo->wDataSize != wDataSize) ||
            (pCDBInfo->wDataEndOffset >= ET9CDBDataAreaSymbs(pCDBInfo)))) {

            /* Update the CDB header with the given size */

            __ET9AWWriteCDBData(pLingInfo,
                     (void ET9FARDATA *)&pCDBInfo->wDataSize,
                     (const void ET9FARDATA*)&wDataSize,
                     sizeof(ET9U16));

            /* and reset to a blank state */

            ET9AWCDBReset(pLingInfo);
        }

        __BreakContext(pLingInfo);

        pLingCmnInfo->Private.bStateCDBEnabled = pCDBInfo ? 1 : 0;
    }

    return wStatus;
}

/*---------------------------------------------------------------------------*/
/**
 * Set CDB to empty.
 *
 * @param pLingInfo        Pointer to ET9 information structure.
 *
 * @return ET9STATUS_NONE on success, otherwise return ET9 error code.
 */

ET9STATUS ET9FARCALL ET9AWCDBReset(ET9AWLingInfo * const pLingInfo)
{
    ET9STATUS wStatus;

    WLOG2(fprintf(pLogFile2, "ET9AWCDBReset\n");)

    if ((wStatus = _ET9AWSys_BasicValidityCheck(pLingInfo)) != ET9STATUS_NONE) {
        return wStatus;
    }

    if (!pLingInfo->pLingCmnInfo->pCDBInfo) {
        return ET9STATUS_NO_CDB;
    }

    {
        ET9AWCDBInfo ET9FARDATA  * const pCDB = pLingInfo->pLingCmnInfo->pCDBInfo;

        const ET9U16 wTmp = 0;

        /* reset update counter to zero */

        __ET9AWCDBUpdateCounter(pLingInfo, 0);

        /* set the 'data end' indicator to zero */

        __ET9AWWriteCDBData(pLingInfo, (void ET9FARDATA *)&pCDB->wDataEndOffset, (const void ET9FARDATA *)&wTmp, sizeof(ET9U16));

        /* */

        __ET9AWFillCDBWithDelims(pLingInfo);

        /* update context */

        if (ET9CDBENABLED(pLingInfo->pLingCmnInfo)) {
            __AssureCdbContext(pLingInfo);
        }

        __LogCDB(pLingInfo, pLogFile2);
    }

    return ET9STATUS_NONE;
}

/*---------------------------------------------------------------------------*/
/**
 * Make this the current context.
 *
 * @param pLingInfo        Pointer to alpha information structure.
 * @param psBuf            Buffer to be used.
 * @param dwBufLen         Length of buffer.
 *
 * @return ET9STATUS_NONE on success, otherwise return T9 error code.
 */

ET9STATUS ET9FARCALL ET9AWFillContextBuffer(ET9AWLingInfo           * const pLingInfo,
                                            ET9SYMB           const * const psBuf,
                                            const ET9U32                    dwBufLen)
{
    ET9STATUS eStatus;

#if 0
    __ProfileStart;
#endif

    eStatus = _ET9AWSys_BasicValidityCheck(pLingInfo);

    if (eStatus) {
        return eStatus;
    }

    if (!psBuf && dwBufLen) {
        return ET9STATUS_INVALID_MEMORY;
    }

    WLOG2(fprintf(pLogFile2, "ET9AWFillContextBuffer, dwBufLen %u\n", dwBufLen);)

    if (dwBufLen) {
        WLOG2String(pLogFile2, "Buffer", psBuf, (ET9INT)dwBufLen, 0, 0);
    }

    /* handle context */

    {
        ET9UINT nWordCount;
        ET9U32 dwWordStart[ET9NLM_CONTEXT_COUNT];
        ET9U16 wWordLen[ET9NLM_CONTEXT_COUNT];

        nWordCount = 0;

        if (dwBufLen) {

            ET9BOOL bInWord;
            ET9S32 sdwIndex;
            ET9U32 dwEndPos;

            bInWord = 0;
            dwEndPos = dwBufLen - 1;

            for (sdwIndex = dwEndPos; sdwIndex >= 0; --sdwIndex) {

                if (_ET9_IsWhiteSpace(psBuf[sdwIndex])) {

                    if (bInWord) {

                        /* word done */

                        bInWord = 0;

                        ++nWordCount;

                        /* enough context? */

                        if (nWordCount >= ET9NLM_CONTEXT_COUNT) {
                            break;
                        }
                    }

                    /* special context break char? */

                    if (_ET9_IsContextBreakChar(psBuf[sdwIndex])) {
                        break;
                    }

                    /* double white to break? */

                    if (sdwIndex > 0 && _ET9_IsWhiteSpace(psBuf[sdwIndex - 1])) {
                        break;
                    }
                }
                else {

                    if (!bInWord) {

                        /* start of new word */

                        bInWord = 1;
                        wWordLen[nWordCount] = 1;
                        dwWordStart[nWordCount] = sdwIndex;
                    }
                    else {

                        /* add to current word */

                        ++wWordLen[nWordCount];
                        --dwWordStart[nWordCount];

                        ET9Assert(dwWordStart[nWordCount] == (ET9U32)sdwIndex);
                    }

                    /* a too long word behaves like a break (probably more like "UNK") */

                    if (wWordLen[nWordCount] > ET9MAXWORDSIZE) {
                        break;
                    }

                    /* "last" char in given string terminates */

                    if (!sdwIndex) {
                        ++nWordCount;
                        break;
                    }
                }
            }
        }

        /* transfer found words to context store */

        {
            ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

            ET9UINT nContextIndex;

            for (nContextIndex = 0; nContextIndex < ET9NLM_CONTEXT_COUNT; ++nContextIndex) {

                if (nContextIndex < nWordCount) {

                    pLingCmnInfo->Private.pContextWords[nContextIndex].wLen = wWordLen[nContextIndex];

                    _ET9SymCopy(pLingCmnInfo->Private.pContextWords[nContextIndex].sString, &psBuf[dwWordStart[nContextIndex]], wWordLen[nContextIndex]);

                }
                else {

                    pLingCmnInfo->Private.pContextWords[nContextIndex].wLen = 0;
                }
            }
        }
    }

#if 0

    /* save raw context */

    {
        ET9AWLingCmnInfo * const pLingCmnInfo = pLingInfo->pLingCmnInfo;

        pLingCmnInfo->Private.sCurrContext.wRawContextLen = (ET9U16)__ET9Min(_ET9_RAW_CONTEXT_SIZE, dwBufLen);

        if (pLingCmnInfo->Private.sCurrContext.wRawContextLen) {

            _ET9SymCopy(pLingCmnInfo->Private.sCurrContext.psRawContext, &psBuf[dwBufLen - pLingCmnInfo->Private.sCurrContext.wRawContextLen], pLingCmnInfo->Private.sCurrContext.wRawContextLen);
        }
    }

#endif

    /* CDB specifics */

    __AssureCdbContext(pLingInfo);

    __LogCDB(pLingInfo, pLogFile2);

    /* invalidate the selection list */

    _ET9InvalidateSelList(pLingInfo->pLingCmnInfo->Base.pWordSymbInfo);

    /* done */

#if 0
    __ProfileEnd(_TT_AW_FillContextBuffer);
#endif

    return ET9STATUS_NONE;
}

#endif /* ET9_ALPHABETIC_MODULE */
/*! @} */
/* ----------------------------------< eof >--------------------------------- */
