/*******************************************************************************
;*******************************************************************************
;**                                                                           **
;**                  COPYRIGHT 2001-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: et9aipanalyzer.c                                            **
;**                                                                           **
;**  Description: Conversion of path points to inflection points and segments.**
;**                                                                           **
;*******************************************************************************
;******************************************************************************/


/*==========================*/
/*      INCLUDE FILES       */
/*==========================*/

#include "et9aipanalyzer.h"

#include "et9aiptable.h"
#include "et9asearchdb.h"
#include "et9alogutils.h"

/*//////////////////////////////////////////////////////////////////// */
/* Construction/Destruction */
/*//////////////////////////////////////////////////////////////////// */

#define MOUSEDATA_MUTEX_EXIST       _TRUE
#define MOUSEDATA_MUTEX_LOCK
#define MOUSEDATA_MUTEX_UNLOCK


SWFixedData* ET9FARCALL SWIPAnalyzer_ObtainFixedData(SWCIPAnalyzer *pThis) {

    if (pThis->wFixedDataCount >= _SWYPE_MAX_FIXED_DATA) {
        Log(SWLogger_DEBUG, SWTEXT("Panic: no more fixed data slots.\n"));
        _ET9AWSP_DATA->bPanic = _TRUE;
        return &pThis->m_FixedDataOverflow;
    }

#if __ET9A_DEBUG_MEM_SIZE
    {
        static ET9INT nMaxPoints = 0;
        if ((pThis->wFixedDataCount + 1) > nMaxPoints) {
            nMaxPoints = (pThis->wFixedDataCount + 1);
            if (nMaxPoints % 10 == 0) {
                Log(SWLogger_DEBUG, TEXT("Cap %d fixed data pool now at %d.\n"), _SWYPE_MAX_FIXED_DATA, nMaxPoints);
            }
        }
    }
#endif
    return &pThis->m_pFixedData[pThis->wFixedDataCount++];
}

/*============================================================================= */
/* Function:    SWCIPAnalyzer_CIPAnalyzer() */
/* Parameters:  None */
/* Description: Construct the ip analyzer object. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_Construct(SWCIPAnalyzer *pThis)
{
    _SWPoint_Construct_Z(&pThis->m_PenDownLoc);
    _SWPoint_Construct_Z(&pThis->originalKeyMinRub);
    _SWPoint_Construct_Z(&pThis->originalKeyMaxRub);
    _SWPoint_Construct_Z(&pThis->m_SmoothPoint);

    Log(SWLogger_INFO, SWTEXT(" IP Analyzer constructor\n"));
#if DEBUG_THREAD_DETAILS
    Log(SWLogger_DEBUG, L" CIPAnalyzer() +\n");
    Trace(SWTracer::TraceTag::TT_CIPAnalyzer, (BYTE2)__LINE__, StrToBYTE4((Str)TEXT("CNSTRC")));
#endif

#if TRACK_KEY_PERFORMANCE
    ZERO(&m_ProcessMouseDataTiming);
#endif
    /* initialize mouse data processing flags */
    pThis->m_bCheckTap = _FALSE;
    pThis->m_bCheckTapHold = _FALSE;
    pThis->m_bPenDown = _FALSE;
    pThis->m_bStylusDown = _FALSE;
    pThis->m_bMaxFixedCount = _FALSE;
    pThis->m_bPenUp = _FALSE;

    pThis->m_wSignChangeIn = 0;
    pThis->m_wGestureTurns = 0;
    pThis->m_wSignChange2Cnt = 0;
    pThis->m_wBadSignChangeFixedIndex = -1;
    pThis->m_wBadSignChangeCount = 0;

    /* Initialize various UI and algorithm parameters */
    pThis->m_dwFixedInterval = Z1_FIXED_INTERVAL << PRECISION_ADJ_EXP;        /* Create fixed-interval points every 3 pixels */
                                                /*   along path */
    pThis->m_wD1Offset = Z1_DIFFERENCE_OFFSET;         /* Calculate first difference offset by 4 Fixed samples */
    pThis->m_wD2AngleThreshold = DIF2_ANGLE_THRESHOLD_MAX;
    pThis->m_wD2LowerThreshold = DIF2_LOWER_THRESHOLD;
    pThis->m_dwPauseThreshold = PAUSE_MIN_THRESHOLD;

#if FILTER_ERRATIC_MOUSE_DATA_BACK
    SWOS* pOS = SWOS::GetInstance();
    if (pOS) {
        maxPathDelta = (pOS->VGAScreenScale) ? (BYTE2)MAX_PATH_DELTA_VGA : (BYTE2)MAX_PATH_DELTA_QVGA;
        maxPathDeltaSum = (pOS->VGAScreenScale) ? (BYTE2)MAX_PATH_DELTA_SUM_VGA : (BYTE2)MAX_PATH_DELTA_SUM_QVGA;
        maxClusterDelta = (pOS->VGAScreenScale) ? (BYTE2)MAX_CLUSTER_DISTANCE_VGA : (BYTE2)MAX_CLUSTER_DISTANCE_QVGA;
        maxValidMetric = (pOS->VGAScreenScale) ? (BYTE2)MAX_PATH_METRIC_VGA : (BYTE2)MAX_PATH_METRIC_QVGA;
        maxValidMetricSum = (pOS->VGAScreenScale) ? (BYTE2)MAX_PATH_METRIC_SUM_VGA : (BYTE2)MAX_PATH_METRIC_SUM_QVGA;;                      /* Maximum acceptable sum of m_Metric values of successive (valid) mouse points */
        maxPenDownPathDelta = (pOS->VGAScreenScale) ? (BYTE2)MAX_PEN_DOWN_PATH_DELTA_VGA : (BYTE2)MAX_PEN_DOWN_PATH_DELTA_QVGA;
        maxPenDownPathDeltaSum = (pOS->VGAScreenScale) ? (BYTE2)MAX_PEN_DOWN_PATH_DELTA_SUM_VGA : (BYTE2)MAX_PEN_DOWN_PATH_DELTA_SUM_QVGA;
        maxPenDownMetric = (pOS->VGAScreenScale) ? (BYTE2)MAX_PEN_DOWN_METRIC_VGA : (BYTE2)MAX_PEN_DOWN_METRIC_QVGA;
        maxPenDownMetricSum = (pOS->VGAScreenScale) ? (BYTE2)MAX_PEN_DOWN_METRIC_SUM_VGA : (BYTE2)MAX_PEN_DOWN_METRIC_SUM_QVGA;
    }
#endif

#if FILTER_ERRATIC_MOUSE_DATA_BACK

    lastMouseData = NULL;     /* last location of Mouse (from MOUSE_MOVE message) */
    prevMouseData = NULL;     /* location of Mouse prior to lastMouseData (from MOUSE_MOVE message) */
    thisMouseData = NULL;     /* current location of Mouse (from MOUSE_MOVE message) */
    PenDownMouseData = NULL;  /* saved location of PenDown (from MOUSE_DOWN message) */
    PenUpMouseData = NULL;    /* saved location of PenUp (from MOUSE_UP message) */
    lastValidMouseData = NULL;     /* last location of Mouse (from MOUSE_MOVE message) */
    prevValidMouseData = NULL;     /* location of Mouse prior to lastValidMouseData (from MOUSE_MOVE message) */
    firstPendingDataTime = 0;                   /* timestamp of first saved Mouse location (from MOUSE_MOVE message) */
    validDataCount = 0;                         /* Number of data points already sent on for processing */
    closeMouseCount = 0;        /* Number of recent, consecutive MOUSE_DOWN, MOUSE_MOVE, and MOUSE_UP messages within a threshold of each other */
    omitMouseCount = 0;                         /* Number of data points filtered out */
    invalidDataPending = 0;                     /* Number of pending data points that may need to be filtered out */
    maxPendingMetric = 0;                       /* Maximum second difference sum among/between successive (pending) mouse points */
    maxPendingPathLen = 0;                      /* Maximum path distance between successive (pending) mouse points */
    maxPathDelta = 0;                           /* Maximum acceptable change in position between successive (valid) mouse points */
    maxPathDeltaSum = 0;                        /* Maximum acceptable sum of successive changes in position between successive (valid) mouse points */
    maxPenDownPathDelta = 0;        /* Maximum acceptable change in position between successive (valid) mouse points (when validating PenDown) */
    maxPenDownPathDeltaSum = 0;     /* Maximum acceptable sum of successive changes in position between successive (valid) mouse points (when validating PenDown) */
    maxPenDownMetric = 0;           /* Maximum acceptable m_Metric value (when validating PenDown) */
    maxPenDownMetricSum = 0;        /* Maximum acceptable sum of m_Metric values of successive (valid) mouse points (when validating PenDown) */
    maxClusterDelta = 0;                        /* Maximum acceptable distance from center of proposed mouse cluster */
    maxValidMetric = 0;                         /* Maximum acceptable m_Metric value between successive (valid) mouse points */
    maxValidMetricSum = 0;                      /* Maximum acceptable sum of m_Metric values of successive (valid) mouse points */
    /*maxPathSpeed = 0;                           // Maximum acceptable rate of change in position between successive (valid) mouse points (pixels/msec) */
    clusterCount = 0;                           /* Number of data clusters identified */
    m_bOriginalKey = 0;                         /* Key ID of key containing PenDown location */
    insideOriginalKey = _FALSE;                      /* True if all mouse data received is within bounds of original key */
    #if DEBUG_ERRATIC_MOUSE_DATA
            lastErrorInterval = 0;                   /* For periodic generation of bad mouse data */
            successiveErrors = 0;
            targetErrors = 0;
    #endif


#endif

    pThis->wFixedDataCount = 0;
    _ET9ClearMem(&pThis->m_FixedDataOverflow, sizeof(pThis->m_FixedDataOverflow));

} /* SWCIPAnalyzer_CIPAnalyzer() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_~CIPAnalyzer() */
/* Parameters:  None */
/* Description: Destruct the ip analyzer object. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_Destruct(SWCIPAnalyzer *pThis)
{
    /* release the handles */
#if DEBUG_THREAD_DETAILS
    Log(SWLogger_DEBUG, L" CIPAnalyzer() -\n");
    Trace(SWTracer::TraceTag::TT_CIPAnalyzer, (BYTE2)__LINE__, StrToBYTE4((Str)TEXT("DESTRC")));
#endif
    SWCIPAnalyzer_ReleaseHandles(pThis);
    Log(SWLogger_INFO, SWTEXT(" IP Analyzer destructor\n"));

} /*lint !e1740    SWCIPAnalyzer_~CIPAnalyzer() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_ReleaseHandles() */
/* Parameters:  None */
/* Description: Release all handles and clear data. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_ReleaseHandles(SWCIPAnalyzer *pThis)
{
    /* Clear (and release memory for) any remaining Mouse or Fixed data */
    SWCIPAnalyzer_ClearFixedData(pThis);

} /* SWCIPAnalyzer_ReleaseHandles() */


/*============================================================================= */
/* Function:    SWCIPAnalyzer::SetSignChange() */
/* Parameters:  SmoothData* newPoint - Current Smoothed data point */
/*              SmoothData& prevPoint - Previous Smoothed data point */
/*              SBYTE2& signChange1, signChange2 - Flags for the patterns detected */
/*                  in the changes in the sign of the point-to-point changes in */
/*                  the X and Y coordinates.  signChange1 is set to one of the */
/*                  values {-2, -1, 0, 1, 2}.  If signChange1 is non-zero, then */
/*                  signChange2 may also be set to one of those values, otherwise */
/*                  signChange2 will be 0. */
/* Operation:   signChange1 is set to one of the following based on the changes in */
/*              sign of one or both of the point-to-point changes in the X and Y */
/*              coordinates: */
/*              signChange1     X                     Y */
/*                  -4 :  changes + to -        changes + to - */
/*                  -3 :    No change           changes + to - */
/*                  -2 :  changes - to +        changes + to - */
/*                  -1 :  changes + to -          No change */
/*                   0 :    No change             No change */
/*                   1 :  changes - to +          No change */
/*                   2 :  changes + to -        changes - to + */
/*                   3 :    No change           changes - to + */
/*                   4 :  changes - to +        changes - to + */
/*              When abs(signChange) == 2 or abs(signChange) == 4, there have */
/*              been simultaneous point-to-point sign changes in both X and Y. */
/*              Based on the relative values of abs(dx) and abs(dy), these changes */
/*              are reported as an appropriate sequence of changes using both */
/*              signChange1 and signChange2. */
/* Description: Monitor and report on changes in sign of the point-to-point */
/*              changes in X and Y. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_SetSignChange(SWCIPAnalyzer *pThis, SWSmoothData *newPoint, SWSmoothData *prevPoint, SBYTE2 *signChange1, SBYTE2 *signChange2)
{
    /* If still within valid rubbing gesture region, track number of sign changes in raw mouse data for rubbing gesture detection */
    if (pThis->m_bInsideBoundingBoxRub)
    {
        /* Track number of sign changes in raw mouse data for rubbing gesture detection */
        SBYTE2  thisRawXdif = newPoint->rawPoint.x - prevPoint->rawPoint.x;
        SBYTE2  thisRawYdif = newPoint->rawPoint.y - prevPoint->rawPoint.y;
        if (thisRawXdif != 0)
        {
            if (pThis->m_wLastRawXdif != 0)
            {
                if ((thisRawXdif > 0) != (pThis->m_wLastRawXdif > 0))
                    pThis->m_wXSignChangeCount++;
            }
            pThis->m_wLastRawXdif = thisRawXdif;
        }
        if (thisRawYdif != 0)
        {
            if (pThis->m_wLastRawYdif != 0)
            {
                if ((thisRawYdif > 0) != (pThis->m_wLastRawYdif > 0))
                    pThis->m_wYSignChangeCount++;
            }
            pThis->m_wLastRawYdif = thisRawYdif;
        }
        pThis->m_wSignChangeCountRub = sw_max(pThis->m_wXSignChangeCount, pThis->m_wYSignChangeCount);
    }

    /*  Z1DebugOutput(TEXT(">>%3d> [%d, %d] => %d - %d // %d - %d => %d\r\n"), smoothCount, newPoint->rawPoint.x, newPoint->rawPoint.y, thisRawXdif, lastRawXdif, thisRawYdif, lastRawYdif, m_wSignChangeCount); */
    *signChange2 = 0;    /* Init to 0 based on assumption of single sign change */
    newPoint->dx = newPoint->smoothPoint.x - prevPoint->smoothPoint.x;
    if (newPoint->dx == 0)  /* Carry forward last actual change for signChange calculation */
        newPoint->dx = prevPoint->dx;
    newPoint->dy = newPoint->smoothPoint.y - prevPoint->smoothPoint.y;
    if (newPoint->dy == 0)  /* Carry forward last actual change for signChange calculation */
        newPoint->dy = prevPoint->dy;
    if (newPoint->dx && newPoint->dy && prevPoint->dx && prevPoint->dy) /* Still need to test for situation immediately following PenDown */
    {
        *signChange1 = (newPoint->dx > 0) - (prevPoint->dx > 0);             /*lint !e514 */
        *signChange1 += 3 * ((newPoint->dy > 0) - (prevPoint->dy > 0));              /*lint !e514 */
        if (*signChange1 == 3)
            *signChange1 = 2;
        else if (*signChange1 == -3)
            *signChange1 = -2;
        else if ((absGeo(*signChange1) == 2) || (absGeo(*signChange1) == 4))
        {
            ET9BOOL dxGreater = (ET9BOOL)(absGeo(prevPoint->dx) > absGeo(prevPoint->dy));
            switch (*signChange1)
            {
            case -4:
                if (dxGreater)
                {
                    *signChange1 = -2;
                    *signChange2 = -1;
                }
                else
                {
                    *signChange1 = -1;
                    *signChange2 = -2;
                }
                return;
            case -2:
                if (dxGreater)
                {
                    *signChange1 = -2;
                    *signChange2 = 1;
                }
                else
                {
                    *signChange1 = 1;
                    *signChange2 = -2;
                }
                return;
            case 2:
                if (dxGreater)
                {
                    *signChange1 = 2;
                    *signChange2 = -1;
                }
                else
                {
                    *signChange1 = -1;
                    *signChange2 = 2;
                }
                return;
            case 4:
                if (dxGreater)
                {
                    *signChange1 = 2;
                    *signChange2 = 1;
                }
                else
                {
                    *signChange1 = 1;
                    *signChange2 = 2;
                }
                return;
            }
        }
    }
    else
        *signChange1 = 0;
}

/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedData() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data element */
/* Description: Return the Fixed data object of the desired element. */
/*              Return the last element if index is too large so that pointer */
/*              is always legal. */
/*=============================================================================             //lint !e744 */

SWFixedData* ET9FARCALL SWCIPAnalyzer_GetFixedData(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex, SBYTE2 callNum)
{
    ET9_UNUSED(callNum);

    if ((fixedIndex < 0) || (fixedIndex >= pThis->wFixedDataCount)) {
        return NULL;
    }

    return &pThis->m_pFixedData[fixedIndex];
} /* SWCIPAnalyzer_GetFixedData() */


/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedPoint() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data element */
/* Description: Return the point position of the desired element. */
/*============================================================================= */

_SWPoint ET9FARCALL SWCIPAnalyzer_GetFixedPoint(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex, SBYTE2 callNum)
{
    /* get a pointer to the fixed mouse data */
    SWFixedData *fixedData = SWCIPAnalyzer_GetFixedData(pThis, fixedIndex, callNum);
    if (fixedData != NULL)
        return fixedData->m_Point;
    else
        return _SWPoint_Construct_SSB2SB2((SBYTE2)0, (SBYTE2)0);
} /* SWCIPAnalyzer_GetFixedPoint() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetPathLength() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data elements */
/* Description: Return the length of the path between the desired elements. */
/*============================================================================= */

BYTE2 ET9FARCALL SWCIPAnalyzer_GetPathLength(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex1, SBYTE2 fixedIndex2)
{
    BYTE2   pathLen8 = SWCIPAnalyzer_GetPathLength8(pThis, fixedIndex1, fixedIndex2);
    return((pathLen8 + 0x04) >> 3);
} /* SWCIPAnalyzer_GetPathLength() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetPathLength8() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data elements */
/* Description: Return the length8 of the path between the desired elements. */
/*============================================================================= */

