//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1991 - 1994.
//
//  File:       DefBreak.cxx
//
//  Contents:   Text Word Breaker
//
//  History:    08-May-91   t-WadeR     Created stubs, filled in ASCII code.
//              06-Jun-91   t-WadeR     Changed to use input-based pipeline
//              11-Apr-92   KyleP       Sync to spec
//
//----------------------------------------------------------------------------

#include <pch.cxx>
#pragma hdrstop

#include <DefBreak.hxx>

//+---------------------------------------------------------------------------
//
//  Member:     CDefWordBreaker::CDefWordBreaker
//
//  Synopsis:   Constructor for the CDefWordBreaker class.
//
//  History:    07-June-91  t-WadeR     Created
//              12-Oct-92   AmyA        Added Unicode support
//
//----------------------------------------------------------------------------

CDefWordBreaker::CDefWordBreaker()
        : _cRefs(1)
{
    ciDebugOut(( DEB_ITRACE, "Creating default wordbreaker\n" ));

    // Look at IsWordChar. We don't want the last non-breaking
    // space in the chunk to be considered a word break.
    // It will be processed again (correctly) when we move to the next chunk.

    _aCharInfo3 [CDefWordBreaker::ccCompare] = C3_NONSPACING;
}

//+---------------------------------------------------------------------------
//
//  Member:     CWordBreaker::~CWordBreaker
//
//  Synopsis:   Destructor for the CWordBreaker class.
//
//----------------------------------------------------------------------------