BYTE2 ET9FARCALL SWCIPAnalyzer_GetPathLength8(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex1, SBYTE2 fixedIndex2)
{
    BYTE2   pathLen8;
    BYTE4   precisePathLen;
    SBYTE2  fixedLimit = pThis->wFixedDataCount;

    if (fixedIndex1 >= fixedIndex2) /* Check that params are in proper order */
    {
        if (fixedIndex1 == fixedIndex2)
            return (0);
        {
            SBYTE2 temp = fixedIndex1;
            fixedIndex1 = fixedIndex2;
            fixedIndex2 = temp;
        }
    }
    if (fixedIndex2 >= fixedLimit)
    {
        fixedIndex2 = fixedLimit - 1;
        if (fixedIndex1 >= (fixedLimit - 1))
            return(0);
    }
    /* Calculate precise actual distance between the two fixed points */
    precisePathLen = pThis->m_pFixedData[fixedIndex2].m_PrecisePathLen -
                        pThis->m_pFixedData[fixedIndex1].m_PrecisePathLen;
    pathLen8 = (BYTE2)((precisePathLen + (1 << (PRECISION_ADJ_EXP - 4))) >> (PRECISION_ADJ_EXP - 3));       /* Round to nearest distance8 value */
    return(pathLen8);
} /* SWCIPAnalyzer_GetPathLength8() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedTime() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data elements */
/* Description: Return the time stamp of the desired element. */
/*============================================================================= */

BYTE4 ET9FARCALL SWCIPAnalyzer_GetFixedTime(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex)
{
    /* get a pointer to the fixed mouse data */
    SWFixedData *fixedData = SWCIPAnalyzer_GetFixedData(pThis, fixedIndex, 24);
    if (fixedData != NULL)
        return fixedData->m_TimeOffset;
    else
        return (BYTE4)0;
} /* SWCIPAnalyzer_GetFixedTime() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedD2Sum() */
/* Parameters:  fixedIndex - Index of desired Fixed mouse data element */
/* Description: Return the summed absolute values of the 2nd differences of the */
/*              desired element. */
/*============================================================================= */

SBYTE4 ET9FARCALL SWCIPAnalyzer_GetFixedD2Sum(SWCIPAnalyzer *pThis, SBYTE2 fixedIndex)
{
    /* get a pointer to the fixed mouse data */
    SWFixedData *fixedData = SWCIPAnalyzer_GetFixedData(pThis, fixedIndex, 25);
    if (fixedData != NULL)
        return fixedData->D2Sum;
    else
        return (SBYTE4)0;
} /* SWCIPAnalyzer_GetFixedD2Sum() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedDataSize() */
/* Parameters:  None */
/* Description: Return the number of SWFixedData points saved */
/*============================================================================= */

SBYTE2 ET9FARCALL SWCIPAnalyzer_GetFixedDataSize(SWCIPAnalyzer *pThis)
{
    return pThis->wFixedDataCount;
} /* SWCIPAnalyzer_GetFixedDataSize() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_SaveFixedTimeOffset() */
/* Parameters:  dwDuration - Flag for initial fixed points */
/* Description: Save the values of the first Z1_DIFFERENCE_OFFSET time stamps */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_SaveFixedTimeOffset(SWCIPAnalyzer *pThis, BYTE4 dwTimeOffset, ET9BOOL penDownFlag)
{
    if (penDownFlag)        /* Also serves as Flag to initialize SavedTime array indexes */
    {
        pThis->m_wSavedTimeIn = 0;
        pThis->m_wSavedTimePrev = 0;
        pThis->m_wSavedTimeNext = 2;
    }
    pThis->m_dwSavedTime[pThis->m_wSavedTimeIn++] = dwTimeOffset;
    pThis->m_wSavedTimeIn &= SAVED_TIME_MASK;
} /* SWCIPAnalyzer_SaveFixedTimeOffset() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetFixedDuration() */
/* Parameters:  dwTimeOffset - current Fixed data point time stamp */
/* Description: Save the value of the current Fixed data point time stamp. */
/*              Calculate and return the duration value for the previous */
/*              data point where the 2nd difference is being set. */
/*============================================================================= */

BYTE4 ET9FARCALL SWCIPAnalyzer_GetFixedDuration(SWCIPAnalyzer *pThis, BYTE4 dwTimeOffset)
{
    BYTE4 dwDuration;
    /* Calculate Duration as the interval from preceding point to following point (two-sample duration) */
    pThis->m_wSavedDurationPrev = pThis->m_dwSavedTime[pThis->m_wSavedTimePrev];
    dwDuration = pThis->m_dwSavedTime[pThis->m_wSavedTimeNext++] - pThis->m_dwSavedTime[pThis->m_wSavedTimePrev++];
    pThis->m_wSavedTimePrev &= SAVED_TIME_MASK;
    pThis->m_wSavedTimeNext &= SAVED_TIME_MASK;
    /* Save previous single-sample duration for total pause calculation */
    pThis->m_wSavedDurationPrev = pThis->m_dwSavedTime[pThis->m_wSavedTimePrev] - pThis->m_wSavedDurationPrev;
    pThis->m_dwSavedTime[pThis->m_wSavedTimeIn++] = dwTimeOffset;
    pThis->m_wSavedTimeIn &= SAVED_TIME_MASK;
    return dwDuration;
} /* SWCIPAnalyzer_GetFixedDuration() */


/*============================================================================= */
/* Function:    SWCIPAnalyzer_ClearFixedData() */
/* Parameters:  None */
/* Description: Clear the Smoothed data array. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_ClearFixedData(SWCIPAnalyzer *pThis)
{
    pThis->wFixedDataCount = 0;
    _ET9ClearMem(&pThis->m_FixedDataOverflow, sizeof(pThis->m_FixedDataOverflow));
    pThis->m_LastFixedPoint = NULL;        /* Next point added will be next PenDown, so no preceding point available */
} /* SWCIPAnalyzer_ClearFixedData() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_CopyFixedDataToZ1() */
/* Parameters:  None */
/* Description: Clear the Smoothed data array. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_CopyFixedDataToZ1(SWCIPAnalyzer *pThis)
{
    pThis->m_backend->m_pSearchDB->m_pZ1FixedData = pThis->m_pFixedData;
    pThis->m_backend->m_pSearchDB->wFixedDataCount = pThis->wFixedDataCount;
    pThis->wFixedDataCount = 0;
} /* SWCIPAnalyzer_CopyFixedDataToZ1() */


/*============================================================================= */
/* Function:    SWCIPAnalyzer_GetSpeedFactor() */
/* Parameters:  BYTE4 dwPenSpeed - New pen speed measurement */
/*              ET9BOOL  calcAverage - If true, use this measurement to update the */
/*              running average pen speed.  If _FALSE, simply return the SpeedFactor */
/*              with respect to dwPenSpeed and the current average */
/* Returns:     BYTE4 - a value between 0 and MAX_SPEED_FACTOR calculated according */
/*              to where dwPenSpeed falls with respect to the historical mean and */
/*              standard deviation of Pen Speed measurements */
/* Description: Calculate the current PenSpeedFactor and update the historical average */
/*              and standard deviation. */
/*============================================================================= */

BYTE4 ET9FARCALL SWCIPAnalyzer_GetSpeedFactor(SWCIPAnalyzer *pThis, BYTE2 penSpeed, ET9BOOL updateHistory)
{
    BYTE4 speedFactor = 0;

    /* make sure these values are initialized because we don't do any update from the UDB below */
    pThis->m_wSpeedMax = MAX_PEN_SPEED;
    pThis->m_wAverageSpeed = 20;           /* 20 is the default from zdbformat.h */
    pThis->m_wSpeedMin = MIN_PEN_SPEED;

    if (penSpeed > MAX_PEN_SPEED)
        penSpeed = MAX_PEN_SPEED;
    else if (penSpeed < MIN_PEN_SPEED)
        penSpeed = MIN_PEN_SPEED;
    if (updateHistory)
    {
    }
    if (penSpeed >= pThis->m_wSpeedMax)
        speedFactor = MAX_SPEED_FACTOR;
    else if (penSpeed <= pThis->m_wSpeedMin)
        speedFactor = MIN_SPEED_FACTOR;
    /* If speed is less than average, then speed factor is between MIN_SPEED_FACTOR and AV_SPEED_FACTOR */
    else if (penSpeed < pThis->m_wAverageSpeed)
    {
        if (pThis->m_wAverageSpeed > pThis->m_wSpeedMin)
            speedFactor = ((penSpeed - pThis->m_wSpeedMin) * (AV_SPEED_FACTOR - MIN_SPEED_FACTOR)) / (pThis->m_wAverageSpeed - pThis->m_wSpeedMin);
        speedFactor += MIN_SPEED_FACTOR;
    }
    /* If speed is greater than average, then speed factor is between AV_SPEED_FACTOR and MAX_SPEED_FACTOR */
    else
    {
        if (pThis->m_wSpeedMax > pThis->m_wAverageSpeed)
            speedFactor = ((penSpeed - pThis->m_wAverageSpeed) * (MAX_SPEED_FACTOR - AV_SPEED_FACTOR)) / (pThis->m_wSpeedMax - pThis->m_wAverageSpeed);
        speedFactor += AV_SPEED_FACTOR;
        if (speedFactor > MAX_SPEED_FACTOR)
            speedFactor = MAX_SPEED_FACTOR;
    }
    return(speedFactor);
}   /* GetSpeedFactor() */

SWFixedData * ET9FARCALL SWCIPAnalyzer_AddFixedPoint(SWCIPAnalyzer *pThis, SBYTE2 x, SBYTE2 y, BYTE4 dwTimeOffset, SWFixedData *prevPoint, SBYTE2 Index)
{
    SWFixedData *fixedPoint;
    SBYTE2      wFixedIndex;
    SWCIPTableRow *ipData, *ipPrev;

    fixedPoint = SWIPAnalyzer_ObtainFixedData(pThis);
    SWFixedData_init(fixedPoint, x, y, dwTimeOffset, prevPoint, Index);

    if (pThis->wFixedDataCount > pThis->m_dwMaxFixedDataCount)
    {
        pThis->m_bMaxFixedCount = _TRUE;
    }
    if (pThis->m_LastFixedPoint != NULL)
        fixedPoint->m_PrecisePathLen = pThis->m_LastFixedPoint->m_PrecisePathLen + _SWPoint_preciseDistance(&pThis->m_LastFixedPoint->m_Point, &fixedPoint->m_Point);
    else
        fixedPoint->m_PrecisePathLen = 0;

    if (fixedPoint->m_Point.x < pThis->m_wPathMinX)
    {
        pThis->m_wPathMinX = fixedPoint->m_Point.x;
        pThis->m_wPathWidth = pThis->m_wPathMaxX - pThis->m_wPathMinX;
    }
    else if (fixedPoint->m_Point.x > pThis->m_wPathMaxX)
    {
        pThis->m_wPathMaxX = fixedPoint->m_Point.x;
        pThis->m_wPathWidth = pThis->m_wPathMaxX - pThis->m_wPathMinX;
    }
    if (fixedPoint->m_Point.y < pThis->m_wPathMinY)
    {
        pThis->m_wPathMinY = fixedPoint->m_Point.y;
        pThis->m_wPathHeight = pThis->m_wPathMaxY - pThis->m_wPathMinY;
    }
    else if (fixedPoint->m_Point.y > pThis->m_wPathMaxY)
    {
        pThis->m_wPathMaxY = fixedPoint->m_Point.y;
        pThis->m_wPathHeight = pThis->m_wPathMaxY - pThis->m_wPathMinY;
    }
    /* Separately track bounding box limit and time limit so that alternate character can be added to WCW if only time limit exceeded */
    if (pThis->m_bInsideBoundingBoxRub)
    {
        ET9BOOL outOfBoundsRub;
        ET9BOOL outOfBoundsVertical;
        if (pThis->m_bInsideOrignalKeyRub)
        {
            pThis->m_bInsideOrignalKeyRub = (ET9BOOL)((x >= pThis->originalKeyMinRub.x) && (x < pThis->originalKeyMaxRub.x) &&
                                      (y >= pThis->originalKeyMinRub.y) && (y < pThis->originalKeyMaxRub.y));
        }
#if FINGER_KEYBOARD
        outOfBoundsVertical = (ET9BOOL)(pThis->m_wPathHeight > NEAR_PEN_DOWN_MARGIN_Y);
#else
        outOfBoundsVertical = !SWCIPTable_PointsInSameRow(pThis->m_backend->pIPTable, pThis->m_wPathMinY, pThis->m_wPathMaxY, 0, _FALSE);
#endif
        outOfBoundsRub = (ET9BOOL)(!pThis->m_bInsideOrignalKeyRub ||
                          (pThis->m_wPathWidth > NEAR_PEN_DOWN_MARGIN_X) ||
                          outOfBoundsVertical);
        if (outOfBoundsRub)        /* Initial detection of path outside of bounding box */
        {
#if DEBUG_RUBBING
            Str report;
            if (!pThis->m_bInsideOrignalKeyRub)
            {
                report.Format(TEXT("{1 ~inOrigKey!} "));
                SWStateMachine::AutoSpaceInjectWord(report);
            }
            if ((pThis->m_wPathWidth > NEAR_PEN_DOWN_MARGIN_X) || (pThis->m_wPathHeight > NEAR_PEN_DOWN_MARGIN_Y))
            {
                int maxWidth = NEAR_PEN_DOWN_MARGIN_X;
                int maxHeight = NEAR_PEN_DOWN_MARGIN_Y;
                report.Format(TEXT("{2 PW %d > %d! PH %d > %d!} "), pThis->m_wPathWidth, maxWidth, pThis->m_wPathHeight, maxHeight);
                SWStateMachine::AutoSpaceInjectWord(report);
            }
#endif
            pThis->m_bInsideBoundingBoxRub = _FALSE;

            pThis->m_bRubbedKeyOK = _FALSE;

            /* Reset vars for calculation of path width and height to validate TapSwype gesture in progress */
            pThis->m_wPathMinX = fixedPoint->m_Point.x;
            pThis->m_wPathMaxX = fixedPoint->m_Point.x;
            pThis->m_wPathMinY = fixedPoint->m_Point.y;
            pThis->m_wPathMaxY = fixedPoint->m_Point.y;
            pThis->m_wPathWidth = pThis->m_wPathHeight = 0;
        }
    }

    if (pThis->m_bCheckShiftGesture && (fixedPoint->m_Point.y < SHIFT_GESTURE_MARGIN) && (pThis->wFixedDataCount >= 2))
    {
        SWFixedData *IPLoc;
        pThis->m_bCheckShiftGesture = _FALSE;
        pThis->m_ShiftGestured = _TRUE;
        pThis->m_bCheckTapHold = _FALSE;
        wFixedIndex = pThis->wFixedDataCount - 2;     /* Find index of last point with y still >= 0 (current point is first one with y < SHIFT_GESTURE_MARGIN) */
        IPLoc = SWCIPAnalyzer_GetFixedData(pThis, wFixedIndex, 26);
        while ((wFixedIndex > 0) && (IPLoc->m_Point.y < KEYBOARD_UPPER_BOUND))
        {
            wFixedIndex--;
            IPLoc = SWCIPAnalyzer_GetFixedData(pThis, wFixedIndex, 27);
        }
        ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Exit, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                wFixedIndex, wFixedIndex, 1, _TRUE);
        SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
    }
    else if (pThis->m_ShiftGestured && (fixedPoint->m_Point.y >= KEYBOARD_UPPER_BOUND))
    {
        SBYTE2 wIPIndex;
        pThis->m_bCheckShiftGesture = _TRUE;
        pThis->m_ShiftGestured = _FALSE;
        wIPIndex = SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable);
        if (wIPIndex > 0)
        {
            SBYTE2 wIPIndexM1 = wIPIndex - 1;                       /* Need &-addressable short param for GetIPTableRow() call */
            ipPrev = SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, &wIPIndexM1);

            wFixedIndex = pThis->wFixedDataCount - 1; /* Index of point just added */
            if (ipPrev->m_IPType != Exit)
            {
                Log(SWLogger_INFO, SWTEXT(" Adding Entry IP at Fixed[%d], but preceding IP[%d] at Fixed[%d] Type = %d  DBDval = %d\n"),
                                        wFixedIndex, ipPrev->m_IPIndex, ipPrev->m_IPPosIndex, ipPrev->m_IPType, ipPrev->debugData);
            }
            else
            {
                ipPrev->m_FixedIndexOut = wFixedIndex;
                ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, fixedPoint->m_Point, Entry, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), fixedPoint->m_TimeOffset,
                                    wFixedIndex, wFixedIndex, 2, _FALSE);
                SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
            }
        }
    }
    /* Check for and record position of any "Diacritic Gesture" (excursion below lower bounds of keyboard) */
#if ENABLE_DIACRITIC_GESTURES
    if (pThis->m_bCheckDiacriticGesture && (fixedPoint->m_Point.y > (pThis->m_backend->sScreenGeometry.keyboardHeight + DIACRITIC_GESTURE_MARGIN)))
    {
        wFixedIndex = (SBYTE2)SWFixedDataArrayLarge_size(pThis->m_pFixedData) - 2;     /* Find index of last point with y still above boundary (current point is first one with y > DIACRITIC_GESTURE_MARGIN) */
        if (wFixedIndex > 0)
        {
            pThis->m_bCheckDiacriticGesture = _FALSE;
            pThis->m_DiacriticGestured = _TRUE;
            SWFixedData *ExitLoc = GetFixedData(wFixedIndex, 26);
            if (pThis->m_backend->pIPTable->m_DiacriticLocCount < DEFAULT_WORD_SIZE)
            {
                if (ExitLoc->m_Point.x > 0)
                    pThis->m_backend->pIPTable->m_DiacriticExitXPos[pThis->m_backend->pIPTable->m_DiacriticLocCount] = ExitLoc->m_Point.x;
                else
                    pThis->m_backend->pIPTable->m_DiacriticExitXPos[pThis->m_backend->pIPTable->m_DiacriticLocCount] = 1;
                pThis->m_backend->pIPTable->m_DiacriticEntryXPos[pThis->m_backend->pIPTable->m_DiacriticLocCount] = 0;        /* Set next m_DiacriticEntryXPos[] value to zero (invalid) until actual re-entry detected */
                pThis->m_backend->pIPTable->m_DiacriticEntryPosIndex[pThis->m_backend->pIPTable->m_DiacriticLocCount] = 0;    /* Set next m_DiacriticEntryPosIndex[] value to zero (invalid) until actual re-entry detected */
                pThis->m_backend->pIPTable->m_DiacriticPos[pThis->m_backend->pIPTable->m_DiacriticLocCount++] = wFixedIndex;
            }
        }
    }
    else if (pThis->m_DiacriticGestured && (fixedPoint->m_Point.y < (SWOS::pIPTable->m_wZ1KeyboardHeight + DIACRITIC_GESTURE_MARGIN)))
    {
        pThis->m_bCheckDiacriticGesture = _TRUE;
        pThis->m_DiacriticGestured = _FALSE;
        if ((pThis->m_backend->pIPTable->m_DiacriticLocCount > 0) && (pThis->m_backend->pIPTable->m_DiacriticLocCount <= DEFAULT_WORD_SIZE))
        {
            if (fixedPoint->m_Point.x > 0)
                pThis->m_backend->pIPTable->m_DiacriticEntryXPos[pThis->m_backend->pIPTable->m_DiacriticLocCount - 1] = fixedPoint->m_Point.x;
            else
                pThis->m_backend->pIPTable->m_DiacriticEntryXPos[pThis->m_backend->pIPTable->m_DiacriticLocCount - 1] = 1;        /* Make sure that m_EntryXPos[] value is non-zero */
            pThis->m_backend->pIPTable->m_DiacriticEntryPosIndex[pThis->m_backend->pIPTable->m_DiacriticLocCount - 1] = (SBYTE2)SWFixedDataArrayLarge_size(pThis->m_pFixedData) - 1;
        }
    }
#endif
    pThis->m_LastFixedPoint = fixedPoint;
    return(fixedPoint);
}


/*============================================================================= */
/* Function:    SWCIPAnalyzer_ProcessMouseData() */
/* Parameters:  SWCMouseData & cmData - single mouse data item to be processed */
/* Description: Process a single mouse data item. */
/*============================================================================= */
void ET9FARCALL SWCIPAnalyzer_ProcessMouseData(SWCIPAnalyzer *pThis, BYTE4 newTimeOffset, _SWPoint *newPoint, ET9BOOL bIsSpecial)
{
#if SMOOTH_RAW_DATA
    _SWPoint calcPoint;
#endif
    SWCIPTableRow *ipData = NULL;
    SBYTE2  wFixedSize;
    ET9BOOL addPenUp = _FALSE;       /* Determine whether this is a normal Z1 path */
    ET9BOOL addTapIP = _FALSE;       /* Determine whether this is a tap event */

    /* Check for flag data that following datapoint is PEN_UP or PEN_DOWN event */
    if (bIsSpecial && (newPoint->x == 0) && (newPoint->y == 0))
    {
        pThis->m_bPenDown = _TRUE;
        return;
    }
    else if (bIsSpecial && (newPoint->x == 1) && (newPoint->y == 1))
    {
        pThis->m_bPenUp = _TRUE;
        return;
    }
#if FILTER_ERRATIC_MOUSE_DATA_BACK
    validDataCount++;
#endif
    /* On PEN_DOWN, Call AddIPtoTable() with time and location of first */
    /* contact point to record inflection point in IPT */
    if (pThis->m_bPenDown)
    {
        pThis->m_bPenDown = _FALSE;         /* Reset flags */
        pThis->m_bPenUp = _FALSE;
        pThis->m_bStylusDown = _TRUE;       /* Stylus is now down on keyboard */
        pThis->m_bMaxFixedCount = _FALSE;
        /* Allow a maximum of DEFAULT_WORD_SIZE times back-and-forth across the entire width of the keyboard */
        pThis->m_dwMaxFixedDataCount = (pThis->m_backend->sScreenGeometry.keyboardWidth / Z1_FIXED_INTERVAL) * DEFAULT_WORD_SIZE;
        pThis->m_bCheckTap = _TRUE;
    #if !FILTER_ERRATIC_MOUSE_DATA
        pThis->m_bCheckTapHold = _TRUE;
    #endif
        pThis->m_bCheckShiftGesture = _TRUE;
        pThis->m_ShiftGestured = _FALSE;
#if ENABLE_DIACRITIC_GESTURES
        pThis->m_bCheckDiacriticGesture = _TRUE;
        pThis->m_DiacriticGestured = _FALSE;
#endif
        pThis->m_dwTotalPauses = pThis->m_wSavedDurationPrev = 0;

        SWCIPTable_SetIPTableAnalyzedFlag(pThis->m_backend->pIPTable, _FALSE);   /* Clear Analyzed flag when table is free */

        /* Clear out previous Smooth and Fixed mouse data arrays */
        SWCIPAnalyzer_ClearFixedData(pThis);

        /* Clear out previous IP Table */
        SWCIPTable_ClearIPTable(pThis->m_backend->pIPTable);

        /* Set as first SmoothData and add as first SWFixedData location */
        /* Save as previous SmoothData location for on-going path creation */
        /* First mouse data point in smoothBuffer[0] */
        pThis->smoothBuffer[0].rawPoint = pThis->smoothBuffer[0].smoothPoint = *newPoint;
        pThis->smoothBuffer[0].m_TimeOffset = newTimeOffset;
        /*TRACE(TEXT("==>[%d,%d] "), pThis->smoothBuffer[0].smoothPoint.x, pThis->smoothBuffer[0].smoothPoint.y); */
        pThis->m_PrevSmoothPoint = &pThis->smoothBuffer[0];
        pThis->smoothIn = 0;                                   /* Next mouse data point goes in smoothBuffer[1] - smoothIn will be incremented first */
        pThis->smoothCount = 1;                                /* Only one data point in mouseData[] buffer */
        pThis->m_PenDownLoc = *newPoint;

#if FILTER_ERRATIC_MOUSE_DATA_BACK
        /* If we are still processing the current path, confirm that accepted PenDown falls in same key as originally received PenDown */
        ET9BOOL upDateOriginalKey = _FALSE;
        if (cmData->m_PathID == SWStateMachine::inputPathID)
        {
        /*    pOS->BeepAndFlash(NT_UNSPECIFIED); */
            SWStateMachine::ptPenDown = pThis->m_PenDownLoc;
            if ((OriginalKey_BackEnd != sm.GetOriginalKey()))
            {
                sm.SetOriginalKey(OriginalKey_BackEnd);                /* Update m_bTapHoldKey now in case not enough data received to flush out pending PenDown */
                                                                         /*   before tap timeout is triggered. */
                upDateOriginalKey = _TRUE;
                if (sm.GetOriginalKey() != SWDbm_KD_INVALID_KEY)
                {
                    pDbm->getKeyBoundsScaled(sm.GetOriginalKey(), &SWStateMachine::OriginalKeyMin, &SWStateMachine::OriginalKeyMax, _TRUE);          /* Highlight the key */
                    SWStateMachine::HighlightArea(-1, &pThis->originalKeyMinRub, &pThis->originalKeyMaxRub, SWStateMachine::HL_TEMPORARY);
                    SWStateMachine::insideOriginalKey = _TRUE;
                }
                else
                    SWStateMachine::insideOriginalKey = _FALSE;
            }
        }
#endif

#if ALLOW_SLOPPY_PREDICTIVE_TAPPING
        SBYTE2  horizontalAdjustment;
        SBYTE2  verticalAdjustmentAbove;
        SBYTE2  verticalAdjustmentBelow;
        SWCSettingsManager* pSettingsManager = SWCSettingsManager::GetInstance();
        if (pSettingsManager && pSettingsManager->GetPredictiveTextSetting().IsWordPredictionOn())
        {
            /* Allow sloppier margins if we are actively tapping a word using prediction */
            if (pThis->m_backend->m_pSearchDB->tappedCharCount > 0)
            {
                horizontalAdjustment = horizontalTapMargin2;
                verticalAdjustmentAbove = verticalTapMarginAbove2;
                verticalAdjustmentBelow = verticalTapMarginBelow2;
            }
            else
            {
                horizontalAdjustment = horizontalTapMargin;
                verticalAdjustmentAbove = verticalTapMarginAbove;
                verticalAdjustmentBelow = verticalTapMarginBelow;
            }
            if (originalKeyMinTap.x > (pThis->m_PenDownLoc.x - horizontalAdjustment))
                originalKeyMinTap.x = (pThis->m_PenDownLoc.x - horizontalAdjustment);
            if (originalKeyMaxTap.x < (pThis->m_PenDownLoc.x + horizontalAdjustment))
                originalKeyMaxTap.x = (pThis->m_PenDownLoc.x + horizontalAdjustment);
            if (originalKeyMinTap.y > (pThis->m_PenDownLoc.y - verticalAdjustmentAbove))
                originalKeyMinTap.y = (pThis->m_PenDownLoc.y - verticalAdjustmentAbove);
            if (originalKeyMaxTap.y < (pThis->m_PenDownLoc.y + verticalAdjustmentBelow))
                originalKeyMaxTap.y = (pThis->m_PenDownLoc.y + verticalAdjustmentBelow);
        }
#endif

#if FILTER_ERRATIC_MOUSE_DATA_BACK
        if (upDateOriginalKey)
        {
            SWStateMachine::OriginalKeyMin = originalKeyMinTap;
            SWStateMachine::OriginalKeyMax = originalKeyMaxTap;
        }
#endif

        pThis->m_dwPenDownTime = newTimeOffset;

        pThis->m_wPathMinX = pThis->m_wPathMaxX = newPoint->x;     /* Initi path minima and maxima to PenDown location */
        pThis->m_wPathMinY = pThis->m_wPathMaxY = newPoint->y;
        pThis->m_wPathWidth = pThis->m_wPathHeight = 0;
        pThis->m_bInsideBoundingBoxTap = _TRUE;
        pThis->m_bCalledZ1Detect = _FALSE;
        pThis->m_bRubbedKeyOK = _FALSE;
        pThis->m_bInsideBoundingBoxRub = pThis->m_bInsideOrignalKeyRub = _FALSE;

        /* !pOS->EnableRubbing */
        {
#if FINGER_KEYBOARD
            SBYTE2  rubWidthAdjust =  (SBYTE2)pThis->m_backend->sScreenGeometry.keyWidth / 2;
            SBYTE2  rubHeightAdjust = (SBYTE2)pThis->m_backend->sScreenGeometry.keyHeight / 2;
#else
            SBYTE2  rubWidthAdjust =  (SBYTE2)pThis->m_backend->sScreenGeometry.keyWidth / 3;
            SBYTE2  rubHeightAdjust = (SBYTE2)pThis->m_backend->sScreenGeometry.keyHeight / 3;
#endif
            pThis->originalKeyMinRub.x = pThis->m_PenDownLoc.x - rubWidthAdjust;
            pThis->originalKeyMaxRub.x = pThis->m_PenDownLoc.x + rubWidthAdjust;
            pThis->originalKeyMinRub.y = pThis->m_PenDownLoc.y - rubHeightAdjust;
            pThis->originalKeyMaxRub.y = pThis->m_PenDownLoc.y + rubHeightAdjust;
        }
#if FILTER_ERRATIC_MOUSE_DATA_BACK
        if (upDateOriginalKey)
        {
            SWStateMachine::OriginalKeyMin = originalKeyMinTap;
            SWStateMachine::OriginalKeyMax = originalKeyMaxTap;
        }
#endif
        pThis->rubbedKey = (BYTE1)SWDbm_KD_INVALID_KEY;               /* No valid rubbed key identified yet */
        _SWPoint_Construct_SB2SB2(&pThis->m_SmoothPoint, (SBYTE2)0, (SBYTE2)0);
        pThis->m_dwSmoothTime = 0;
        pThis->m_dwFixedSegmentLength = 0;
        pThis->m_LastFixedPoint = NULL;        /* */
        /* PenDown flag set to true to initialize TimeSave variables */
        SWCIPAnalyzer_SaveFixedTimeOffset(pThis, newTimeOffset, _TRUE);
        pThis->m_PrevFixedPoint = SWCIPAnalyzer_AddFixedPoint(pThis, newPoint->x, newPoint->y, newTimeOffset, NULL, (SBYTE2) 0);
        /* Add PEN_DOWN location as first Fixed point */
        ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, pThis->m_PenDownLoc, PenDown, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), pThis->m_dwPenDownTime, (SBYTE2) 0, (SBYTE2) 0, 3, _FALSE);
        if (SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData) != IPT_OK)
        {
            pThis->m_bStylusDown = _FALSE;      /* Reject mouse data since we couldn't add the PenDown IP */
            return;
        }
        ipData->m_ForceShowWCW = _FALSE;             /* Force showing of WCW for 1-letter words when multiple DoubleLetter gesture loops performed */
        ipData->rubbedKey = pThis->rubbedKey;              /* Init assuming no rubbed key detected */

        pThis->m_bD2ThresholdExceeded = _FALSE;     /* Clear Second Difference index flag value */
        pThis->m_wLastAddedD2Index = 0;    /* Init to 0 to flag that no Angle IP added yet */
        pThis->m_bPauseThresholdExceeded = _FALSE;  /* Clear Duration index flag value */
        pThis->D2Sums2In = 0;
        pThis->D2Sums2Full = _FALSE;
        pThis->lastTrackedAngleIndex = -1;

        pThis->m_wSignChangeIn = 0;                /* Clear sign change tracking array */
        pThis->m_wGestureTurns = 0;
        pThis->m_wSignChange2Cnt = 0;

        /* Signal state machine that PenDown has been processed? */
        return;
    }
    /* If no stylus down event received or the keyboard has changed, ignore incoming data */
    /* If path length exceeds maximum, ignore remaining data until PenUp is received */
    if (!pThis->m_bStylusDown ||
        (pThis->m_bMaxFixedCount && !pThis->m_bPenUp))
    {
        /*ignoreCount++;            // Un-comment if we appear to be failing to process mouse data in the back end */
        /*if (ignoreCount > 40) */
        /*{ */
        /*    ignoreCount = 0; */
        /*    pOS->BeepAndFlash(NT_UNSPECIFIED); */
        /*} */
        return;
    }
        /* Add to smoothed data point buffer.  Extract next smoothed point if buffer has enough data. */
    /* Advance smoothIn now for new incoming data */
    pThis->smoothIn = (pThis->smoothIn + 1) % SMOOTH_BUFFER_SIZE;
    /* Current mouse data point in smoothBuffer[smoothIn].  Set as both rawPoint and SmoothPoint, and update SmoothPoint if/when next data ia available. */
    pThis->smoothBuffer[pThis->smoothIn].rawPoint = pThis->smoothBuffer[pThis->smoothIn].smoothPoint = *newPoint;
    pThis->smoothBuffer[pThis->smoothIn].m_TimeOffset = newTimeOffset;
    /* Calculate offsets of each element */
    pThis->smoothOut = pThis->smoothIn - 1;
    if (pThis->smoothOut < 0)
        pThis->smoothOut = SMOOTH_BUFFER_SIZE - 1;
    pThis->smoothCount++;
#if SMOOTH_RAW_DATA
    pThis->smoothPrev = pThis->smoothOut - 1;
    if (pThis->smoothPrev < 0)
        pThis->smoothPrev = SMOOTH_BUFFER_SIZE - 1;
    pThis->smoothPrev2 = pThis->smoothPrev - 1;
    if (pThis->smoothPrev2 < 0)
        pThis->smoothPrev2 = SMOOTH_BUFFER_SIZE - 1;
    /* Calculate smoothed location for next point as weighted average of the original point with the preceding and following points */
    if (pThis->smoothCount >= SMOOTHING_DATA_COUNT_MIN)            /* Note: No smoothing if PenUp received prior to receiving enough data (i.e. > 3 data points) for smoothing */
    {
        if ((!pThis->m_bPenUp || (pThis->smoothCount > SMOOTHING_DATA_COUNT_MIN)) &&                  /* Don't smooth middle point if only three data points total */
            (pThis->smoothBuffer[pThis->smoothOut].rawPoint.y >= 0))
        {
            calcPoint = pThis->smoothBuffer[pThis->smoothPrev].rawPoint;
            calcPoint += pThis->smoothBuffer[pThis->smoothOut].rawPoint;
#if !SMOOTHER_RAW_DATA
            calcPoint += pThis->smoothBuffer[pThis->smoothOut].rawPoint;           /* Add the actual point in twice */
#else
            calcPoint += pThis->smoothBuffer[pThis->smoothPrev2].rawPoint;         /* Add four consecutive points to smooth... */
#endif
            calcPoint += pThis->smoothBuffer[pThis->smoothIn].rawPoint;
            calcPoint.x = calcPoint.x >> 2;                          /* Divide by 4 */
            calcPoint.y = calcPoint.y >> 2;
            pThis->smoothBuffer[pThis->smoothOut].smoothPoint = calcPoint;         /* Update smoothed value for current point */
        }
        pThis->m_SmoothPoint = pThis->smoothBuffer[pThis->smoothOut].smoothPoint;         /* Set smoothed value for current point */
        pThis->m_dwSmoothTime = pThis->smoothBuffer[pThis->smoothOut].m_TimeOffset;
        pThis->m_NewSmoothPoint = &pThis->smoothBuffer[pThis->smoothOut];
        pThis->m_PrevSmoothPoint = &pThis->smoothBuffer[pThis->smoothPrev];
    }
/* Continue processing if PenUp received (even if buffer not filled) */
    else if (pThis->m_bPenUp)  /* If PenUp received as 2nd mouse data point, then no actual smoothing possible */
    {
        pThis->m_SmoothPoint = pThis->smoothBuffer[pThis->smoothIn].smoothPoint;     /* Set smoothed value for current point */
        pThis->m_dwSmoothTime = pThis->smoothBuffer[pThis->smoothIn].m_TimeOffset;
        pThis->m_NewSmoothPoint = &pThis->smoothBuffer[pThis->smoothIn];
        pThis->m_PrevSmoothPoint = &pThis->smoothBuffer[pThis->smoothOut];
    }
    else
        return;