CDefWordBreaker::~CDefWordBreaker()
{
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreaker::QueryInterface
//
//  Synopsis:   Rebind to other interface
//
//  Arguments:  [riid]      -- IID of new interface
//              [ppvObject] -- New interface * returned here
//
//  Returns:    S_OK if bind succeeded, E_NOINTERFACE if bind failed
//
//  History:    23-Feb-1994     KyleP   Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreaker::QueryInterface( REFIID riid,
                                                         void ** ppvObject)
{
    if ( 0 == ppvObject )
        return E_INVALIDARG;

    if ( IID_IWordBreaker == riid )
        *ppvObject = (IUnknown *)(IWordBreaker *)this;
    else if ( IID_IUnknown == riid )
        *ppvObject = (IUnknown *)(IPersist *)(IPersistFile *)this;
    else
    {
        *ppvObject = 0;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreaker::AddRef
//
//  Synopsis:   Increments refcount
//
//  History:    23-Feb-1994     KyleP   Created
//
//--------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CDefWordBreaker::AddRef()
{
    return InterlockedIncrement( &_cRefs );
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreaker::Release
//
//  Synopsis:   Decrement refcount.  Delete if necessary.
//
//  History:    23-Feb-1994     KyleP   Created
//
//--------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CDefWordBreaker::Release()
{
    unsigned long uTmp = InterlockedDecrement( &_cRefs );

    if ( 0 == uTmp )
        delete this;

    return uTmp;
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreaker::Init
//
//  Synopsis:   Initialize word-breaker
//
//  Arguments:  [fQuery]         -- TRUE if query-time
//              [ulMaxTokenSize] -- Maximum size token stored by caller
//              [pfLicense]      -- Set to true if use restricted
//
//  Returns:    Status code
//
//  History:    11-Apr-1994  KyleP       Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreaker::Init( BOOL fQuery,
                                               ULONG ulMaxTokenSize,
                                               BOOL *pfLicense )
{
    if ( 0 == pfLicense )
        return E_INVALIDARG;

    *pfLicense = FALSE;

    return S_OK;
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreaker::IsWordChar
//
//  Synopsis:   Find whether the i'th character in the buffer _awString
//              is a word character (rather than word break)
//
//  Arguments:  [i] -- index into _awString
//
//  History:    22-Jul-1994  BartoszM       Created
//
//--------------------------------------------------------------------------

inline BOOL CDefWordBreaker::IsWordChar (int i) const
{
    if (   (_aCharInfo1[i] & (C1_ALPHA | C1_DIGIT))
        || (_aCharInfo3[i] & C3_NONSPACING)  )
    {
        return TRUE;
    }

    WCHAR c = _pwcChunk[i];

    if (c == L'_')
        return TRUE;

    if (c == 0xa0) // non breaking space
    {
        // followed by a non-spacing character
        // (looking ahead is okay)
        if (_aCharInfo3[i+1] & C3_NONSPACING)
            return TRUE;
    }
    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CDefWordBreaker::ScanChunk
//
//  Synopsis:   For each character find its type
//
//
//  History:    16-Aug-94  BartoszM     Created
//
//----------------------------------------------------------------------------

BOOL CDefWordBreaker::ScanChunk ()
{

    //
    // GetStringTypeW is returning error 87 (ERROR_INVALID_PARAMETER) if
    // we pass in a null string.
    //
    Win4Assert( (0 != _cMapped) && (0 != _pwcChunk) );

    if ( !GetStringTypeW( CT_CTYPE1,              // POSIX character typing
                          _pwcChunk,              // Source
                          _cMapped,               // Size of source
                          _aCharInfo1 ) )         // Character info
    {
        ciDebugOut(( DEB_ERROR, "GetStringTypeW returned %d\n",
                     GetLastError() ));
        return FALSE;
    }

    if ( !GetStringTypeW( CT_CTYPE3,              // Additional POSIX
                          _pwcChunk,
                          _cMapped,               // Size of source
                          _aCharInfo3 ) )         // Character info 3
    {
        ciDebugOut(( DEB_ERROR, "GetStringTypeW CTYPE3 returned %d\n",
                     GetLastError() ));
        return FALSE;
    }
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CDefWordBreaker::BreakText
//
//  Synopsis:   Break input stream into words.
//
//  Arguments:  [pTextSource] - source of input buffers
//              [pWordSink] - sink for words
//              [pPhraseSink] - sink for noun phrases
//
//  History:    07-June-91  t-WadeR     Created
//              12-Oct-92   AmyA        Added Unicode support
//              18-Nov-92   AmyA        Overloaded
//              11-Apr-94   KyleP       Sync with spec
//              26-Aug-94   BartoszM    Fixed Unicode parsing
//
//----------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreaker::BreakText( TEXT_SOURCE *pTextSource,
                                                    IWordSink *pWordSink,
                                                    IPhraseSink *pPhraseSink )
{
    if ( 0 == pTextSource )
        return E_INVALIDARG;

    if ( 0 == pWordSink || pTextSource->iCur == pTextSource->iEnd)
        return S_OK;

    if (pTextSource->iCur > pTextSource->iEnd)
    {
        Win4Assert ( !"BreakText called with bad TEXT_SOURCE" );
        return E_FAIL;
    }

    SCODE sc = S_OK;

    ULONG cwc, cwcProcd;     // cwcProcd is # chars actually processed by Tokenize()
    TRY
    {
        do
        {
            //
            // Flag for first time thru loop below. This is to fix the case
            // where the length of the buffer passed in is less than
            // MAX_II_BUFFER_LEN. In this case iEnd-iCur is <= MAX_II_BUFFER_LEN
            // and we break out the inner loop and call
            // pfnFillTextBuffer without having processed any characters,
            // and so pfnFillTextBuffer returns TRUE without adding any new
            // characters and this results in an infinite loop.
            //
            BOOL fFirstTime = TRUE;

            while ( pTextSource->iCur < pTextSource->iEnd )
            {
                cwc = pTextSource->iEnd - pTextSource->iCur;

                //
                // Process in buckets of MAX_II_BUFER_LEN only
                //
                if ( cwc >= CDefWordBreaker::ccCompare )
                    cwc = CDefWordBreaker::ccCompare;
                else if ( !fFirstTime )
                    break;

                Tokenize( pTextSource, cwc, pWordSink, cwcProcd );

                Win4Assert( cwcProcd <= cwc );

                pTextSource->iCur += cwcProcd;

                fFirstTime = FALSE;
            }
        } while ( SUCCEEDED(pTextSource->pfnFillTextBuffer(pTextSource)) );

        cwc = pTextSource->iEnd - pTextSource->iCur;

        // we know that the remaining text should be less than ccCompare
        Win4Assert( cwc < CDefWordBreaker::ccCompare );

        if ( 0 != cwc )
        {
            Tokenize( pTextSource, cwc, pWordSink, cwcProcd );
        }

    }
    CATCH (CException, e)
    {
        ciDebugOut(( DEB_ITRACE,
                     "Exception 0x%x caught when breaking text in default wordbreaker\n",
                     e.GetErrorCode() ));

        sc = GetOleError( e );
    }
    END_CATCH

    return sc;
}




//+---------------------------------------------------------------------------
//
//  Member:     CDefWordBreaker::Tokenize
//
//  Synopsis:   Tokenize the input buffer into words
//
//  Arguments:  [pTextSource]  --  input text source
//              [cwc]          --  # chars to process
//              [pWordSink]    --  sink for words
//              [cwcProd]      --  # chars actually processed returned here
//
//  History:    10-Aug-95   SitaramR    Created
//
//----------------------------------------------------------------------------

void CDefWordBreaker::Tokenize( TEXT_SOURCE *pTextSource,
                                ULONG cwc,
                                IWordSink *pWordSink,
                                ULONG& cwcProcd )
{
    _pwcChunk = &pTextSource->awcBuffer[pTextSource->iCur];
    _cMapped = cwc;


    if ( !ScanChunk() )
        THROW( CException( E_FAIL ) );

    BOOL fWordHasZWS = FALSE;     // Does the current word have a zero-width-space ?
    unsigned uLenZWS;             // Length of a word minus embedded zero-width-spaces

    //
    // iBeginWord is the offset into _aCharInfo of the beginning character of
    // a word.  iCur is the first *unprocessed* character.
    // They are indexes into the mapped chunk.
    //

    unsigned iBeginWord = 0;
    unsigned iCur = 0;

    SCODE sc = S_OK;

    //
    // Pump words from mapped chunk to word sink
    //
    while ( iCur < _cMapped )
    {
        //
        // Skip whitespace, punctuation, etc.
        //
        for (; iCur < _cMapped; iCur++)
            if ( IsWordChar (iCur) )
                break;

        // iCur points to a word char or is equal to _cMapped

        iBeginWord = iCur;
        if (iCur < _cMapped)
            iCur++; // we knew it pointed at word character

        //
        // Find word break. Filter may output Unicode zero-width-space, which
        // should be ignored by the wordbreaker.
        //
        fWordHasZWS = FALSE;
        for (; iCur < _cMapped; iCur++)
        {
            if ( !IsWordChar (iCur) )
            {
                if ( _pwcChunk[iCur] == ZERO_WIDTH_SPACE )
                    fWordHasZWS = TRUE;
                else
                    break;
            }
        }

        if ( fWordHasZWS )
        {
            //
            // Copy word into _awcBufZWS after stripping zero-width-spaces
            //

            uLenZWS = 0;
            for ( unsigned i=iBeginWord; i<iCur; i++ )
            {
                if ( _pwcChunk[i] != ZERO_WIDTH_SPACE )
                    _awcBufZWS[uLenZWS++] = _pwcChunk[i];
            }
        }

        // iCur points to a non-word char or is equal to _cMapped

        if ( iCur < _cMapped )
        {
            // store the word and its source position
            if ( fWordHasZWS )
                sc = pWordSink->PutWord( uLenZWS,
                                         _awcBufZWS,    // stripped word
                                         iCur - iBeginWord,
                                         pTextSource->iCur + iBeginWord );
            else
                sc = pWordSink->PutWord( iCur - iBeginWord,
                                         _pwcChunk + iBeginWord, // the word
                                         iCur - iBeginWord,
                                         pTextSource->iCur + iBeginWord );

            if ( FAILED( sc ) )
                THROW( CException( sc ) );

            iCur++; // we knew it pointed at non-word char
            iBeginWord = iCur; // in case we exit the loop now
        }
    } // next word

    Win4Assert( iCur == _cMapped );

    // End of words in chunk.
    // iCur == _cMapped
    // iBeginWord points at beginning of word or == _cMapped

    if ( 0 == iBeginWord )
    {
        // A single word fills from beginning of this chunk
        // to the end. This is either a very long word or
        // a short word in a leftover buffer.

        // store the word and its source position
        if ( fWordHasZWS )
            sc = pWordSink->PutWord( uLenZWS,
                                     _awcBufZWS,       // stripped word
                                     iCur,
                                     pTextSource->iCur ); // its source pos.
        else
            sc = pWordSink->PutWord( iCur,
                                     _pwcChunk,       // the word
                                     iCur,
                                     pTextSource->iCur ); // its source pos.

        if ( FAILED( sc ) )
            THROW( CException( sc ) );

        //
        // Position it to not add the word twice.
        //

        iBeginWord = iCur;
    }

    //
    // If this is the last chunk from text source, then process the
    // last fragment
    //

    if ( cwc < CDefWordBreaker::ccCompare && iBeginWord != iCur )
    {
        // store the word and its source position
        if ( fWordHasZWS )
            sc = pWordSink->PutWord( uLenZWS,
                                     _awcBufZWS,    // stripped word
                                     iCur - iBeginWord,
                                     pTextSource->iCur + iBeginWord );
        else
            sc = pWordSink->PutWord( iCur - iBeginWord,
                                     _pwcChunk + iBeginWord,  // the word
                                     iCur - iBeginWord,
                                     pTextSource->iCur + iBeginWord );

        if ( FAILED( sc ) )
            THROW( CException( sc ) );

        iBeginWord = iCur;
    }

    cwcProcd = iBeginWord;
}



//+---------------------------------------------------------------------------
//
//  Member:     CDefWordBreaker::ComposePhrase
//
//  Synopsis:   Convert a noun and a modifier into a phrase
//
//  Arguments:  [pwcNoun] -- pointer to noun.
//              [cwcNoun] -- count of chars in pwcNoun
//              [pwcModifier] -- pointer to word modifying pwcNoun
//              [cwcModifier] -- count of chars in pwcModifier
//              [ulAttachmentType] -- relationship between pwcNoun &pwcModifier
//
//  History:    10-Aug-95   SitaramR    Created Header
//
//----------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreaker::ComposePhrase( WCHAR const *pwcNoun,
                                                        ULONG cwcNoun,
                                                        WCHAR const *pwcModifier,
                                                        ULONG cwcModifier,
                                                        ULONG ulAttachmentType,
                                                        WCHAR *pwcPhrase,
                                                        ULONG *pcwcPhrase )
{
    //
    // Never emitted phrase in the first place.
    //

    ciDebugOut(( DEB_WARN,
                 "IWordBreaker::ComposePhrase called on default word breaker\n" ));
    return( E_FAIL );
}

//+---------------------------------------------------------------------------
//
//  Member:     CWordBreaker::GetLicenseToUse
//
//  Synopsis:   Returns a pointer to vendors license information
//
//  Arguments:  [ppwcsLicense] -- ptr to ptr to which license info is returned
//
//  History:    10-Aug-95  SitaramR     Created Header
//
//----------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreaker::GetLicenseToUse( const WCHAR **ppwcsLicense )
{
    if ( 0 == ppwcsLicense )
        return E_INVALIDARG;

    static WCHAR const * wcsCopyright = L"Copyright (c) Microsoft Corporation, 1991-1998";
    *ppwcsLicense = wcsCopyright;

    return( S_OK );
}



extern long gulcInstances;


//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::CDefWordBreakerCF
//
//  Synopsis:   Default Word Breaker class factory constructor
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

CDefWordBreakerCF::CDefWordBreakerCF( )
        : _cRefs( 1 )
{
    InterlockedIncrement( &gulcInstances );
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::~CDefWordBreakerCF
//
//  Synopsis:   Default Word Breaker class factory destructor
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

CDefWordBreakerCF::~CDefWordBreakerCF()
{
    InterlockedDecrement( &gulcInstances );
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::QueryInterface
//
//  Synopsis:   Rebind to other interface
//
//  Arguments:  [riid]      -- IID of new interface
//              [ppvObject] -- New interface * returned here
//
//  Returns:    S_OK if bind succeeded, E_NOINTERFACE if bind failed
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreakerCF::QueryInterface(
    REFIID  riid,
    void ** ppvObject )
{
    if ( IID_IClassFactory == riid )
        *ppvObject = (IUnknown *)(IClassFactory *)this;
    else if ( IID_IUnknown == riid )
        *ppvObject = (IUnknown *)this;
    else
    {
        *ppvObject = 0;
        return E_NOINTERFACE;
    }

    AddRef();
    return S_OK;
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::AddRef
//
//  Synopsis:   Increments refcount
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CDefWordBreakerCF::AddRef()
{
    return InterlockedIncrement( &_cRefs );
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::Release
//
//  Synopsis:   Decrement refcount.  Delete if necessary.
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CDefWordBreakerCF::Release()
{
    unsigned long uTmp = InterlockedDecrement( &_cRefs );

    if ( 0 == uTmp )
        delete this;

    return uTmp;
}


//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::CreateInstance
//
//  Synopsis:   Creates new CDefWordBreaker object
//
//  Arguments:  [pUnkOuter] -- 'Outer' IUnknown
//              [riid]      -- Interface to bind
//              [ppvObject] -- Interface returned here
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreakerCF::CreateInstance( IUnknown * pUnkOuter,
                                                        REFIID riid,
                                                        void  * * ppvObject )
{
    CDefWordBreaker *pIUnk = 0;
    SCODE sc = S_OK;

    TRY
    {
        pIUnk = new CDefWordBreaker();
        sc = pIUnk->QueryInterface(  riid , ppvObject );

        pIUnk->Release();  // Release extra refcount from QueryInterface
    }
    CATCH(CException, e)
    {
        Win4Assert( 0 == pIUnk );

        switch( e.GetErrorCode() )
        {
        case E_OUTOFMEMORY:
            sc = (E_OUTOFMEMORY);
            break;

        default:
            sc = (E_UNEXPECTED);
        }
    }
    END_CATCH;

    return (sc);
}

//+-------------------------------------------------------------------------
//
//  Method:     CDefWordBreakerCF::LockServer
//
//  Synopsis:   Force class factory to remain loaded
//
//  Arguments:  [fLock] -- TRUE if locking, FALSE if unlocking
//
//  Returns:    S_OK
//
//  History:    07-Feb-1995     SitaramR   Created
//
//--------------------------------------------------------------------------

SCODE STDMETHODCALLTYPE CDefWordBreakerCF::LockServer(BOOL fLock)
{
    if(fLock)
        InterlockedIncrement( &gulcInstances );
    else
        InterlockedDecrement( &gulcInstances );

    return(S_OK);
}