#else
    pThis->m_NewSmoothPoint = &pThis->smoothBuffer[pThis->smoothIn];         /* Set pointers for current and preceding points */
    pThis->m_PrevSmoothPoint = &pThis->smoothBuffer[pThis->smoothOut];
#endif
    /* On PEN_Up, Call AddIPtoTable() with time and location of last */
    /* contact point to record inflection point in IPT */
    if (pThis->m_bPenUp)
    {
        SWFixedData *prevFixed1 = 0, *prevFixed2 = 0, *prevFixed3 = 0;
#if SMOOTH_RAW_DATA
        {
            if (pThis->smoothCount >= SMOOTHING_DATA_COUNT_MIN)
            {
                pThis->m_bPenUp = _FALSE;           /* Set _FALSE to process final smooth point received just prior to actual PenUp... */
                ProcessSmoothDataPoint();
                pThis->m_bPenUp = _TRUE;            /* Now we are at actual PenUp location */
            }
            newPoint = pThis->smoothBuffer[pThis->smoothIn].rawPoint;
            pThis->m_NewSmoothPoint = &pThis->smoothBuffer[pThis->smoothIn];
            pThis->m_PrevSmoothPoint = &pThis->smoothBuffer[pThis->smoothOut];
        }
#endif
        SWCIPAnalyzer_ProcessSmoothDataPoint(pThis);

        /* Set PathLength on receipt of PenUp */
        wFixedSize = pThis->wFixedDataCount;
        prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, wFixedSize - 1, 32);
        SWCIPAnalyzer_AddFixedPoint(pThis, newPoint->x, newPoint->y, newTimeOffset, prevFixed1, wFixedSize);
        wFixedSize = pThis->wFixedDataCount;
        SWCIPTable_SetActualPathLength(pThis->m_backend->pIPTable, SWCIPAnalyzer_GetPathLength(pThis, 0, (wFixedSize - 1)));

#if DEBUG_TAP_DETECTION
        /* SWCSettingsManager* pSettingsManager = SWCSettingsManager::GetInstance(); */
        /*if (pSettingsManager && pSettingsManager->GetPredictiveTextSetting().IsWordPredictionOn()) */
        {
            Str report;
            report.Format(TEXT("/SC-%d/APL-%d/SCC-%d/"), pThis->smoothCount, pThis->m_backend->pIPTable->ActualPathLength, m_wSignChangeCountRub);
            SWStateMachine::AutoSpaceInjectWord(report);
        }
#endif
        /* If current input path is a Z1 word (not a Tap), then save PenUp and PenDown times for timing analysis */

        pThis->m_dwMovementDuration = (newTimeOffset - pThis->m_dwPenDownTime) - pThis->m_dwTotalPauses;
        if (pThis->m_dwMovementDuration > 0)
        {
            pThis->m_dwPenSpeed = 100 * (BYTE4)SWCIPTable_GetActualPathLength(pThis->m_backend->pIPTable);
            pThis->m_dwPenSpeed /= pThis->m_dwMovementDuration;
            pThis->m_dwSpeedFactor = SWCIPAnalyzer_GetSpeedFactor(pThis, (BYTE2)pThis->m_dwPenSpeed, _TRUE);
        }
        else
            pThis->m_dwSpeedFactor = AV_SPEED_FACTOR;
#if     DEBUG_IPT1A
        Log(SWLogger_DEBUG, L" PenUp received [%d] SpeedFactor = %d\n", newTimeOffset, pThis->m_dwSpeedFactor);
#endif
        if (pThis->m_bD2ThresholdExceeded) /* Process any pending Angle IP */
        {
            pThis->m_bD2ThresholdExceeded = _FALSE;     /* Reset flag */
            /* If this IP has not already been added, then add it now */
            if (pThis->m_wD2Index != pThis->m_wLastAddedD2Index)
            {
                if (pThis->m_wD2Index < (wFixedSize - 1))
                {
                    /* The appropriate location for the Angle IP may be at a later point.  Compare 2nd difference with */
                    /* adjacent points to fine-tune actual location, since we cannot use standard interval 2nd */
                    /* difference as we approach the PenUp location. */
                    SBYTE2  checkD2Index = pThis->m_wD2Index;
                    SBYTE2  D1xPrev, D1xNext, D1yPrev, D1yNext;
                    SBYTE2  D2x, D2y, D2MaxIndex = -1;
                    SBYTE4  D2Sum, D2Max = -1;
                    prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, checkD2Index - 1, 33);
                    prevFixed2 = SWCIPAnalyzer_GetFixedData(pThis, checkD2Index, 34);
                    prevFixed3 = SWCIPAnalyzer_GetFixedData(pThis, checkD2Index + 1, 35);
                    /* Init values for copy-forward */
                    /* Adjusted calculation may be needed if Z1_FIXED_INTERVAL is not set to 5 */
                    D1xNext = (prevFixed1->m_Point.x - prevFixed2->m_Point.x);
                    D1yNext = (prevFixed1->m_Point.y - prevFixed2->m_Point.y);
                    while (checkD2Index < (wFixedSize - 1))
                    {
                        D1xPrev = D1xNext;
                        D1xNext = (prevFixed2->m_Point.x - prevFixed3->m_Point.x);
                        D1yPrev = D1yNext;
                        D1yNext = (prevFixed2->m_Point.y - prevFixed3->m_Point.y);
                        D2x = D1xPrev - D1xNext;
                        D2y = D1yPrev - D1yNext;
                        D2Sum = (D2x * D2x) + (D2y * D2y);
                        if (D2Sum >= D2Max)
                        {
                            D2Max = D2Sum;
                            D2MaxIndex = checkD2Index;
                        }
                        checkD2Index++;
                        if (checkD2Index < (wFixedSize - 1))
                        {
                            prevFixed2 = prevFixed3;
                            prevFixed3 = SWCIPAnalyzer_GetFixedData(pThis, checkD2Index + 1, 36);
                        }
                    }
                    if ((D2MaxIndex > 0) && (D2MaxIndex != pThis->m_wD2Index))
                        pThis->m_wD2Index = D2MaxIndex;
                }
                {
                    SWFixedData *IPLoc = SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wD2Index, 37);
                    ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Angle, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                                pThis->m_wD2Index, pThis->m_wD2Index, 5, _TRUE);
                    SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
                    pThis->m_wLastAddedD2Index = pThis->m_wD2Index;
                }
            }
        }
        /* Flush out valid sequence for DoubleLetter, if pending */
        /* TrackSignChanges(pThis->m_PrevSmoothPoint, 0, 0, _TRUE); */

        {
            ET9BOOL useNewpoint = _TRUE;
            ET9BOOL ipVectorMismatch = _FALSE;
            SBYTE2 prevFixedIndex = 0, discontinuityIndex = 0;
#if END_PATH_AT_DISCONTINUITY
            /* Look for discontinuous change in slope in fixed points preceding PenUp.  If found, back */
            /* PenUp location to first point prior to discontinuity */
#if DEBUG_IPT7
            Log(SWLogger_DEBUG, L"PenDown initial data:\n");
            Log(SWLogger_DEBUG, L" Idx [xxx, yyy] (D1X, D1Y) (D2X, D2Y) D2Sum  PLen\n");
            for (SBYTE2 i = 0; i <= sw_min((3 * DISCONTINUITY_SEARCH_LIMIT), (wFixedSize - 1)); i++)
            {
                SWFixedData *dFixed = GetFixedData(i, 61);
                Log(SWLogger_DEBUG, L" %3d [%3d, %3d] (%3d, %3d) (%3d, %3d) %4d   %4d\n", i, dFixed->m_Point.x, dFixed->m_Point.y, dFixed->D1x, dFixed->D1y, dFixed->D2x, dFixed->D2y, dFixed->D2Sum, dFixed->m_PrecisePathLen);
            }
#endif
            SBYTE2 stepCount = 0;
            SBYTE2 lastIPIndex = SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable) - 1;
            if (lastIPIndex >= 0)       /* Must have at least one preceding IP */
            {
                SWCIPTableRow *prevIP = SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, lastIPIndex);
                /* ipVector is not very meaningful if newPoint is very close to prevIP (i.e. if the path has doubled back */
                /* to an earlier position, but no intervening IP has yet been detected).  Check for (and disallow) a path length */
                /* that is significantly greater than the straight-line distance ??? */
                _SWVector    ipVector = _SWVector(prevIP->m_IPLocation, newPoint);
                prevFixedIndex = wFixedSize - 1;
                prevFixed2 = GetFixedData(prevFixedIndex, 38);
                /* Make sure we have a distinct point to define prevVector1 */
                if (prevFixed2->m_Point == newPoint)
                {
                    prevFixedIndex--;
                    prevFixed2 = GetFixedData(prevFixedIndex, 39);
                }
                prevFixed1 = prevFixed2;        /* Initialize now in case while loop (below) is never executed */
                if (wFixedSize > 2)
                {
                    prevFixed3 = GetFixedData(prevFixedIndex - 1, 40);
                    _SWVector prevVector1 = _SWVector(prevFixed2->m_Point, newPoint);                 /* First check newPoint itself */
                    _SWVector prevVector2 = _SWVector(prevFixed3->m_Point, prevFixed2->m_Point);
                    ipVectorMismatch = (prevVector1.slopeDifference(ipVector) > DISTANT_KEY_SLOPE_THRESHOLD);
                    slopeDiscontinuity = (prevVector1.slopeDifference(prevVector2) > DISCONTINUOUS_SLOPE_THRESHOLD);
                    foundDiscontiuity = slopeDiscontinuity;
                    if (foundDiscontiuity)
                    {
                        discontinuityIndex = prevFixedIndex;    /* prevFixed2 is the vertex of any detected slope discontinuity */
                        useNewpoint = _FALSE;
                    }
                    stepCount = 1;
                    /* If we can back up to a match with the ipVector without crossing a discontinuity, then use newPoint */
                    while ((ipVectorMismatch || slopeDiscontinuity) && (prevFixedIndex > (prevIP->m_FixedIndexOut + 2)) &&
                           (stepCount <= DISCONTINUITY_SEARCH_LIMIT))
                    {
                        prevFixedIndex--;
                        prevFixed1 = prevFixed2;        /* prevFixed1 is the point at (prevFixedIndex + 1) */
                        prevFixed2 = prevFixed3;        /* prevFixed2 is the point at (prevFixedIndex) */
                        prevFixed3 = GetFixedData(prevFixedIndex - 1, 41);
                        prevVector1 = prevVector2;
                        prevVector2 = _SWVector(prevFixed3->m_Point, prevFixed2->m_Point);
                        /* If we haven't matched with the ipVector yet, see if we do now */
                        if (ipVectorMismatch)
                            ipVectorMismatch = (prevVector1.slopeDifference(ipVector) > DISTANT_KEY_SLOPE_THRESHOLD);
                        slopeDiscontinuity = (prevVector1.slopeDifference(prevVector2) > DISCONTINUOUS_SLOPE_THRESHOLD);
                        if (slopeDiscontinuity)
                        {
                            foundDiscontiuity = _TRUE;
                            ipVectorMismatch = ((prevFixedIndex > (prevIP->m_FixedIndexOut + 2)) && (stepCount < DISCONTINUITY_SEARCH_LIMIT));
                            discontinuityIndex = prevFixedIndex;
                            useNewpoint = _FALSE;
                        }
                        stepCount++;
                    }
                }
            }
#endif
            /* If newPoint is OK, or we simply failed to find a better point, then use newPoint */
#if !DEBUG_IPT7
            if ((wFixedSize > pThis->m_wD1Offset) && (useNewpoint || ipVectorMismatch || (discontinuityIndex <= 0)))
#else
            if (wFixedSize > (pThis->m_wD1Offset + 1))
#endif
            {
                prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, (wFixedSize - pThis->m_wD1Offset - 1), 42);
                for (prevFixedIndex = sw_max((wFixedSize - pThis->m_wD1Offset), 0); prevFixedIndex < wFixedSize; prevFixedIndex++)
                {
                    /* Pass in same newTimeOffset for each call.  Only the saved value for the first call is */
                    /*   actually used.  Subsequent values stored but never accessed. */
                    prevFixed2 = SWCIPAnalyzer_GetFixedData(pThis, prevFixedIndex, 42);
                    SWCIPAnalyzer_TrackDurationChanges(pThis, SWCIPAnalyzer_GetFixedDuration(pThis, newTimeOffset), prevFixedIndex);

                    /* SavedCodeSegments/286 - Old D1x calculation based on limited context */
                    /* Set D1x and D1y for last (pThis->m_wD1Offset+1) points to proportional values */
                    /* Adjusted calculation may be needed if Z1_FIXED_INTERVAL is not set to 5 */
                    prevFixed2->D1x = (prevFixed2->m_Point.x - prevFixed1->m_Point.x) * pThis->m_wD1Offset;
                    prevFixed2->D1y = (prevFixed2->m_Point.y - prevFixed1->m_Point.y) * pThis->m_wD1Offset;
                    /* Init 2nd difference fields for final locations */
                    prevFixed2->D2x = (prevFixed2->D1x - prevFixed1->D1x);
                    prevFixed2->D2y = (prevFixed2->D1y - prevFixed1->D1y);
                    prevFixed2->D2Sum = (prevFixed2->D2x * prevFixed2->D2x) + (prevFixed2->D2y * prevFixed2->D2y);
                    prevFixed1 = prevFixed2;
                }
                prevFixedIndex = wFixedSize - pThis->m_wD1Offset;
                prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, prevFixedIndex, 43);      /* Reset for SWFixedData() call below... */
            }
#if DEBUG_IPT7
            Log(SWLogger_DEBUG, L"\nPenUp final data:\n");
            Log(SWLogger_DEBUG, L" Idx [xxx, yyy] (D1X, D1Y) (D2X, D2Y) D2Sum  PLen\n");
            for (SBYTE2 i = sw_max((wFixedSize - (3 * DISCONTINUITY_SEARCH_LIMIT)), 0); i <= (wFixedSize - 1); i++)
            {
                SWFixedData *dFixed = GetFixedData(i, 62);
                Log(SWLogger_DEBUG, L" %3d [%3d, %3d] (%3d, %3d) (%3d, %3d) %4d   %4d\n", i, dFixed->m_Point.x, dFixed->m_Point.y, dFixed->D1x, dFixed->D1y, dFixed->D2x, dFixed->D2y, dFixed->D2Sum, dFixed->m_PrecisePathLen);
            }
#endif
            /* Determine whether the current path is an instance of "rubbing" a key to get its alternate character */
            addPenUp = _TRUE;       /* Assume this is a normal Z1 path */
            addTapIP = _FALSE;       /* Assume this is not a tap event */

            /* If entire path was constrained within neighborhood of PenDown */
            pThis->m_bRubbedKeyOK = _FALSE;
            pThis->rubbedKey = (BYTE1)SWDbm_KD_INVALID_KEY;
            {
                ET9BOOL callZ1DetectNow = (!pThis->m_bRubbedKeyOK && !pThis->m_bCalledZ1Detect);
                if (!pThis->m_bRubbedKeyOK)
                {
                        callZ1DetectNow = !pThis->m_bCalledZ1Detect;
                }
                if (callZ1DetectNow)   /* Signal Z1 Input since path does not qualify as any form of "tapped" key */
                {
                    /* Clear flags in case set, so ReleaseTentativeWord() will be called */
                    pThis->m_bInsideBoundingBoxRub = _FALSE;
                }
            }
            /* Add PenUp IP (unless this was a "rubbed" key */
            if (addPenUp)
            {
                /* If newPoint is OK, or we simply failed to find any discontinuity, then use newPoint */
                if (useNewpoint || ipVectorMismatch || (discontinuityIndex <= 0))
                {
                    ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, *newPoint, PenUp, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), newTimeOffset, (wFixedSize - 1), (wFixedSize - 1), 6, _TRUE);
                }
                else
                {
                    prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, discontinuityIndex, 44);
                    ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, prevFixed1->m_Point, PenUp, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), prevFixed1->m_TimeOffset, discontinuityIndex, discontinuityIndex, 7, _TRUE);
                    SWCIPTable_SetActualPathLength(pThis->m_backend->pIPTable, SWCIPAnalyzer_GetPathLength(pThis, 0, discontinuityIndex));
                }
                /* Save min and max path coordinates in PenUp IP */
                ipData->minX = pThis->m_wPathMinX;
                ipData->maxX = pThis->m_wPathMaxX;
                ipData->minY = pThis->m_wPathMinY;
                ipData->maxY = pThis->m_wPathMaxY;

                /* Ensure that any DoubleLetter IPs have each generated at least two IPs */
                {
                    SBYTE2 doubleIndex;
                    for (doubleIndex = 0; doubleIndex < SWCIPTable_GetDoubleIPCount(pThis->m_backend->pIPTable); doubleIndex++)
                    {
                        SBYTE2  gestureIPsNeeded;
                        SBYTE2  pathDivIPCount;
                        SWCIPTableRow *ipDouble = SWCIPTable_GetIPTable2Row(pThis->m_backend->pIPTable, doubleIndex);               /* June 26, 2009    TODO !!!! This pointer is invalid as it is not protected by the table's mutex. */
                        if (ipDouble == NULL)
                            break;
                        gestureIPsNeeded = 2 - ipDouble->gestureIPCount;
                        pathDivIPCount = 0;
                        /* Increment # of IPs needed for each PathDivision IP included, since these will be deleted */
                        if ((ipDouble->gestureIPCount > 0) && (ipDouble->gestureIPIndexIn >= 0))
                        {
                            SBYTE2 checkIndex;
                            for (checkIndex = ipDouble->gestureIPIndexIn; checkIndex <= ipDouble->gestureIPIndexOut; checkIndex++)
                            {
                                if (SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, &checkIndex)->m_IPType == PathDivision)
                                    pathDivIPCount++;
                            }
                            if (ipDouble->gestureIn == 0)       /* If gesture includes softDown, then require SoftDown plus two additional IPs */
                                gestureIPsNeeded++;
                        }
                        gestureIPsNeeded = (SBYTE2)(gestureIPsNeeded + pathDivIPCount);
                        if (gestureIPsNeeded >= 2)          /* If we still need two more IPs in gesture */
                        {
                            SBYTE2 addIndex = ipDouble->m_FixedIndexIn + 1;
                            SWCIPTableRow *ipAdd;
                            AlwaysAssert(((ipDouble->gestureIn == ipDouble->gestureOut) && (ipDouble->gestureIn <= 0)) || (pathDivIPCount > 0));
                            prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, addIndex, 45);
                            /* Add new Ip as PauseAngle, since it was not actually generated by the path */
                            /*SWCIPTableRow *ipAdd = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, prevFixed1->m_Point, PauseAngle, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), prevFixed1->m_TimeOffset, addIndex, addIndex, 8, _FALSE); */
                            /* Add new Ip as Multiple, since it was not actually generated by the path */
                            ipAdd = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, prevFixed1->m_Point, Multiple, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), prevFixed1->m_TimeOffset, addIndex, addIndex, 8, _FALSE);

                            ipAdd->gestureLoc = ipDouble->m_IPLocation;
                            ipAdd->gestureIn = ipDouble->m_FixedIndexIn;
                            ipAdd->gestureOut = ipDouble->m_FixedIndexOut;
                            /* Track number and location of included IPs */
                            if (SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipAdd) != IPT_FAILURE)
                            {   /* ipAdd may already have been included by AddIPtoTable() */
                                if (ipAdd->m_DoubleIPIndex != ipDouble->m_DoubleIPIndex)
                                {
                                    /* Set gestureIPIndexIn, etc., after index is (possibly) adjusted by AddIPtoTable() */
                                    ipAdd->m_DoubleIPIndex = -ipDouble->m_DoubleIPIndex;
                                    SWCIPTable_recordIncludedIP(pThis->m_backend->pIPTable, ipDouble, ipAdd, 1);
                                }
                            }
                            else
                            {
                                /*pThis->m_backend->pIPTable->wIPPoolCount--; */
                            }
                        }
                        /* If only one IP for gesture and we will not be adding the PenUp IP as part of the final gesture (or if PenUp will eliminate/replace */
                        /*   currently existing gesture IP */
                        if (gestureIPsNeeded >= 1)
                        {
                            /* See where existing IP(s) falls within gesture */
                            SBYTE2  fromStart = ipDouble->gestureIn - ipDouble->m_FixedIndexIn;
                            SBYTE2  fromEnd = ipDouble->m_FixedIndexOut - ipDouble->gestureOut;
                            SBYTE2  addIndex = -1;      /* Flag that addIndex not yet set */
                            SWCIPTableRow *ipAdd;
                            if (ipDouble->gestureIPCount == 2)
                            {
                                SBYTE2  between = ipDouble->gestureOut - ipDouble->gestureIn;
                                SBYTE2  fromEnds = sw_max(fromStart, fromEnd) - 1;
                                if (between >= (2 * fromEnds))
                                    addIndex = (ipDouble->gestureIn + ipDouble->gestureOut) / 2;
                            }
                            if (addIndex < 0)       /* Set addIndex (if not set above) to end farthest from existing IPs */
                            {
                                if (fromEnd > fromStart)
                                    addIndex = ipDouble->m_FixedIndexOut - 1;
                                else
                                    addIndex = ipDouble->m_FixedIndexIn + 1;
                            }
                            prevFixed1 = SWCIPAnalyzer_GetFixedData(pThis, addIndex, 46);
                            /* Add new Ip as PauseAngle, since it was not actually generated by the path */
                            /*SWCIPTableRow *ipAdd = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, prevFixed1->m_Point, PauseAngle, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), prevFixed1->m_TimeOffset, addIndex, addIndex, 9, _FALSE); */
                            /* Add new Ip as Multiple, since it was not actually generated by the path */
                            ipAdd = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, prevFixed1->m_Point, Multiple, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), prevFixed1->m_TimeOffset, addIndex, addIndex, 9, _FALSE);

                            ipAdd->gestureLoc = ipDouble->m_IPLocation;
                            ipAdd->gestureIn = ipDouble->m_FixedIndexIn;
                            ipAdd->gestureOut = ipDouble->m_FixedIndexOut;
                            if (SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipAdd) != IPT_FAILURE)
                            {   /* ipAdd may already have been included by AddIPtoTable() */
                                if (ipAdd->m_DoubleIPIndex != ipDouble->m_DoubleIPIndex)
                                {
                                    /* Track number and location of included IPs */
                                    ipAdd->m_DoubleIPIndex = -ipDouble->m_DoubleIPIndex;
                                    SWCIPTable_recordIncludedIP(pThis->m_backend->pIPTable, ipDouble, ipAdd, 2);
                                    ipData->m_IPIndex = SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable);            /* Adjust for inserted IP */
                                }
                            }
                            else
                            {
                                /*pThis->m_backend->pIPTable->wIPPoolCount--; */
                            }
                        }
                        /*AlwaysAssert(ipDouble->gestureIPCount >= ((ipDouble->gestureIn == 0) ? 3 : 2)); */
                        if (ipDouble->gestureIPCount < ((ipDouble->gestureIn == 0) ? 3 : 2))
                        {
                            SBYTE2  deletedDoubleIndex = ipDouble->m_DoubleIPIndex;
                            SWCIPTable_RemoveIPTable2Row(pThis->m_backend->pIPTable, doubleIndex);
                            {
                                SBYTE2 checkDoubleIndex;
                                for (checkDoubleIndex = 0; checkDoubleIndex < SWCIPTable_GetDoubleIPCount(pThis->m_backend->pIPTable); checkDoubleIndex++)
                                {
                                    SWCIPTableRow *ipDoubleCheck = SWCIPTable_GetIPTable2Row(pThis->m_backend->pIPTable, checkDoubleIndex);
                                    if (ipDoubleCheck == NULL)
                                        break;
                                    if (ipDoubleCheck->m_DoubleIPIndex >= deletedDoubleIndex)
                                        ipDoubleCheck->m_DoubleIPIndex--;
                                }
                            }
                            {
                                SBYTE2 checkIPIndex;
                                for (checkIPIndex = 0; checkIPIndex < SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable); checkIPIndex++)
                                {
                                    SWCIPTableRow *ipCheck = SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, &checkIPIndex);
                                    if (ipCheck == NULL)
                                        break;
                                    if (ipCheck->m_DoubleIPIndex == deletedDoubleIndex)
                                    {
                                        ipCheck->m_DoubleIPIndex = 0;
                                        ipCheck->gestureIPCount = 0;
                                        ipCheck->gestureIn = ipCheck->gestureOut = -1;              /* Init to invalid values */
                                        ipCheck->gestureIPIndexIn = ipCheck->gestureIPIndexOut = -1;
                                    }
                                    else if (ipCheck->m_DoubleIPIndex > deletedDoubleIndex)
                                        ipCheck->m_DoubleIPIndex--;
                                }
                            }
                        }
                    }
                }
            }
            /* Now add PenUp IP to table (or Tap IP if this was a "rubbed" path */
            if (addPenUp || addTapIP)
            {
                ipData->m_IPIndex = SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable);    /* Make sure index is up-to-date in case any IPs added above */
                SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
            }
            pThis->m_bPenUp = _FALSE;           /* Reset flags */
            pThis->m_bStylusDown = _FALSE;      /* Stylus no longer down on keyboard */
            pThis->m_bCheckShiftGesture = _TRUE;
            pThis->m_ShiftGestured = _FALSE;
#if ENABLE_DIACRITIC_GESTURES
            pThis->m_bCheckDiacriticGesture = _TRUE;
            pThis->m_DiacriticGestured = _FALSE;
#endif
            return;
        }
    }
    else
        SWCIPAnalyzer_ProcessSmoothDataPoint(pThis);

} /* SWCIPAnalyzer_ProcessMouseData() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_PathIsLikelySwypePath() */
/* Description: Determine whether path appears to be a Swype path rather than an */
/*              ordinary tap, tap-hold, or rubbing gesture. */
/*============================================================================= */

ET9BOOL ET9FARCALL SWCIPAnalyzer_PathIsLikelySwypePath(SWCIPAnalyzer *pThis, BYTE2 totalPathLength, BYTE2 horzSignChangeCount)
{
    BYTE2 minSwypePathLengthX, maxTapPathLength, avgXSegmentLength;
#if ALLOW_SLOPPY_PREDICTIVE_TAPPING
    SWCSettingsManager* pSettingsManager = SWCSettingsManager::GetInstance();
    if (pSettingsManager && pSettingsManager->GetPredictiveTextSetting().IsWordPredictionOn())
    {
        minSwypePathLengthX = (BYTE2)(pThis->m_backend->sScreenGeometry.keyWidth * 3) >> 1;  /* If average horizontal segment length is >= 1.5 key widths (~36) */
        maxTapPathLength = (BYTE2)(pThis->m_backend->sScreenGeometry.keyWidth * 3) >> 1;     /* If total path length is greater than 1.5 key widths */
    }
    else
#endif
    {
        minSwypePathLengthX = (BYTE2)(pThis->m_backend->sScreenGeometry.keyHeight * 3) >> 2;  /* If average horizontal segment length is >= 3/4 of a key height (~30) */
        maxTapPathLength = (BYTE2)(pThis->m_backend->sScreenGeometry.keyHeight * 3) >> 2;     /* If total path length is greater than 3/4 of a key height */
    }
    avgXSegmentLength = totalPathLength / (horzSignChangeCount + 1);      /* Calculate average horizontal segment length */
    {
        ET9BOOL likelySwypePath = (ET9BOOL)((totalPathLength > maxTapPathLength) || (avgXSegmentLength >= minSwypePathLengthX));
        return(likelySwypePath);
    }
}   /* SWCIPAnalyzer_PathIsLikelySwypePath() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_ProcessSmoothDataPoint() */
/* Description: Process latest (newly generated) SmoothData point */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_ProcessSmoothDataPoint(SWCIPAnalyzer *pThis)
{
    BYTE4   m_wFixedIn;
    SBYTE4  smoothInterval;
    ET9BOOL calledtrackRoutines = _FALSE;

    pThis->m_SmoothPoint = pThis->m_NewSmoothPoint->smoothPoint;
    pThis->m_dwSmoothTime = pThis->m_NewSmoothPoint->m_TimeOffset;

#if !FINGER_ON_RESISTIVE_BUILD
    if (pThis->m_bCheckTap)
    {
        /* If contact action no longer qualifies as either an ordinary tap or tap-hold */
        if ((pThis->smoothCount > MAX_TAP_PATH_RAW_MOUSE_COUNT) &&
            (SWFixedDataArrayLarge_size(pThis->m_pFixedData) > (Z1_TAP_FIXED_INTERVAL_LIMIT + 1)) &&       /* "+1" since first fixed point is PenDown, not the end of an interval */
            /* Also require that path extend beyond minimal neighborhood to disqualify as Tap */
             (((pThis->m_wPathMaxX - pThis->m_wPathMinX) > Z1_NON_TAP_AREA_MIN_X) || ((pThis->m_wPathMaxY - pThis->m_wPathMinY) > Z1_NON_TAP_AREA_MIN_Y)))
        {
            pThis->m_bCheckTapHold = _FALSE;
            m_bOrdinaryTap = _FALSE;
            /* SavedCodeSegments/223 */
        }
        /* SavedCodeSegments/224 */
    }
#endif
    /* SavedCodeSegments/201  (TIME_SLICE_AVERAGING) */

    /* Add distance of new segment and determine if new FixedPoint is needed */
    smoothInterval = (SBYTE4)_SWPoint_preciseDistance(&pThis->m_PrevSmoothPoint->smoothPoint, &pThis->m_SmoothPoint);
    pThis->m_dwFixedSegmentLength += smoothInterval;
    /* D2Sum tracking routines may not be called below at PenUp. Make sure they are called at least once. */
    while ((smoothInterval > 0) && (pThis->m_dwFixedSegmentLength >= pThis->m_dwFixedInterval))
    {
        /* Create new Fixed point interpolated between smoothed points */
        SBYTE4 fixedOvershoot = pThis->m_dwFixedSegmentLength - pThis->m_dwFixedInterval;
        SBYTE4 FixedX = pThis->m_SmoothPoint.x;
        SBYTE4 FixedY = pThis->m_SmoothPoint.y;
        BYTE4  FixedTime = pThis->m_dwSmoothTime;
        if (fixedOvershoot > 0)
        {
            SBYTE4 adjustY;
            BYTE4 adjustTime;
            SBYTE4 adjustX = fixedOvershoot * (FixedX - pThis->m_PrevSmoothPoint->smoothPoint.x);
            adjustX = (adjustX + (smoothInterval >> 1)) / smoothInterval;               /*lint !e702 */
            FixedX -= adjustX;
            adjustY = fixedOvershoot * (FixedY - pThis->m_PrevSmoothPoint->smoothPoint.y);
            adjustY = (adjustY + (smoothInterval >> 1)) / smoothInterval;               /*lint !e702 */
            FixedY -= adjustY;
            adjustTime = fixedOvershoot * (FixedTime - pThis->m_PrevSmoothPoint->m_TimeOffset);
            adjustTime = (adjustTime + ((BYTE4)smoothInterval >> 1)) / (BYTE4)smoothInterval;           /*lint !e702 */
            FixedTime -= adjustTime;
        }
        pThis->m_dwFixedSegmentLength = (BYTE4)fixedOvershoot; /* Initialize interval sum to next Fixed point */
        /* Calculate first and second differences */
        m_wFixedIn = pThis->wFixedDataCount;
        if (m_wFixedIn <= (BYTE4)pThis->m_wD1Offset)  /* Special case until enough data in for first difference */
        {
            SWFixedData *initFixedPoint;
            /* PenDown flag set to _FALSE */
            SWCIPAnalyzer_SaveFixedTimeOffset(pThis, FixedTime, _FALSE);
            /* Set D1x and D1y for first (pThis->m_wD1Offset+1) points to adjusted values */
            initFixedPoint = SWCIPAnalyzer_AddFixedPoint(pThis, (SBYTE2)FixedX, (SBYTE2)FixedY, FixedTime, pThis->m_LastFixedPoint, (SBYTE2)m_wFixedIn);
            initFixedPoint->D1x = (SBYTE2)(initFixedPoint->D1x * pThis->m_wD1Offset);
            initFixedPoint->D1y = (SBYTE2)(initFixedPoint->D1y * pThis->m_wD1Offset);
            if (m_wFixedIn == 1)   /* Init 2nd difference and duration fields for PenDown location */
            {
                pThis->m_LastFixedPoint->D2x >>= 1;
                pThis->m_LastFixedPoint->D2y >>= 1;
                pThis->m_LastFixedPoint->D2Sum = (pThis->m_LastFixedPoint->D2x * pThis->m_LastFixedPoint->D2x) + (pThis->m_LastFixedPoint->D2y * pThis->m_LastFixedPoint->D2y);
            }
        }
        else
        {
            SBYTE2 prevIndex = (SBYTE2)m_wFixedIn - pThis->m_wD1Offset;
            pThis->m_PrevFixedPoint = SWCIPAnalyzer_GetFixedData(pThis, prevIndex, 29);
            SWCIPAnalyzer_AddFixedPoint(pThis, (SBYTE2)FixedX, (SBYTE2)FixedY, FixedTime, pThis->m_PrevFixedPoint, (SBYTE2)m_wFixedIn);
            SWCIPAnalyzer_TrackD2Changes(pThis, (SBYTE2)pThis->m_PrevFixedPoint->D2Sum, prevIndex, pThis->m_bPenUp);
            SWCIPAnalyzer_TrackDurationChanges(pThis, SWCIPAnalyzer_GetFixedDuration(pThis, FixedTime), prevIndex);
            calledtrackRoutines = _TRUE;
        }
    }
    m_wFixedIn = pThis->wFixedDataCount;

    /*AlwaysAssert(!pThis->m_bPenUp || !calledtrackRoutines); <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */

    if (pThis->m_bPenUp && (m_wFixedIn > (BYTE4)pThis->m_wD1Offset))
    {
        SBYTE2 prevIndex;
        SWFixedData *lastPoint = SWCIPAnalyzer_GetFixedData(pThis, (SBYTE2)(m_wFixedIn - 1), 30);
        SBYTE2 lastX = lastPoint->m_Point.x;
        SBYTE2 lastY = lastPoint->m_Point.y;
        SWFixedData *setPoint;
        for (prevIndex = ((SBYTE2)m_wFixedIn - pThis->m_wD1Offset); prevIndex < (SBYTE2)(m_wFixedIn - 1); prevIndex++)
        {
            SBYTE2 interval;
            setPoint = SWCIPAnalyzer_GetFixedData(pThis, prevIndex, 31);
            interval = (SBYTE2)m_wFixedIn - prevIndex - 1;
            if (interval > 0)
            {
                /* Adjusted calculation may be needed if Z1_FIXED_INTERVAL is not set to 5 */
                SBYTE2 D1x = ((lastX - setPoint->m_Point.x) * pThis->m_wD1Offset / interval);
                SBYTE2 D1y = ((lastY - setPoint->m_Point.y) * pThis->m_wD1Offset / interval);
                setPoint->D2x = D1x - setPoint->D1x;
                setPoint->D2y = D1y - setPoint->D1y;
                setPoint->D2Sum = (setPoint->D2x * setPoint->D2x) + (setPoint->D2y * setPoint->D2y);
            }
            SWCIPAnalyzer_TrackD2Changes(pThis, (SBYTE2)setPoint->D2Sum, prevIndex, (ET9BOOL)(prevIndex == (SBYTE2)(m_wFixedIn - 2)));
        }
#if DEBUG_IPT7
        Log(SWLogger_DEBUG, L"\nPenUp final data (from ProcessSmoothDataPoint()):\n");
        Log(SWLogger_DEBUG, L" Idx [xxx, yyy] (D1X, D1Y) (D2X, D2Y) D2Sum  PLen\n");
        for (SBYTE2 i = sw_max(((SBYTE2)m_wFixedIn - (3 * DISCONTINUITY_SEARCH_LIMIT)), 0); i <= ((SBYTE2)m_wFixedIn - 1); i++)
        {
            SWFixedData *dFixed = GetFixedData(i, 62);
            Log(SWLogger_DEBUG, L" %3d [%3d, %3d] (%3d, %3d) (%3d, %3d) %4d   %4d\n", i, dFixed->m_Point.x, dFixed->m_Point.y, dFixed->D1x, dFixed->D1y, dFixed->D2x, dFixed->D2y, dFixed->D2Sum, dFixed->m_PrecisePathLen);
        }
#endif
    }

    {
        /* Use both the sign-change method and the D2Sum running average method to identify DoubleLetter gestures */
        SBYTE2 signChange1, signChange2;
        SWCIPAnalyzer_SetSignChange(pThis, pThis->m_NewSmoothPoint, pThis->m_PrevSmoothPoint, &signChange1, &signChange2);
        /* Record (and monitor) changes in sign of point-to-point changes in X and Y */
        /* in order to detect circular "Double letter" gestures */
        /* TrackSignChanges() implicitly looks at changes for point m_PrevSmoothPoint */
        SWCIPAnalyzer_TrackSignChanges(pThis, signChange1, signChange2, pThis->m_bPenUp);
    }

    pThis->m_PrevSmoothPoint = pThis->m_NewSmoothPoint;
}   /* SWCIPAnalyzer_ProcessSmoothDataPoint() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_TrackD2Changes() */
/* Parameters:  SBYTE2 D2Sum - Latest value of sum of absolute values of X and Y */
/*                  second differences */
/*              SBYTE2 prevIndex - Index of Fixed data point with value D2Sum */
/* Description: Detect when an inflection point occurs and add an Angle */
/*              IP when detected */
/*============================================================================= */

SBYTE2 ET9FARCALL SWCIPAnalyzer_TrackD2Changes(SWCIPAnalyzer *pThis, SBYTE2 D2Sum, SBYTE2 prevIndex, ET9BOOL penLifted)
{
    SWCIPTableRow   *ipData;
    SBYTE2          i, D2Sum2Limit, D2Sum2Sum, D2Sum2Av = 0;
#if DEBUG_IPT4 || DEBUG_IPT5
    _SWPoint        checkFixed = SCWIPAnalyzer_GetFixedPoint(pThis, prevIndex, 4);
#endif
    /* Calculate running average of last D2_BUFFER2_SIZE D2 Sum values */
    ET9BOOL nowPenUp = _FALSE;
    ET9_UNUSED(penLifted);
    if (prevIndex > pThis->lastTrackedAngleIndex)
    {
        pThis->D2Sums2[pThis->D2Sums2In++] = D2Sum;
        if (pThis->D2Sums2In >= D2_BUFFER2_SIZE)
        {
            pThis->D2Sums2Full = _TRUE;
            pThis->D2Sums2In = 0;
        }
        pThis->lastTrackedAngleIndex = prevIndex;
    }
    else
        nowPenUp = _TRUE;
    D2Sum2Limit = (pThis->D2Sums2Full) ? D2_BUFFER2_SIZE : pThis->D2Sums2In;
    D2Sum2Sum = D2Sum2Av = 0;
    if (D2Sum2Limit > 0)
    {
    for (i = 0; i < D2Sum2Limit; i++)
        D2Sum2Sum = (SBYTE2)(D2Sum2Sum + pThis->D2Sums2[i]);
    D2Sum2Av = D2Sum2Sum / D2Sum2Limit;
    }
    pThis->lastTrackedAngleIndex = prevIndex;
    if (nowPenUp)       /* Must be PenUp - current index was already tracked */
        return(D2Sum2Av);

    if (pThis->m_bD2ThresholdExceeded)     /* If a "true" Angle IP has been detected */
    {
        if (D2Sum > pThis->m_wD2LocalMax)          /* Record new max D2 value */
        {
            pThis->m_wD2Index = prevIndex;
            pThis->m_wD2LocalMax = D2Sum;
            pThis->m_wD2LocalThreshold = pThis->m_wD2LocalMax >> 1;           /*lint !e702 */
        }
            /* Still above below lower threshold, but less than half of local max */
        else if ((D2Sum >= pThis->m_wD2LowerThreshold) && (D2Sum < pThis->m_wD2LocalThreshold) &&
                  (pThis->m_wD2Index != pThis->m_wLastAddedD2Index))
        {
            SWFixedData *IPLoc = SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wD2Index, 47);
            ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Angle, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                    pThis->m_wD2Index, pThis->m_wD2Index, 10, _TRUE);
            SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
            pThis->m_wLastAddedD2Index = pThis->m_wD2Index;
            /* Set m_wD2LocalMax equal to default m_wD2AngleThreshold, and accept new IP if one is */
            /* found that exceeds this threshold */
            pThis->m_wD2LocalMax = pThis->m_wD2AngleThreshold;
        }
        else if (D2Sum < pThis->m_wD2LowerThreshold)   /* Fell back below lower threshold */
        {
            pThis->m_bD2ThresholdExceeded = _FALSE;     /* Reset flag */
            /* If this IP has not already been added, then add it now */
            if (pThis->m_wD2Index != pThis->m_wLastAddedD2Index)
            {
                SWFixedData *IPLoc = SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wD2Index, 48);
                ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Angle, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                        pThis->m_wD2Index, pThis->m_wD2Index, 11, _TRUE);
                SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
                pThis->m_wLastAddedD2Index = pThis->m_wD2Index;
                pThis->m_wD2LocalThreshold = 0;
            }
        }
    }
    else if (D2Sum >= pThis->m_wD2AngleThreshold)
    {
        pThis->m_bD2ThresholdExceeded = _TRUE;
        pThis->m_wD2Index = prevIndex;
        pThis->m_wD2LocalMax = D2Sum;
        pThis->m_wD2LocalThreshold = pThis->m_wD2LocalMax >> 1;           /*lint !e702 */
    }

    return(D2Sum2Av);
}   /* SWCIPAnalyzer_TrackD2Changes() */



/*============================================================================= */
/* Function:    SWCIPAnalyzer_TrackDurationChanges() */
/* Parameters:  BYTE4 FixedDuration - time interval from timestamp of preceding */
/*                  Fixed point to that of following Fixed point */
/*              SBYTE2 prevIndex - Index of Fixed point whose duration is being processed */
/* Description: Detect when a Pause occurs and add a Pause IP when detected */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_TrackDurationChanges(SWCIPAnalyzer *pThis, BYTE4 FixedDuration, SBYTE2 prevIndex)
{
    SWCIPTableRow *ipData;

    if (pThis->m_bPauseThresholdExceeded)
    {
        if (FixedDuration > pThis->m_dwPauseLocalMax)
        {
            pThis->m_wPauseIndex = prevIndex;
            pThis->m_dwPauseLocalMax = FixedDuration;
            pThis->m_dwTotalPauses += pThis->m_wSavedDurationPrev;
            pThis->m_dwPauseLocalThreshold = pThis->m_dwPauseLocalMax >> 1;
        }
        /* Still above lower threshold, but less than half of local max */
        else if ((FixedDuration >= pThis->m_dwPauseThreshold) && (FixedDuration < pThis->m_dwPauseLocalThreshold) &&
                  (pThis->m_wPauseIPAdded != pThis->m_wPauseIndex))
        {
            SWFixedData *IPLoc = SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wPauseIndex, 49);
            ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Pause, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                        pThis->m_wPauseIndex, pThis->m_wPauseIndex, 13, _TRUE);
            SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
            pThis->m_dwTotalPauses += pThis->m_dwPauseLocalMax;
            /* Set m_dwPauseLocalMax equal to m_dwPauseLocalThreshold, and accept new IP if one is */
            /* found above that exceeds this value */
            pThis->m_dwPauseLocalMax = pThis->m_dwPauseLocalThreshold;
            /* Flag the fact that the current detected pause has already been added */
            pThis->m_wPauseIPAdded = pThis->m_wPauseIndex;
        }
        else if (FixedDuration < pThis->m_dwPauseThreshold)    /* Fell back below lower threshold */
        {
            pThis->m_bPauseThresholdExceeded = _FALSE;      /* Reset flag */
            /* If this IP has not already been added, then add it now */
            if (pThis->m_wPauseIPAdded != pThis->m_wPauseIndex)
            {
                SWFixedData *IPLoc = SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wPauseIndex, 50);
                ipData = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, IPLoc->m_Point, Pause, SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable), IPLoc->m_TimeOffset,
                                            pThis->m_wPauseIndex, pThis->m_wPauseIndex, 14, _TRUE);
                SWCIPTable_AddIPtoTable(pThis->m_backend->pIPTable, ipData);
                pThis->m_dwTotalPauses += pThis->m_dwPauseLocalMax;
                pThis->m_dwPauseLocalThreshold = 0;
            }
        }
    }
    else if (FixedDuration >= pThis->m_dwPauseThreshold)
    {
        pThis->m_bPauseThresholdExceeded = _TRUE;
        pThis->m_wPauseIndex = prevIndex;
        pThis->m_dwPauseLocalMax = FixedDuration;
        pThis->m_dwPauseLocalThreshold = pThis->m_dwPauseLocalMax >> 1;
        pThis->m_wSavedDurationPrev = 0;
        pThis->m_wPauseIPAdded = 0;
    }
} /* SWCIPAnalyzer_TrackDurationChanges() */


/*============================================================================= */
/* Function:    SWCIPAnalyzer::TrackSignChanges() */
/* Parameters:  (Implicit) m_PrevSmoothPoint - Data point location where */
/*                  sign change occurred */
/*              SBYTE2 signChange1, signChange2 - Flags for the patterns detected */
/*                  in the changes in the sign of the point-to-point changes in */
/*                  the X and Y coordinates.  signChange1 is set to one of the */
/*                  values {-2, -1, 1, 2}.  signChange2 may also be set to one of */
/*                  those values, or signChange2 may be 0. */
/*              bool penLifted - If True, check for valid sequence for closing */
/*              DoubleLetter gesture */
/* Description: Record when and where changes in sign have occurred.  When a */
/*              sequence of sign changes is detected that correspond to a */
/*              Double Letter gesture, add a Double Letter IP to the IP table. */
/*              Check first (in AddIPtoTable())for a prematurely added Angle IP */
/*              at approximately the same time and location, and delete it if found. */
/*============================================================================= */

void ET9FARCALL SWCIPAnalyzer_TrackSignChanges(SWCIPAnalyzer *pThis, SBYTE2 signChange1, SBYTE2 signChange2, ET9BOOL penLifted)
{
    ET9BOOL changeOK = _TRUE, generateNow = _FALSE;
    BYTE2   gestureWidth, gestureHeight;
    SBYTE2  lastFixedIndex = (SBYTE2)(pThis->wFixedDataCount - 1);
    _SWPoint changePoint;
    SWCIPTableRow *ipDouble = NULL;
    SWDbm* pDbm = pThis->m_backend->m_pDbm;

    if (pThis->m_PrevSmoothPoint == NULL)
        return;

    changePoint.x = (SBYTE2)pThis->m_PrevSmoothPoint->smoothPoint.x;
    changePoint.y = (SBYTE2)pThis->m_PrevSmoothPoint->smoothPoint.y;

#if DEBUG_IPT2
    {
        SBYTE2  i;
        if ((signChange1 != 0) || penLifted)
        {
            if (penLifted)
            {
                Log(SWLogger_DEBUG, L" (PenUp!) signChange[%d/%d]: ", pThis->m_wSignChangeIn, pThis->m_wGestureTurns);
            }
            else
                Log(SWLogger_DEBUG, L" ######## signChange[%d/%d]: ", pThis->m_wSignChangeIn, pThis->m_wGestureTurns);
            for (i = 0; i < m_wSignChangeIn; i++)
                Log(SWLogger_DEBUG, L" %d ", m_wSignChanges[i]);
            Log(SWLogger_DEBUG, L"   + %d (%d) [(%d, %d) at %d]\n", signChange1, signChange2, changePoint.x, changePoint.y, lastFixedIndex);
        }
#if DEBUG_IPT3
        else
            Log(SWLogger_DEBUG, L" No change: [(%d, %d) at %d]\n", changePoint.x, changePoint.y, lastFixedIndex);
#endif
    }
#endif
    /* If one or more sign changes previously saved, or if un-processed gesture was detected by alternate algorithm prior to PenUp */
    if (pThis->m_wSignChangeIn > 0)
    {
        if (!pThis->gestureClosed)     /* Check whether gesture has been "closed" (in a loop) yet */
        {
            BYTE2  thisDistance = _SWPoint_distance(&pThis->m_SignChangeOrigin, &changePoint);
            BYTE2  closureThreshold = DOUBLE_GESTURE_CLOSURE_DISTANCE;      /* Init to default value */
            if (pThis->thisGestureRadius > closureThreshold)
                closureThreshold = pThis->thisGestureRadius;
            if (thisDistance < pThis->lastClosureDistance)     /* Wait till we start doubling back on origin point */
            {
                BYTE2  thisDistance2 = _SWPoint_distance(&pThis->m_SignChangeOrigin2, &changePoint);
                thisDistance2 = sw_min(thisDistance, thisDistance2);
                if (thisDistance2 <= closureThreshold)
                {
                    pThis->gestureClosed = _TRUE;
                    if (changePoint.x < pThis->m_wSignChangeMinX)
                        pThis->m_wSignChangeMinX = changePoint.x;
                    else if (changePoint.x > pThis->m_wSignChangeMaxX)
                        pThis->m_wSignChangeMaxX = changePoint.x;
                    if (changePoint.y < pThis->m_wSignChangeMinY)
                        pThis->m_wSignChangeMinY = changePoint.y;
                    else if (changePoint.y > pThis->m_wSignChangeMaxY)
                        pThis->m_wSignChangeMaxY = changePoint.y;
                    gestureWidth = (BYTE2)sw_max(pThis->m_wSignChangeMaxX - pThis->m_wSignChangeMinX, 1);
                    gestureHeight = (BYTE2)sw_max(pThis->m_wSignChangeMaxY - pThis->m_wSignChangeMinY, 1);
                    pThis->gestureClosedRatio = (ET9FLOAT)gestureWidth / (ET9FLOAT)gestureHeight;
                    if (pThis->gestureClosedRatio > (ET9FLOAT)1.0)
                        pThis->gestureClosedRatio = (ET9FLOAT)1.0 / pThis->gestureClosedRatio;
                    /* Also, compute average distance from gesture center and variance? Circle = implies larger av. distance with smaller variance */
                    /* Should get good discrimination from (av. / variance)? */
#if DEBUG_IPT2
                    if (thisGestureRadius > DOUBLE_GESTURE_CLOSURE_DISTANCE)
                    {
                        Log(SWLogger_DEBUG, L" Gesture closed at [(%d, %d) at %d]. Threshold (radius) = %d\n", changePoint.x, changePoint.y, lastFixedIndex, closureThreshold);
                    }
                    else
                        Log(SWLogger_DEBUG, L" Gesture closed at [(%d, %d) at %d]. Default Threshold = %d  (Radius = %d)\n", changePoint.x, changePoint.y, lastFixedIndex, closureThreshold, thisGestureRadius);
                    Log(SWLogger_DEBUG, L" gestureClosedRatio = %f  (W: %d  H: %d))\n", gestureClosedRatio, gestureWidth, gestureHeight);
#endif
                }
            }
            pThis->lastClosureDistance = thisDistance;
        }
        if (!penLifted)
        {
            /* If no sign change reported, check whether pen has moved away from gesture location */
            if (signChange1 == 0)
            {
                if (pThis->m_ShiftGestured)
                    changeOK = (ET9BOOL)(changePoint.y < SWDbm_keyboardUpperBound(pDbm));
                else
                {
                    changeOK = (ET9BOOL)(((lastFixedIndex - pThis->m_wSignChangeFixedIndex2) <= DOUBLE_GESTURE_FIXED_LIMIT) && (changePoint.y >= (SWDbm_keyboardUpperBound(pDbm) - DOUBLE_GESTURE_MARGIN)));
                    generateNow = !changeOK;
#if DEBUG_IPT2
                    if (!changeOK)
                    {
                        if ((lastFixedIndex - pThis->m_wSignChangeFixedIndex2) > DOUBLE_GESTURE_FIXED_LIMIT)
                        {
                            Log(SWLogger_DEBUG, L" !changeOK: Distance from last change = %d\n", (lastFixedIndex - pThis->m_wSignChangeFixedIndex2));
                        }
                        else
                            Log(SWLogger_DEBUG, L" !changeOK: changePoint.y = %d\n", changePoint.y);
                    }
#endif
                }
            }
            else
            {
                if (pThis->m_ShiftGestured)
                    changeOK = (ET9BOOL)(changePoint.y < SWDbm_keyboardUpperBound(pDbm));
                else
                    changeOK = (ET9BOOL)(changePoint.y >= (SWDbm_keyboardUpperBound(pDbm) - DOUBLE_GESTURE_MARGIN));
#if DEBUG_IPT2
                if (!changeOK)
                    Log(SWLogger_DEBUG, L" !changeOK: changePoint.y = %d\n", changePoint.y);
#endif
                if (changeOK)
                {
                    /* Test validity of reported sign change for possible double-letter sequence */
                    /* signChange value sequence must alternate between odd and even */
                    changeOK = (ET9BOOL)(((pThis->m_wSignChanges[pThis->m_wSignChangeIn - 1] ^ signChange1) & 0x01) ||
                        (changePoint.y == pThis->m_wSignChangeMinY));    /* We've hit the topmost coordinate that the client sees */
#if DEBUG_IPT2
                    if (!changeOK)
                        Log(SWLogger_DEBUG, L" !changeOK: odd/even bad\n", m_SignChangeCenter.distance(changePoint));
#endif
                }
                if (changeOK && (pThis->m_wSignChangeIn > 1))    /* If at least two sign changes previously saved */
                {
                    /* Every other change must be equal in magnitude and opposite in sign */
                    changeOK = (ET9BOOL)(((pThis->m_wSignChanges[pThis->m_wSignChangeIn - 2] + signChange1) == 0) ||
                        (changePoint.y == pThis->m_wSignChangeMinY));    /* We've hit the topmost coordinate that the client sees */
#if DEBUG_IPT2
                    if (!changeOK)
                        Log(SWLogger_DEBUG, L" !changeOK: magnitude & sign bad\n", m_SignChangeCenter.distance(changePoint));
#endif
                    if (changeOK && signChange2)
                    {
                        changeOK = (ET9BOOL)(((pThis->m_wSignChanges[pThis->m_wSignChangeIn - 1] + signChange2) == 0) ||
                            (changePoint.y == pThis->m_wSignChangeMinY));    /* We've hit the topmost coordinate that the client sees */
#if DEBUG_IPT2
                        if (!changeOK)
                            Log(SWLogger_DEBUG, L" !changeOK: magnitude & sign bad (2)\n", pThis->m_SignChangeCenter.distance(changePoint));
#endif
                    }
                }
                if (changeOK && !pThis->m_ShiftGestured)       /* CAPS-LOCK gesture can be large */
                {
                    /* Each change location in the sequence must be within threshold distance of center */
                    ET9BOOL closeEnough;
                    if (pThis->m_wSignChangeIn >= 3)   /* If 3 or more sign changes previously saved */
                    {
                        closeEnough = (ET9BOOL)(_SWPoint_distance(&pThis->m_SignChangeCenter, &changePoint) <= pThis->thisGestureRadiusThreshold);
#if DEBUG_IPT2
                        if (!closeEnough)
                            Log(SWLogger_DEBUG, L" SWPoint distance %d > radius threshold %d [at %d]\n", pThis->m_SignChangeCenter.distance(changePoint), thisGestureRadiusThreshold, lastFixedIndex);
#endif
                    }
                    else if (pThis->m_wSignChangeIn == 2)  /* If exactly two sign changes previously saved */
                    {
                        _SWPoint checkCenter;
                        checkCenter.x = ((2 * pThis->m_SignChangeCenter.x) + changePoint.x) / 3;
                        checkCenter.y = ((2 * pThis->m_SignChangeCenter.y) + changePoint.y) / 3;
                        closeEnough = (ET9BOOL)(_SWPoint_distance(&checkCenter, &changePoint) <= DOUBLE_GESTURE_MAX_RADIUS);
#if DEBUG_IPT2
                        if (!closeEnough)
                            Log(SWLogger_DEBUG, L" Gesture radius too large = %d from center (%d, %d) to [(%d, %d) at %d]\n", checkCenter.distance(changePoint),
                                                        checkCenter.x, checkCenter.y, changePoint.x, changePoint.y, lastFixedIndex);
#endif
                    }
                    else
                    {
                        closeEnough = (ET9BOOL)(_SWPoint_distance(&pThis->m_SignChangeCenter, &changePoint) < DOUBLE_GESTURE_MAX_DIAM);
#if DEBUG_IPT2
                        if (!closeEnough)
                            Log(SWLogger_DEBUG, L" Gesture diameter too large = %d [at %d]\n", pThis->m_SignChangeCenter.distance(changePoint), lastFixedIndex);
#endif
                    }
                    if (!closeEnough)       /* Can't continue if we left the scene... */
                        generateNow = _TRUE;         /* Leave changeOK set to true so that this point is included */
                }
            }
        }
        {
            ET9BOOL enoughTurns;
            ET9BOOL endOfAllCapsGesture;
            if ((changeOK && (signChange1 != 0)))     /* Current sign change OK but not yet added to buffer */
                enoughTurns = (ET9BOOL)((pThis->m_wSignChangeIn >= (DOUBLE_GESTURE_TURNS - 1)) || (pThis->m_wGestureTurns > 0));
            else
                enoughTurns = (ET9BOOL)((pThis->m_wSignChangeIn >= DOUBLE_GESTURE_TURNS) || (pThis->m_wGestureTurns > 0));

            endOfAllCapsGesture = (ET9BOOL)( enoughTurns &&
                                        (pThis->m_SignChangeCenter.y < ALL_CAP_GESTURE_MARGIN) &&
                                        (changePoint.y < (SWDbm_keyboardUpperBound(pDbm) + ALL_CAP_GESTURE_CLOSURE_MARGIN))
                                       );
            if (penLifted || !changeOK || endOfAllCapsGesture)
            {
                generateNow = _TRUE; /* Force check for completed gesture */
            }

            if (generateNow)
            {
                ET9BOOL gestureAboveKeyboard;
                ET9BOOL signChangesOK;
                ET9BOOL checkD2Sum2AvDoubleGesture;
#if DEBUG_IPT2
                Log(SWLogger_DEBUG, L" generateNow == true!\n");
#endif
                if (changeOK && (signChange1 != 0))     /* Current sign change OK but not yet added to buffer */
                {
                    /*enoughTurns = ((m_wSignChangeIn >= (DOUBLE_GESTURE_TURNS - 1)) || (m_wGestureTurns > 0)); */
                    pThis->m_wSignChangeFixedIndex2 = lastFixedIndex;
                    changeOK = _FALSE;       /* Current sign change used in current gesture - Don't add to empty buffer */
                }
                else
                {
                    /*enoughTurns = ((pThis->m_wSignChangeIn >= DOUBLE_GESTURE_TURNS) || (pThis->m_wGestureTurns > 0)); */
                    changeOK = _TRUE;        /* OK to add current change as first in now-empty buffer */
                }
                if ((pThis->m_wGestureTurns >= 2) || ((pThis->m_wGestureTurns > 0) && (pThis->m_wSignChangeIn >= (DOUBLE_GESTURE_TURNS / 2))))
                {
                    SBYTE2 penDownIndex = 0;
                    SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, &penDownIndex)->m_ForceShowWCW = _TRUE;                      /* Force showing of WCW for 1-letter words when multiple DoubleLetter gesture loops performed */
                }
                    /* Before creating the IP, check whether it has been created (more or less) completely above the keyboard. */
                    /* FixedIndexOut location may be within the keyboard since the path may loop down to the next key. */
                gestureAboveKeyboard = (ET9BOOL)((pThis->m_SignChangeCenter.y < ALL_CAP_GESTURE_MARGIN) ||
                                             ((pThis->m_SignChangeCenter.y < SWDbm_keyboardUpperBound(pDbm)) &&
                                              (SWCIPAnalyzer_GetFixedData(pThis, pThis->m_wSignChangeFixedIndex1, 53)->m_Point.y < SWDbm_keyboardUpperBound(pDbm))));
                signChangesOK = (ET9BOOL)(enoughTurns && ((pThis->gestureClosed && pThis->gestureLargeEnough /* && gestureSmallEnough (we're checking "closeEnough" above...)??? */)
                                                     || gestureAboveKeyboard) );
                checkD2Sum2AvDoubleGesture = _FALSE;
                /* Reset counters now since we may have a race condition with a following Double-IP */
                pThis->m_wBadSignChangeFixedIndex = -1;
                pThis->m_wBadSignChangeCount = 0;
                pThis->m_wGestureTurns = 0;
                pThis->m_wSignChangeIn = 0;    /* Reset so current/following change(s) saved at start of tracking array */
                pThis->m_wSignChange2Cnt = 0;  /* Count number of non-zero signChange2 occurrences for m_SignChangeCenter computation */

                gestureWidth = (BYTE2)sw_max(pThis->m_wSignChangeMaxX - pThis->m_wSignChangeMinX, 1);
                gestureHeight = (BYTE2)sw_max(pThis->m_wSignChangeMaxY - pThis->m_wSignChangeMinY, 1);
                if (gestureAboveKeyboard && endOfAllCapsGesture)
                {
                    if ((gestureWidth > DOUBLE_GESTURE_MIN_DIAM) &&
                        (gestureHeight < (DOUBLE_GESTURE_MIN_DIAM / 4)))
                    {
                        /* Declare it a squiggle all-caps gesture */
                        SWCIPTable_SetShiftAll(pThis->m_backend->pIPTable, _TRUE);
                    }
                }
                {
                    BYTE2  gestureDiameter = sw_max(gestureWidth, gestureHeight);
                    /* Use ratio of gesture width to height to adjust skipping penalties */
                    ET9FLOAT  gestureRatio = (ET9FLOAT)gestureHeight / (ET9FLOAT)gestureWidth;
                    SBYTE4    avRadius = (SBYTE4)(gestureDiameter / 2);       /* Approximation to ensure value is defined for debug output... */
                    SBYTE4    nData = (SBYTE4)pThis->wChangeDataCount;
                    ET9FLOAT  circleScore = (ET9FLOAT)0.0;       /* No penalties for failing to Double-match an alternate algorithm gesture. */
                    ET9FLOAT  stdDev = (ET9FLOAT)0.0;
                    if (gestureRatio > (ET9FLOAT)1.0)
                        gestureRatio = (ET9FLOAT)1.0 / gestureRatio;
                    if (signChangesOK)      /* Calculate "figure of merit" for circle gestures; use to adjust non-matching penalties */
                    {
                        if ((nData > 2) && enoughTurns)
                        {
                            SWCIPAnalyzer_AnalyzeChangeData(pThis, &avRadius, &stdDev);
                            if (avRadius < (SBYTE4)(gestureDiameter * 8))       /* Sanity check... */
                            {
                                ET9FLOAT shapeFactor;
                                ET9FLOAT shapeFactor2;
                                shapeFactor = (ET9FLOAT)1.0 - (((ET9FLOAT)2.0 * stdDev) / avRadius);
                                if (shapeFactor < MIN_SHAPE_FACTOR)
                                    shapeFactor = MIN_SHAPE_FACTOR;
                                /* Should have diameter approximately equal to 2 * radius */
                                shapeFactor2 = (ET9FLOAT)_ET9fabsf((ET9FLOAT)1.0 - ((ET9FLOAT)(gestureDiameter * 4)/avRadius));     /* Small if diameter close to 2 * radius */
                                shapeFactor2 = (ET9FLOAT)1.0 - shapeFactor2;
                                if (shapeFactor2 < MIN_SHAPE_FACTOR2)
                                    shapeFactor2 = MIN_SHAPE_FACTOR2;
                                circleScore = gestureRatio * shapeFactor * shapeFactor2 * (ET9FLOAT)1.75;
                                if (circleScore > (ET9FLOAT)1.0)
                                    circleScore = (ET9FLOAT)1.0;
                            }
                            else
                                signChangesOK = _FALSE;
                        }
                        else    /* Not enough data points for analysis.  Use conservatively small gestureRatio for penalties. */
                        {
                            circleScore = gestureRatio * (ET9FLOAT)0.25;
                        }
                    }
#if DEBUG_IPT5 || DEBUG_IPT2
                    if (signChangesOK)
                    {
                        Log(SWLogger_DEBUG, L" =====>   Check Circle Gesture VALID at [%d, %d] (%d points)  Radius8/Diam: %d/%d  StdDev: %f  Score: %f\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, nData, avRadius, gestureDiameter, stdDev, circleScore);
                    }
                    else if (checkD2Sum2AvDoubleGesture)
                    {
                        Log(SWLogger_DEBUG, L" =====>   Check D2Sum Gesture VALID at [%d, %d] (%d points)  Radius8/Diam: %d/%d  StdDev: %f  Score: %f\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, nData, avRadius, gestureDiameter, stdDev, circleScore);
                    }
                    else
                    {
                        Log(SWLogger_DEBUG, L" =====>   Circle/D2Sum Gestures BOTH INVALID at [%d, %d] (%d points)  Radius8/Diam: %d/%d  StdDev: %f  Score: %f\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, nData, avRadius, gestureDiameter, stdDev, circleScore);
                    }
#endif
                    pThis->wChangeDataCount = 0;
                    /* Add Double Letter IP if valid gesture detected: Enough turns, gesture loop has been closed, and large enough */
                    if (signChangesOK || checkD2Sum2AvDoubleGesture
                        /* and not overly flattened: i.e. Width < 3.0 * Height;  and not overly skinny: i.e. Height < 2.5 * Width */
                        /*  && (gestureWidth < (3 * gestureHeight)) && ((2 * gestureHeight) < (5 * gestureWidth)) */
                        )
                    {
                        /* Use middle of sign change sequence as representative Fixed Index for DoubleLetter IP */
                        /*SBYTE2 wSignChangeFixedIndexAv = (pThis->m_wSignChangeFixedIndex1 + pThis->m_wSignChangeFixedIndex2) / 2; */

                        /* Start at m_wSignChangeFixedIndex1/m_wSignChangeFixedIndex2, and work outward in both directions */
                        /*   until a point is reached that is twice the radius of the gesture from the center of the gesture. */
                        /*   Then back up to the last location within slightly more than one gesture-radius from the center to find */
                        /*   points that should approximate where the path first touches (tangentially) the circular gesture. */
                        SBYTE2 FixedIndexIn = pThis->m_wSignChangeFixedIndex1;
                        SBYTE2 FixedIndexOut = pThis->m_wSignChangeFixedIndex2;
#if DEBUG_IPT5
                        if (signChangesOK)
                            Log(SWLogger_DEBUG, L" =====>   Circle Gesture at [%d, %d]  (%d points)  Radius8/Diam: %d/%d  StdDev: %f  ratio: %f  Score: %f\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, nData, avRadius, gestureDiameter, stdDev, gestureRatio, circleScore);
#endif
                        BYTE2  thisDistance;
                        BYTE2  closeEnough = (gestureDiameter * 2) / 3;
                        SBYTE2 lastCloseIndex = FixedIndexIn;
                        SBYTE2 FixedIndexInLimit = sw_max(0, (pThis->m_wSignChangeFixedIndex1 - DOUBLE_ANGLE_INCLUDE_MARGIN1));
                        if (pThis->m_backend->pIPTable->m_wDoubleIPCount > 0)
                        {
                            SWCIPTableRow *ipLastDouble = SWCIPTable_GetIPTable2Row(pThis->m_backend->pIPTable, pThis->m_backend->pIPTable->m_wDoubleIPCount - 1);       /* June 26, 2009    TODO !!!! This pointer is invalid as it is not protected by the table's mutex. */
                            SBYTE2 lastIndexOutLimit = ipLastDouble->m_FixedIndexOut + DOUBLE_ANGLE_INCLUDE_MARGIN2;    /* Avoid conflicts over included IPs */
                            if (FixedIndexInLimit < lastIndexOutLimit)
                            {
                                if (FixedIndexIn >= ipLastDouble->m_FixedIndexOut)
                                    FixedIndexInLimit = (FixedIndexIn + ipLastDouble->m_FixedIndexOut) / 2;
                                else
                                {
                                    /*AlwaysAssertUnreached(); */
                                    AlwaysAssert(checkD2Sum2AvDoubleGesture);
                                    FixedIndexIn = FixedIndexInLimit = ipLastDouble->m_FixedIndexOut + 1;
                                }
                            }
                        }
                        while ((FixedIndexIn > FixedIndexInLimit) && ((thisDistance = _SWPoint_distance(&SWCIPAnalyzer_GetFixedData(pThis, FixedIndexIn, 54)->m_Point, &pThis->m_SignChangeCenter)) < gestureDiameter))
                        {
                            if (thisDistance <= closeEnough)
                                lastCloseIndex = FixedIndexIn;
                            FixedIndexIn--;
                        }
                        FixedIndexIn = lastCloseIndex;
                        lastCloseIndex = FixedIndexOut;
                        while ((FixedIndexOut < lastFixedIndex) && ((thisDistance = _SWPoint_distance(&SWCIPAnalyzer_GetFixedData(pThis, FixedIndexOut, 55)->m_Point, &pThis->m_SignChangeCenter)) < gestureDiameter))
                        {
                            if (thisDistance <= closeEnough)
                                lastCloseIndex = FixedIndexOut;
                            FixedIndexOut++;
                        }
                        FixedIndexOut = lastCloseIndex;
                        /* If the Double IP has been created (more or less) completely above the keyboard, then recognize it */
                        /*   as a valid CAPsLock gesture.  No more processing required, since it will not be matched against any keys. */
                        if (gestureAboveKeyboard)
                        {
                            /*// Use time Offset of first Fixed point determined to be part of the gesture */
                            SWCIPTable_SetShiftAll(pThis->m_backend->pIPTable, _TRUE);
                        }
                        /* Before creating a sequence of Angle IPs from the detected DoubleLetter IP, make sure it is valid */
                        else if (FixedIndexOut >= (FixedIndexIn + DOUBLE_GESTURE_MIN_INTERVALS))
                        {
                            SBYTE2  gestureStart = FixedIndexIn - DOUBLE_ANGLE_INCLUDE_MARGIN1;
                            SBYTE2  gestureEnd = FixedIndexOut + DOUBLE_ANGLE_INCLUDE_MARGIN2;
                            ET9BOOL addThisDoubleIP = _TRUE;
                            /* Make sure this is not a repeated detection of an already-detected Double letter gesture */
                            SBYTE2  firstCheck = pThis->m_backend->pIPTable->m_wDoubleIPCount - 1;
                            SBYTE2 doubleIndex;
                            /* This loop is here because firstCheck was set to 0 if GENERATE_D2SUM_DOUBLE_IPS was turned on. */
                            for (doubleIndex = firstCheck; doubleIndex < pThis->m_backend->pIPTable->m_wDoubleIPCount; doubleIndex++)
                            {
                                ipDouble = SWCIPTable_GetIPTable2Row(pThis->m_backend->pIPTable, doubleIndex);              /* June 26, 2009    TODO !!!! This pointer is invalid as it is not protected by the table's mutex. */
                                if (ipDouble != NULL)
                                {
                                    ET9BOOL overLap = (ET9BOOL)((FixedIndexIn <= (ipDouble->m_FixedIndexOut + DOUBLE_DOUBLE_MIN_INTERVALS)) &&
                                                    (FixedIndexOut >= (ipDouble->m_FixedIndexIn - DOUBLE_DOUBLE_MIN_INTERVALS)) &&
                                                    (ipDouble->m_FixedIndexIn <= (FixedIndexOut + DOUBLE_DOUBLE_MIN_INTERVALS)) &&
                                                    (ipDouble->m_FixedIndexOut >= (FixedIndexIn - DOUBLE_DOUBLE_MIN_INTERVALS)) );
                                    if ((_SWPoint_distance(&ipDouble->gestureLoc, &pThis->m_SignChangeCenter) < DOUBLE_DOUBLE_MIN_DISTANCE) &&
                                        SWCIPTable_PointsInSameRow(pThis->m_backend->pIPTable, &ipDouble->gestureLoc.y, pThis->m_SignChangeCenter.y, 0, _FALSE) &&
                                        overLap)
                                        addThisDoubleIP = _FALSE;
                                }
                            }
                            if (addThisDoubleIP)
                            {
                                /* Use time Offset of first Fixed point determined to be part of the gesture */
                                ipDouble = SWCIPTable_NewIPTableRow(pThis->m_backend->pIPTable, pThis->m_SignChangeCenter, DoubleLetter,
                                    pThis->m_backend->pIPTable->m_wDoubleIPCount + 1, SWCIPAnalyzer_GetFixedData(pThis, FixedIndexIn, 57)->m_TimeOffset,
                                    FixedIndexIn, FixedIndexOut, 17, _FALSE);
                                ipDouble->gestureLoc = pThis->m_SignChangeCenter;
                                ipDouble->gestureRatio = gestureRatio;
                                ipDouble->circleScore = circleScore;
                                ipDouble->signChangesOK = signChangesOK;
                                ipDouble->minX = pThis->m_wSignChangeMinX;         /* Record for now.  Not currently referenced... */
                                ipDouble->maxX = pThis->m_wSignChangeMaxX;
                                ipDouble->minY = pThis->m_wSignChangeMinY;
                                ipDouble->maxY = pThis->m_wSignChangeMaxY;
                                if (SWCIPTable_AddIPtoTable2(pThis->m_backend->pIPTable, ipDouble) == IPT_FAILURE) {
                                    addThisDoubleIP = _FALSE;
                                    /*pThis->m_backend->pIPTable->wIPPoolCount--; */
                                }
                            }
                            else if (!checkD2Sum2AvDoubleGesture)   /* Update FixedIndexOut and gestureLoc of previously existing Double IP if it is being extended */
                            {
#if DEBUG_IPT5
                                if (signChangesOK)
                                {
                                    Log(SWLogger_DEBUG, L" =====>   Circle Gesture at [%d, %d] MERGED with previous gesture at [%d, %d]\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, ipDouble->gestureLoc.x, ipDouble->gestureLoc.y);
                                }
                                else
                                {
                                    Log(SWLogger_DEBUG, L" =====>   D2Sum Gesture at [%d, %d] MERGED with previous gesture at [%d, %d]\n", pThis->m_SignChangeCenter.x, pThis->m_SignChangeCenter.y, ipDouble->gestureLoc.x, ipDouble->gestureLoc.y);
                                }
#endif
                                ipDouble->m_FixedIndexOut = FixedIndexOut;
                                ipDouble->m_IPPosIndex = (ipDouble->m_FixedIndexIn + FixedIndexOut) / 2;
                                ipDouble->gestureLoc.x = (ipDouble->gestureLoc.x + pThis->m_SignChangeCenter.x) / 2;
                                ipDouble->gestureLoc.y = (ipDouble->gestureLoc.y + pThis->m_SignChangeCenter.y) / 2;
                                FixedIndexIn = ipDouble->m_FixedIndexIn;
                                gestureStart = ipDouble->m_FixedIndexIn - DOUBLE_ANGLE_INCLUDE_MARGIN1;
                            }
                            if (penLifted && (addThisDoubleIP || !checkD2Sum2AvDoubleGesture))
                            {
                                if (FixedIndexOut >= lastFixedIndex - 1)
                                    SWCIPTable_SetPenUpDoubleIPIndex(pThis->m_backend->pIPTable, pThis->m_backend->pIPTable->m_wDoubleIPCount);
#if DEBUG_IPT2
                                else
                                    Log(SWLogger_DEBUG, L" PenUp NOT included: FixedIndexOut: %d  lastFixedIndex: %d\n", FixedIndexOut, lastFixedIndex);
#endif
                            }
                            if (addThisDoubleIP)
                            {
                                SBYTE2 index;
                                for (index = 0; index < SWCIPTable_GetIPTableSize(pThis->m_backend->pIPTable); index++)
                                {
                                    ET9BOOL includeIP;
                                    SWCIPTableRow *ipData = SWCIPTable_GetIPTableRow(pThis->m_backend->pIPTable, &index);
                                    if (ipData->m_DoubleIPIndex != 0)       /* Don't consider IP if already part of a gesture */
                                        continue;
                                    if ((ipData->m_IPType != Angle) && (ipData->m_IPType != PauseAngle) && (ipData->m_IPType != Multiple) && (ipData->m_IPType != Pause)
                                        && (ipData->m_IPType != PenDown))
                                        continue;                           /* Ignore if not valid candidate type for inclusion */
                                    if (ipData->m_IPType != PenDown)
                                    {
                                        BYTE2 distLimit = (BYTE2)sw_max((BYTE2)DOUBLE_ANGLE_INCLUDE_DISTANCE, pThis->thisGestureRadiusThreshold);
                                        includeIP = (ET9BOOL)((ipData->m_FixedIndexIn >= gestureStart) && (ipData->m_FixedIndexOut <= gestureEnd) &&
                                                        (_SWPoint_distance(&ipDouble->gestureLoc, &ipData->m_IPLocation) <= distLimit));
#if DEBUG_IPT2
                                        if (!includeIP)
                                        {
                                            Log(SWLogger_DEBUG, L" EXCLUDED type %d IP[%d] at [%d, %d] {%d} from gesture %d. ", ipData->m_IPType, ipData->m_IPIndex,
                                                ipData->m_IPLocation.x, ipData->m_IPLocation.y, ipData->m_IPPosIndex, SWCIPTable_GetIPTable2Size(pThis->m_backend->pIPTable));
                                            SBYTE2 Limit = DOUBLE_ANGLE_INCLUDE_DISTANCE;
                                            Log(SWLogger_DEBUG, L" FixedIn >= gestStart? (%d : %d)   FixedOut <= gestEnd? (%d : %d)   Dist < Limit/GestRadThr? (%d : %d/%d)\n", ipData->m_FixedIndexIn, gestureStart,
                                                ipData->m_FixedIndexOut, gestureEnd, _SWPoint_distance(&ipDouble->gestureLoc, &ipData->m_IPLocation), Limit, pThis->thisGestureRadiusThreshold);
                                        }
#endif
                                    }
                                    else    /* Additional margin to include penDown */
                                        includeIP = (ET9BOOL)((ipData->m_FixedIndexIn > (FixedIndexIn - DOUBLE_PENDOWN_INCLUDE_MARGIN)) &&
                                                        (_SWPoint_distance(&ipDouble->gestureLoc, &ipData->m_IPLocation) < DOUBLE_PENDOWN_INCLUDE_DISTANCE));
                                    if (includeIP)              /*   && (m_backend->pIPTable->FindRowNumber(ipData->m_IPLocation.y) == gestureRow)) */
                                    {
                                        /* SavedCodeSegments/SearchDB.cpp/119 - Convert PenDown to SoftDown */
                                        ipData->gestureLoc = pThis->m_SignChangeCenter;
                                        ipData->gestureIn = FixedIndexIn;
                                        ipData->gestureOut = FixedIndexOut;
                                        ipData->gestureRatio = gestureRatio;
                                        ipData->circleScore = circleScore;
                                        ipData->signChangesOK = signChangesOK;
                                        /* Track number and location of included IPs */
                                        ipData->m_DoubleIPIndex = -pThis->m_backend->pIPTable->m_wDoubleIPCount;
                                        SWCIPTable_recordIncludedIP(pThis->m_backend->pIPTable, ipDouble, ipData, 3);
#if DEBUG_IPT2
                                        Log(SWLogger_DEBUG, L" Labeled type %d IP at [%d, %d] {%d} from gesture %d. \n", ipData->m_IPType,
                                            ipData->m_IPLocation.x, ipData->m_IPLocation.y, ipData->m_IPPosIndex, pThis->m_backend->pIPTable->GetIPTable2Size());
#endif
                                    }
                                }
                            }
                        }
#if DEBUG_IPT2
                        else
                            Log(SWLogger_DEBUG, L" Gesture too large - No Double IP...\n");
#endif
                    }
#if DEBUG_IPT2 || DEBUG_IPT3 || DEBUG_IPT5
                    else
                    {
                        if (!enoughTurns)
                            Log(SWLogger_DEBUG, L" Not enough turns - No Double IP...\n");
                        if (!gestureClosed)
                            Log(SWLogger_DEBUG, L" Gesture not closed - No Double IP...\n");
                        if (!gestureLargeEnough)
                            Log(SWLogger_DEBUG, L" Gesture not large enough - No Double IP...\n");
                        if (!gestureSmallEnough)
                            Log(SWLogger_DEBUG, L" Gesture not small enough - No Double IP...\n");
                    }
#endif
                 }
            }
        }
    }
    if (signChange1 && changeOK)        /* If an acceptable sign change has been reported, record it */
    {
        SBYTE2 roundUp;
        if (pThis->m_wSignChangeIn == 0)       /* If starting a new sequence */
        {   /* Set CapLock gesture flag if first sign change is above keyboard */
            /* record fixed point at current position in Fixed Data array as FixedDataIn location */
            pThis->m_wSignChangeFixedIndex1 = lastFixedIndex;
            if (pThis->m_wSignChangeFixedIndex1 < 0)
                pThis->m_wSignChangeFixedIndex1 = 0;
            _SWPoint_Construct_Z(&pThis->m_SignChangeSum);
            /* Initialize Min and Max X and Y values */
            pThis->m_wSignChangeMinX = pThis->m_wSignChangeMaxX = changePoint.x;
            pThis->m_wSignChangeMinY = pThis->m_wSignChangeMaxY = changePoint.y;
            pThis->m_SignChangeOrigin = changePoint;
#if DEBUG_IPT2
            Log(SWLogger_DEBUG, L" Sign change origin set to [(%d, %d) at %d]\n", pThis->m_SignChangeOrigin.x, pThis->m_SignChangeOrigin.y, lastFixedIndex);
#endif
            pThis->lastClosureDistance = 0;
            pThis->gestureClosed = _FALSE;
            pThis->gestureClosing = _FALSE;
            pThis->gestureSmallEnough = _FALSE;
            pThis->gestureLargeEnough = _FALSE;
            pThis->thisGestureRadius = 0;          /* Set to 0 so not used in test for gesture closure */
        }
        else {
            if (changePoint.x < pThis->m_wSignChangeMinX)
                pThis->m_wSignChangeMinX = changePoint.x;
            else if (changePoint.x > pThis->m_wSignChangeMaxX)
                pThis->m_wSignChangeMaxX = changePoint.x;
            if (changePoint.y < pThis->m_wSignChangeMinY)
                pThis->m_wSignChangeMinY = changePoint.y;
            else if (changePoint.y > pThis->m_wSignChangeMaxY)
                pThis->m_wSignChangeMaxY = changePoint.y;
        }
        /* Always record current position in Fixed Data array as end of data already added.  When changeOK is false */
        /* above and DoubleLetter IP is added, recorded position will be at end of valid sequence. */
        pThis->m_wSignChangeFixedIndex2 = lastFixedIndex;
        /* Append current changes to end of valid sequence */
        pThis->m_SignChangeLoc[pThis->m_wSignChangeIn] = changePoint;
        pThis->m_wSignChanges[pThis->m_wSignChangeIn++] = signChange1;
        if (signChange2)
        {
            pThis->m_SignChangeLoc[pThis->m_wSignChangeIn] = changePoint;
            pThis->m_wSignChanges[pThis->m_wSignChangeIn++] = signChange2;
            pThis->m_wSignChange2Cnt++;
        }
        if (pThis->wChangeDataCount >= _SWYPE_MAX_CHANGE_DATA) {
            Log(SWLogger_DEBUG, SWTEXT("Panic: no more change data slots.\n"));
            _ET9AWSP_DATA->bPanic = _TRUE;
            return;
        }
        else {
            SWCMouseData *saveData = &pThis->m_pChangeData[pThis->wChangeDataCount++];
            saveData->m_Point = pThis->m_PrevSmoothPoint->smoothPoint;
            saveData->m_TimeOffset = pThis->m_PrevSmoothPoint->m_TimeOffset;
        }
        pThis->m_SignChangeSum = _SWPoint_Add_P(&pThis->m_SignChangeSum, &changePoint);
        roundUp = (pThis->m_wSignChangeIn >> 1);            /*lint !e702 */
        pThis->m_SignChangeCenter.x = (pThis->m_SignChangeSum.x + roundUp) / (pThis->m_wSignChangeIn - pThis->m_wSignChange2Cnt);
        pThis->m_SignChangeCenter.y = (pThis->m_SignChangeSum.y + roundUp) / (pThis->m_wSignChangeIn - pThis->m_wSignChange2Cnt);
        if (((pThis->m_wSignChangeIn - pThis->m_wSignChange2Cnt) == 2) && (pThis->m_wGestureTurns == 0))     /* Reset origin to midpoint of first two changes */
            pThis->m_SignChangeOrigin2 = pThis->m_SignChangeCenter;
        if ((pThis->m_wGestureTurns == 0) && (pThis->m_wSignChangeIn > 0))        /* Calculate gesture radius from up to first (SIGN_TRACK_MAX - 2) points received */
        {
            BYTE2 thisGestureRadiusSum8 = 0;
            SBYTE2 i;
            for (i = 0; i < pThis->m_wSignChangeIn; i++)
                thisGestureRadiusSum8 = (BYTE2)(thisGestureRadiusSum8 + _SWPoint_distance8(&pThis->m_SignChangeCenter, &pThis->m_SignChangeLoc[i]));
            pThis->thisGestureRadius = (thisGestureRadiusSum8 + 8) / (pThis->m_wSignChangeIn * 8);
            if (pThis->m_wSignChangeIn < 3)
            {
                pThis->thisGestureRadiusThreshold = (BYTE2)(ET9S32)(pThis->thisGestureRadius * GESTURE_RADIUS_THRESHOLD_FACTOR1);
                pThis->thisGestureRadius = 0;          /* Set back to 0 so not used in test for gesture closure */
            }
            else
                pThis->thisGestureRadiusThreshold = (BYTE2)(ET9S32)(pThis->thisGestureRadius * GESTURE_RADIUS_THRESHOLD_FACTOR2);
            /* Don't let small start to gesture be overly constraining */
            if (pThis->thisGestureRadiusThreshold < ((4 * DOUBLE_GESTURE_MAX_RADIUS) / 5))
                pThis->thisGestureRadiusThreshold = (4 * DOUBLE_GESTURE_MAX_RADIUS) / 5;
            /* Check for valid size (not too large) as soon as enough sign changes detected.  Gesture can expand later and still be valid. */
            if (pThis->m_wSignChangeIn >= GESTURE_TURNS_FOR_SIZE_MEASUREMENT)
            {
                BYTE2 gestureWidth = (BYTE2)(pThis->m_wSignChangeMaxX - pThis->m_wSignChangeMinX);
                BYTE2 gestureHeight = (BYTE2)(pThis->m_wSignChangeMaxY - pThis->m_wSignChangeMinY);
                /* large enough, */
                pThis->gestureLargeEnough = (ET9BOOL)((gestureWidth >= DOUBLE_GESTURE_MIN_DIAM) && (gestureHeight >= DOUBLE_GESTURE_MIN_DIAM));
                /* Before creating a sequence of Angle IPs from the detected DoubleLetter IP, make sure it is not too large */
                pThis->gestureSmallEnough = (ET9BOOL)((gestureWidth <= (2 * DOUBLE_GESTURE_MAX_RADIUS)) && (gestureHeight <= (2 * DOUBLE_GESTURE_MAX_RADIUS)));
            }
        }
        /* If next entry could overflow array bounds, then shift array down (saving a sequence of the */
        /*   last SIGN_TRACK_SAVE valid changes) */
        if (pThis->m_wSignChangeIn > (SIGN_TRACK_MAX - 2))     /* test accounts for double entry */
        {
            SBYTE2 index2 = pThis->m_wSignChangeIn - SIGN_TRACK_SAVE;
            SBYTE2 index1;
            for (index1 = 0; index1 < SIGN_TRACK_SAVE; index1++)
            {
                pThis->m_SignChangeLoc[index1] = pThis->m_SignChangeLoc[index2];
                pThis->m_wSignChanges[index1] = pThis->m_wSignChanges[index2];
                index2++;
            }
            pThis->m_wSignChangeIn = SIGN_TRACK_SAVE;
            pThis->m_wGestureTurns++;
            pThis->m_SignChangeSum.x = (SBYTE2)(pThis->m_SignChangeCenter.x * pThis->m_wSignChangeIn);
            pThis->m_SignChangeSum.y = (SBYTE2)(pThis->m_SignChangeCenter.y * pThis->m_wSignChangeIn);
            pThis->m_wSignChange2Cnt = 0;      /* m_SignChangeSum now weighted according to current value of m_wSignChangeIn */
        }
    }
} /* SWCIPAnalyzer::TrackSignChanges() */

SBYTE4 ET9FARCALL SWCIPAnalyzer_AnalyzeChangeData(SWCIPAnalyzer *pThis, SBYTE4 *avRadius, ET9FLOAT *stdDev)
{
    SWCMouseData *changeData;
    _SWPoint     checkPoint;
    SBYTE4       i;
    SBYTE4       nData = (SBYTE4)pThis->wChangeDataCount;
    ET9DOUBLE variance = 0.0;
    *avRadius = 0;
    *stdDev = (ET9FLOAT)0.0f;
    if (nData > 0)
    {
        for (i = 0; i < nData; i++)
        {
            changeData = &pThis->m_pChangeData[i];
            /* Use m_TimeOffset field to store distance from gesture center */
            checkPoint.x = (SBYTE2)changeData->m_Point.x;
            checkPoint.y = (SBYTE2)changeData->m_Point.y;
            changeData->m_TimeOffset = _SWPoint_distance8(&pThis->m_SignChangeCenter, &checkPoint);
            *avRadius += (SBYTE4)changeData->m_TimeOffset;
        }
        *avRadius = (*avRadius + (nData >> 1)) / nData;
        for (i = 0; i < nData; i++)
        {
            ET9DOUBLE difference;
            changeData = &pThis->m_pChangeData[i];
            difference = (ET9DOUBLE)((SBYTE4)changeData->m_TimeOffset - *avRadius);
            variance += (difference * difference);
        }
        variance = variance / (ET9DOUBLE)nData;
        *stdDev = (ET9FLOAT)_ET9sqrt(variance);
    }
    return(nData);
}

/* eof */
