/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_linguistic.hxx"

#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
#include <com/sun/star/linguistic2/SpellFailure.hpp>
#include <com/sun/star/registry/XRegistryKey.hpp>

#include <cppuhelper/factory.hxx>	// helper for factories
#include <unotools/localedatawrapper.hxx>
#include <unotools/processfactory.hxx>
#include <tools/debug.hxx>
#include <svl/lngmisc.hxx>
#include <osl/mutex.hxx>

#include <vector>

#include "spelldsp.hxx"
#include "linguistic/spelldta.hxx"
#include "lngsvcmgr.hxx"
#include "linguistic/lngprops.hxx"


using namespace utl;
using namespace osl;
using namespace rtl;
using namespace com::sun::star;
using namespace com::sun::star::beans;
using namespace com::sun::star::lang;
using namespace com::sun::star::uno;
using namespace com::sun::star::linguistic2;
using namespace linguistic;

///////////////////////////////////////////////////////////////////////////
// ProposalList: list of proposals for misspelled words
//   The order of strings in the array should be left unchanged because the
// spellchecker should have put the more likely suggestions at the top.
// New entries will be added to the end but duplicates are to be avoided.
// Removing entries is done by assigning the empty string.
// The sequence is constructed from all non empty strings in the original 
// while maintaining the order.
//
class ProposalList
{
    std::vector< OUString > aVec;

    sal_Bool    HasEntry( const OUString &rText ) const;

    // make copy c-tor and assignment operator private
    ProposalList( const ProposalList & );
    ProposalList & operator = ( const ProposalList & );

public:
    ProposalList()  {}

    //size_t  Size() const   { return aVec.size(); }
    size_t  Count() const;
    void    Prepend( const OUString &rText );
    void    Append( const OUString &rNew );
    void    Append( const std::vector< OUString > &rNew );
    void    Append( const Sequence< OUString > &rNew );
    void    Remove( const OUString &rText );
    Sequence< OUString >    GetSequence() const;
};


sal_Bool ProposalList::HasEntry( const OUString &rText ) const
{
    sal_Bool bFound = sal_False;
    size_t nCnt = aVec.size();
    for (size_t i = 0;  !bFound && i < nCnt;  ++i)
    {
        if (aVec[i] == rText)
            bFound = sal_True;
    }
    return bFound;
}

void ProposalList::Prepend( const OUString &rText )
{
    if (!HasEntry( rText ))
        aVec.insert( aVec.begin(), rText );
}

void ProposalList::Append( const OUString &rText )
{
    if (!HasEntry( rText ))
        aVec.push_back( rText );
}

void ProposalList::Append( const std::vector< OUString > &rNew )
{
    size_t nLen = rNew.size();
    for ( size_t i = 0;  i < nLen;  ++i)
    {
        const OUString &rText = rNew[i];
        if (!HasEntry( rText ))
            Append( rText );
    }
}

void ProposalList::Append( const Sequence< OUString > &rNew )
{
    sal_Int32 nLen = rNew.getLength();
    const OUString *pNew = rNew.getConstArray();
    for (sal_Int32 i = 0;  i < nLen;  ++i)
    {
        const OUString &rText = pNew[i];
        if (!HasEntry( rText ))
            Append( rText );
    }
}

size_t ProposalList::Count() const
{
    // returns the number of non-empty strings in the vector

    size_t nRes = 0;
    size_t nLen = aVec.size();
    for (size_t i = 0;  i < nLen;  ++i)
    {
        if (aVec[i].getLength() != 0)
            ++nRes;
    }
    return nRes;
}
 
Sequence< OUString > ProposalList::GetSequence() const
{
    sal_Int32 nCount = Count();
    sal_Int32 nIdx = 0;
    Sequence< OUString > aRes( nCount );
    OUString *pRes = aRes.getArray();
    sal_Int32 nLen = aVec.size();
    for (sal_Int32 i = 0;  i < nLen;  ++i)
    {
        const OUString &rText = aVec[i];
        DBG_ASSERT( nIdx < nCount, "index our of range" );
        if (nIdx < nCount && rText.getLength() > 0)
            pRes[ nIdx++ ] = rText;
    }
    return aRes;
}

void ProposalList::Remove( const OUString &rText )
{
    size_t nLen = aVec.size();
    for (size_t i = 0;  i < nLen;  ++i)
    {
        OUString &rEntry = aVec[i];
        if (rEntry == rText)
        {
            rEntry = OUString(); 
            break;  // there should be only one matching entry
        }
    }
}


///////////////////////////////////////////////////////////////////////////

sal_Bool SvcListHasLanguage( 
        const LangSvcEntries_Spell &rEntry, 
        LanguageType nLanguage )
{
    sal_Bool bHasLanguage = sal_False;
    Locale aTmpLocale;
    
    const Reference< XSpellChecker >  *pRef  = rEntry.aSvcRefs .getConstArray();
    sal_Int32 nLen = rEntry.aSvcRefs.getLength();
    for (sal_Int32 k = 0;  k < nLen  &&  !bHasLanguage;  ++k)
    {
        if (pRef[k].is())
        {
            if (0 == aTmpLocale.Language.getLength())
                aTmpLocale = CreateLocale( nLanguage );
            bHasLanguage = pRef[k]->hasLocale( aTmpLocale );
        }
    }
    
    return bHasLanguage;
}

///////////////////////////////////////////////////////////////////////////


SpellCheckerDispatcher::SpellCheckerDispatcher( LngSvcMgr &rLngSvcMgr ) :
	rMgr	(rLngSvcMgr)
{
    pCache = NULL;
}


SpellCheckerDispatcher::~SpellCheckerDispatcher()
{
	ClearSvcList();
    delete pCache;
}


void SpellCheckerDispatcher::ClearSvcList()
{
	// release memory for each table entry
    SpellSvcByLangMap_t aTmp;
    aSvcMap.swap( aTmp );
}


Sequence< Locale > SAL_CALL SpellCheckerDispatcher::getLocales()
		throw(RuntimeException)
{
	MutexGuard	aGuard( GetLinguMutex() );
    
    Sequence< Locale > aLocales( static_cast< sal_Int32 >(aSvcMap.size()) );
    Locale *pLocales = aLocales.getArray();
    SpellSvcByLangMap_t::const_iterator aIt;
    for (aIt = aSvcMap.begin();  aIt != aSvcMap.end();  ++aIt)
    {
        *pLocales++ = CreateLocale( aIt->first );
    }    
    return aLocales;
}


sal_Bool SAL_CALL SpellCheckerDispatcher::hasLocale( const Locale& rLocale )
		throw(RuntimeException)
{
	MutexGuard	aGuard( GetLinguMutex() );
    SpellSvcByLangMap_t::const_iterator aIt( aSvcMap.find( LocaleToLanguage( rLocale ) ) );
    return aIt != aSvcMap.end();
}


sal_Bool SAL_CALL
	SpellCheckerDispatcher::isValid( const OUString& rWord, const Locale& rLocale,
			const PropertyValues& rProperties )
		throw(IllegalArgumentException, RuntimeException)
{
	MutexGuard	aGuard( GetLinguMutex() );
    return isValid_Impl( rWord, LocaleToLanguage( rLocale ), rProperties, sal_True );
}


Reference< XSpellAlternatives > SAL_CALL
	SpellCheckerDispatcher::spell( const OUString& rWord, const Locale& rLocale,
			const PropertyValues& rProperties )
		throw(IllegalArgumentException, RuntimeException)
{
	MutexGuard	aGuard( GetLinguMutex() );
    return spell_Impl( rWord, LocaleToLanguage( rLocale ), rProperties, sal_True );
}


// returns the overall result of cross-checking with all user-dictionaries
// including the IgnoreAll list
static Reference< XDictionaryEntry > lcl_GetRulingDictionaryEntry(
    const OUString &rWord,
    LanguageType nLanguage )
{
    Reference< XDictionaryEntry > xRes;

    // the order of winning from top to bottom is:
    // 1) IgnoreAll list will always win
    // 2) Negative dictionaries will win over positive dictionaries
    Reference< XDictionary > xIgnoreAll( GetIgnoreAllList() );
    if (xIgnoreAll.is())
        xRes = xIgnoreAll->getEntry( rWord );
    if (!xRes.is())
    {
        Reference< XDictionaryList > xDList( GetDictionaryList() );
        Reference< XDictionaryEntry > xNegEntry( SearchDicList( xDList,
                rWord, nLanguage, sal_False, sal_True ) );
        if (xNegEntry.is())
            xRes = xNegEntry;
        else
        {
            Reference< XDictionaryEntry > xPosEntry( SearchDicList( xDList,
                    rWord, nLanguage, sal_True, sal_True ) );
            if (xPosEntry.is())
                xRes = xPosEntry;
        }
    }

    return xRes;
}


sal_Bool SpellCheckerDispatcher::isValid_Impl(
			const OUString& rWord, 
            LanguageType nLanguage, 
			const PropertyValues& rProperties,
			sal_Bool bCheckDics) 
		throw( RuntimeException, IllegalArgumentException )
{
	MutexGuard	aGuard( GetLinguMutex() );

	sal_Bool bRes =	sal_True;
	
	if (nLanguage == LANGUAGE_NONE  || !rWord.getLength())
		return bRes;
	
	// search for entry with that language
    SpellSvcByLangMap_t::iterator    aIt( aSvcMap.find( nLanguage ) );
    LangSvcEntries_Spell    *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
	
	if (!pEntry)
	{
#ifdef LINGU_EXCEPTIONS
		throw IllegalArgumentException();
#endif
	}
	else
	{
		OUString aChkWord( rWord );
        Locale aLocale( CreateLocale( nLanguage ) );

        // replace typographical apostroph by ascii apostroph
        String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
        DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
        if (aSingleQuote.Len())
            aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );

		RemoveHyphens( aChkWord );
		if (IsIgnoreControlChars( rProperties, GetPropSet() ))
			RemoveControlChars( aChkWord );

		sal_Int32 nLen = pEntry->aSvcRefs.getLength();
		DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(), 
				"lng : sequence length mismatch");
        DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen, 
				"lng : index out of range");

		sal_Int32 i = 0;
		sal_Bool bTmpRes = sal_True;
		sal_Bool bTmpResValid = sal_False;

		// try already instantiated services first
		{
			const Reference< XSpellChecker >  *pRef  = 
					pEntry->aSvcRefs.getConstArray();
            while (i <= pEntry->nLastTriedSvcIndex
                   &&  (!bTmpResValid  ||  sal_False == bTmpRes))
			{
				bTmpResValid = sal_True;
                if (pRef[i].is()  &&  pRef[i]->hasLocale( aLocale ))
				{
                    bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
					if (!bTmpRes)
					{
                        bTmpRes = pRef[i]->isValid( aChkWord, aLocale, rProperties );
						
						// Add correct words to the cache.
						// But not those that are correct only because of
						// the temporary supplied settings.
						if (bTmpRes  &&  0 == rProperties.getLength())
                            GetCache().AddWord( aChkWord, nLanguage );
					}
				}
				else
					bTmpResValid = sal_False;

				if (bTmpResValid)
					bRes = bTmpRes;

				++i;
			}
		}
		
		// if still no result instantiate new services and try those
		if ((!bTmpResValid  ||  sal_False == bTmpRes)
            &&  pEntry->nLastTriedSvcIndex < nLen - 1)
		{
			const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
			Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs .getArray();
			
			Reference< XMultiServiceFactory >  xMgr( getProcessServiceFactory() );
			if (xMgr.is())
			{
				// build service initialization argument
				Sequence< Any > aArgs(2);
				aArgs.getArray()[0] <<= GetPropSet();
				//! The dispatcher searches the dictionary-list
				//! thus the service needs not to now about it
				//aArgs.getArray()[1] <<= GetDicList();

				while (i < nLen  &&  (!bTmpResValid  ||  sal_False == bTmpRes))
				{
					// create specific service via it's implementation name
					Reference< XSpellChecker > xSpell;
					try
					{
						xSpell = Reference< XSpellChecker >( 
								xMgr->createInstanceWithArguments( 
								pImplNames[i], aArgs ),  UNO_QUERY );
					}
					catch (uno::Exception &)
					{
                        DBG_ASSERT( 0, "createInstanceWithArguments failed" );
					}
					pRef [i] = xSpell;
					
					Reference< XLinguServiceEventBroadcaster > 
							xBroadcaster( xSpell, UNO_QUERY );
					if (xBroadcaster.is())
						rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
					
					bTmpResValid = sal_True;			   
                    if (xSpell.is()  &&  xSpell->hasLocale( aLocale ))
					{
                        bTmpRes = GetCache().CheckWord( aChkWord, nLanguage );
						if (!bTmpRes)
						{
                            bTmpRes = xSpell->isValid( aChkWord, aLocale, rProperties );
							
							// Add correct words to the cache.
							// But not those that are correct only because of
							// the temporary supplied settings.
							if (bTmpRes  &&  0 == rProperties.getLength())
                                GetCache().AddWord( aChkWord, nLanguage );
						}
					}
					else
						bTmpResValid = sal_False;
				
					if (bTmpResValid)
						bRes = bTmpRes;

                    pEntry->nLastTriedSvcIndex = (sal_Int16) i;
					++i;
				}
                
                // if language is not supported by any of the services
                // remove it from the list.
                if (i == nLen)
                {
                    if (!SvcListHasLanguage( *pEntry, nLanguage ))
                        aSvcMap.erase( nLanguage );
                }
			}
		}
		
		// cross-check against results from dictionaries which have precedence!
		if (bCheckDics  &&  
			GetDicList().is()  &&  IsUseDicList( rProperties, GetPropSet() ))
		{
            Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
            if (xTmp.is())
                bRes = !xTmp->isNegative();
		}
	}

	return bRes;
}


Reference< XSpellAlternatives > SpellCheckerDispatcher::spell_Impl( 
			const OUString& rWord, 
            LanguageType nLanguage,
			const PropertyValues& rProperties,
			sal_Bool bCheckDics ) 
		throw(IllegalArgumentException, RuntimeException)
{
	MutexGuard	aGuard( GetLinguMutex() );

	Reference< XSpellAlternatives > xRes;
	
	if (nLanguage == LANGUAGE_NONE  || !rWord.getLength())
		return xRes;
	
	// search for entry with that language
    SpellSvcByLangMap_t::iterator    aIt( aSvcMap.find( nLanguage ) );
    LangSvcEntries_Spell    *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
	
	if (!pEntry)
	{
#ifdef LINGU_EXCEPTIONS
		throw IllegalArgumentException();
#endif
	}
	else
	{
		OUString aChkWord( rWord );
        Locale aLocale( CreateLocale( nLanguage ) );

        // replace typographical apostroph by ascii apostroph
        String aSingleQuote( GetLocaleDataWrapper( nLanguage ).getQuotationMarkEnd() );
        DBG_ASSERT( 1 == aSingleQuote.Len(), "unexpectend length of quotation mark" );
        if (aSingleQuote.Len())
            aChkWord = aChkWord.replace( aSingleQuote.GetChar(0), '\'' );

        RemoveHyphens( aChkWord );
		if (IsIgnoreControlChars( rProperties, GetPropSet() ))
			RemoveControlChars( aChkWord );

		sal_Int32 nLen = pEntry->aSvcRefs.getLength();
		DBG_ASSERT( nLen == pEntry->aSvcImplNames.getLength(), 
				"lng : sequence length mismatch");
        DBG_ASSERT( pEntry->nLastTriedSvcIndex < nLen, 
				"lng : index out of range");

		sal_Int32 i = 0;
		Reference< XSpellAlternatives > xTmpRes;
		sal_Bool bTmpResValid = sal_False;

		// try already instantiated services first
		{
            const Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs.getConstArray();
            sal_Int32 nNumSugestions = -1;
            while (i <= pEntry->nLastTriedSvcIndex
				   &&  (!bTmpResValid || xTmpRes.is()) )
			{
				bTmpResValid = sal_True;
                if (pRef[i].is()  &&  pRef[i]->hasLocale( aLocale ))
				{
                    sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
					if (bOK)
						xTmpRes = NULL;
					else
					{
                        xTmpRes = pRef[i]->spell( aChkWord, aLocale, rProperties );
						
						// Add correct words to the cache.
						// But not those that are correct only because of
						// the temporary supplied settings.
						if (!xTmpRes.is()  &&  0 == rProperties.getLength())
                            GetCache().AddWord( aChkWord, nLanguage );
					}
				}
				else
					bTmpResValid = sal_False;

                // return first found result if the word is not known by any checker.
                // But if that result has no suggestions use the first one that does 
                // provide suggestions for the misspelled word.
				if (!xRes.is() && bTmpResValid)
                {
					xRes = xTmpRes;
                    nNumSugestions = 0;
                    if (xRes.is())
                        nNumSugestions = xRes->getAlternatives().getLength();
                }
                sal_Int32 nTmpNumSugestions = 0;
                if (xTmpRes.is() && bTmpResValid)
                    nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
                if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
                {
                    xRes = xTmpRes;
                    nNumSugestions = nTmpNumSugestions;
                }    

				++i;
			}
		}
		
		// if still no result instantiate new services and try those
		if ((!bTmpResValid || xTmpRes.is())
            &&  pEntry->nLastTriedSvcIndex < nLen - 1)
		{
			const OUString *pImplNames = pEntry->aSvcImplNames.getConstArray();
			Reference< XSpellChecker >  *pRef  = pEntry->aSvcRefs .getArray();
			
			Reference< XMultiServiceFactory >  xMgr( getProcessServiceFactory() );
			if (xMgr.is())
			{
				// build service initialization argument
				Sequence< Any > aArgs(2);
				aArgs.getArray()[0] <<= GetPropSet();
				//! The dispatcher searches the dictionary-list
				//! thus the service needs not to now about it
				//aArgs.getArray()[1] <<= GetDicList();

                sal_Int32 nNumSugestions = -1;
				while (i < nLen  &&  (!bTmpResValid || xTmpRes.is()))
				{
					// create specific service via it's implementation name
					Reference< XSpellChecker > xSpell;
					try
					{
						xSpell = Reference< XSpellChecker >( 
								xMgr->createInstanceWithArguments( 
								pImplNames[i], aArgs ), UNO_QUERY );
					}
					catch (uno::Exception &)
					{
                        DBG_ASSERT( 0, "createInstanceWithArguments failed" );
					}
					pRef [i] = xSpell;
		
					Reference< XLinguServiceEventBroadcaster > 
							xBroadcaster( xSpell, UNO_QUERY );
					if (xBroadcaster.is())
						rMgr.AddLngSvcEvtBroadcaster( xBroadcaster );
					
					bTmpResValid = sal_True;			   
                    if (xSpell.is()  &&  xSpell->hasLocale( aLocale ))
					{
                        sal_Bool bOK = GetCache().CheckWord( aChkWord, nLanguage );
						if (bOK)
							xTmpRes = NULL;
						else
						{
                            xTmpRes = xSpell->spell( aChkWord, aLocale, rProperties );
							
							// Add correct words to the cache.
							// But not those that are correct only because of
							// the temporary supplied settings.
							if (!xTmpRes.is()  &&  0 == rProperties.getLength())
                                GetCache().AddWord( aChkWord, nLanguage );
						}
					}
					else
						bTmpResValid = sal_False;
				
                    // return first found result if the word is not known by any checker.
                    // But if that result has no suggestions use the first one that does 
                    // provide suggestions for the misspelled word.
                    if (!xRes.is() && bTmpResValid)
                    {
                        xRes = xTmpRes;
                        nNumSugestions = 0;
                        if (xRes.is())
                            nNumSugestions = xRes->getAlternatives().getLength();
                    }
                    sal_Int32 nTmpNumSugestions = 0;
                    if (xTmpRes.is() && bTmpResValid)
                        nTmpNumSugestions = xTmpRes->getAlternatives().getLength();
                    if (xRes.is() && nNumSugestions == 0 && nTmpNumSugestions > 0)
                    {
                        xRes = xTmpRes;
                        nNumSugestions = nTmpNumSugestions;
                    }    

                    pEntry->nLastTriedSvcIndex = (sal_Int16) i;
					++i;
				}

                // if language is not supported by any of the services
                // remove it from the list.
                if (i == nLen)
                {
                    if (!SvcListHasLanguage( *pEntry, nLanguage ))
                        aSvcMap.erase( nLanguage );
                }
			}
		}
		
		// if word is finally found to be correct
		// clear previously remembered alternatives
		if (bTmpResValid  &&  !xTmpRes.is())
			xRes = NULL;

        // list of proposals found (to be checked against entries of
        // neagtive dictionaries)
        ProposalList aProposalList;
//        Sequence< OUString > aProposals;
        sal_Int16 eFailureType = -1;	// no failure
        if (xRes.is())
        {
            aProposalList.Append( xRes->getAlternatives() );
//            aProposals = xRes->getAlternatives();
            eFailureType = xRes->getFailureType();
        }    
        Reference< XDictionaryList > xDList;
        if (GetDicList().is()  &&  IsUseDicList( rProperties, GetPropSet() ))
            xDList = Reference< XDictionaryList >( GetDicList(), UNO_QUERY );

		// cross-check against results from user-dictionaries which have precedence!
        if (bCheckDics  &&  xDList.is())
		{
            Reference< XDictionaryEntry > xTmp( lcl_GetRulingDictionaryEntry( aChkWord, nLanguage ) );
            if (xTmp.is())
            {
                if (xTmp->isNegative())    // positive entry found
                {
				    eFailureType = SpellFailure::IS_NEGATIVE_WORD;

                    // replacement text to be added to suggestions, if not empty
                    OUString aAddRplcTxt( xTmp->getReplacementText() );

                    // replacement text must not be in negative dictionary itself
                    if (aAddRplcTxt.getLength() &&
                        !SearchDicList( xDList, aAddRplcTxt, nLanguage, sal_False, sal_True ).is())
                    {
                        aProposalList.Prepend( aAddRplcTxt );
                    }
                }
                else    // positive entry found
                {
                    xRes = NULL;
                    eFailureType = -1;  // no failure
                }
            }
		}

        if (eFailureType != -1)     // word misspelled or found in negative user-dictionary
		{
            // search suitable user-dictionaries for suggestions that are
            // similar to the misspelled word
            std::vector< OUString > aDicListProps;   // list of proposals from user-dictionaries
            SearchSimilarText( aChkWord, nLanguage, xDList, aDicListProps );
            aProposalList.Append( aDicListProps );
            Sequence< OUString > aProposals = aProposalList.GetSequence();
            
            // remove entries listed in negative dictionaries
            // (we don't want to display suggestions that will be regarded as misspelledlater on)
            if (bCheckDics  &&  xDList.is())
                SeqRemoveNegEntries( aProposals, xDList, nLanguage );
            
            uno::Reference< linguistic2::XSetSpellAlternatives > xSetAlt( xRes, uno::UNO_QUERY );
            if (xSetAlt.is())
            {
                xSetAlt->setAlternatives( aProposals );
                xSetAlt->setFailureType( eFailureType );
            }
            else
            {
                if (xRes.is())
                {
                    DBG_ASSERT( 0, "XSetSpellAlternatives not implemented!" );
                }
                else if (aProposals.getLength() > 0)
                {
                    // no xRes but Proposals found from the user-dictionaries.
                    // Thus we need to create an xRes...
                    xRes = new linguistic::SpellAlternatives( rWord, nLanguage, 
                            SpellFailure::IS_NEGATIVE_WORD, aProposals );
                }
            }
		}
	}

	return xRes;
}

uno::Sequence< sal_Int16 > SAL_CALL SpellCheckerDispatcher::getLanguages(  ) 
throw (uno::RuntimeException)
{
    MutexGuard  aGuard( GetLinguMutex() );
    uno::Sequence< Locale > aTmp( getLocales() );
    uno::Sequence< sal_Int16 > aRes( LocaleSeqToLangSeq( aTmp ) );
    return aRes;
}

    
sal_Bool SAL_CALL SpellCheckerDispatcher::hasLanguage( 
    sal_Int16 nLanguage ) 
throw (uno::RuntimeException)
{
    MutexGuard  aGuard( GetLinguMutex() );
    Locale aLocale( CreateLocale( nLanguage ) );
    return hasLocale( aLocale );
}

    
sal_Bool SAL_CALL SpellCheckerDispatcher::isValid( 
    const OUString& rWord, 
    sal_Int16 nLanguage, 
    const uno::Sequence< beans::PropertyValue >& rProperties ) 
throw (lang::IllegalArgumentException, uno::RuntimeException)
{
    MutexGuard  aGuard( GetLinguMutex() );
    Locale aLocale( CreateLocale( nLanguage ) );
    return isValid( rWord, aLocale, rProperties);
}

    
uno::Reference< linguistic2::XSpellAlternatives > SAL_CALL SpellCheckerDispatcher::spell( 
    const OUString& rWord, 
    sal_Int16 nLanguage, 
    const uno::Sequence< beans::PropertyValue >& rProperties ) 
throw (lang::IllegalArgumentException, uno::RuntimeException)
{
    MutexGuard  aGuard( GetLinguMutex() );
    Locale aLocale( CreateLocale( nLanguage ) );
    return spell( rWord, aLocale, rProperties);
}

    
void SpellCheckerDispatcher::SetServiceList( const Locale &rLocale,
		const Sequence< OUString > &rSvcImplNames )
{
	MutexGuard	aGuard( GetLinguMutex() );

    if (pCache)
        pCache->Flush();    // new services may spell differently...

	sal_Int16 nLanguage = LocaleToLanguage( rLocale );

    sal_Int32 nLen = rSvcImplNames.getLength();
    if (0 == nLen)
        // remove entry
        aSvcMap.erase( nLanguage );
    else
    {
        // modify/add entry
        LangSvcEntries_Spell *pEntry = aSvcMap[ nLanguage ].get();
        if (pEntry)
        {
            pEntry->Clear();
            pEntry->aSvcImplNames = rSvcImplNames;
            pEntry->aSvcRefs = Sequence< Reference < XSpellChecker > > ( nLen );
        }
        else
        {
            boost::shared_ptr< LangSvcEntries_Spell > pTmpEntry( new LangSvcEntries_Spell( rSvcImplNames ) );
            pTmpEntry->aSvcRefs = Sequence< Reference < XSpellChecker > >( nLen );
            aSvcMap[ nLanguage ] = pTmpEntry;
        }
    }
}


Sequence< OUString > 
	SpellCheckerDispatcher::GetServiceList( const Locale &rLocale ) const
{
	MutexGuard	aGuard( GetLinguMutex() );
	
	Sequence< OUString > aRes;
	
	// search for entry with that language and use data from that
	sal_Int16 nLanguage = LocaleToLanguage( rLocale );
    SpellCheckerDispatcher          *pThis = (SpellCheckerDispatcher *) this;
    const SpellSvcByLangMap_t::iterator aIt( pThis->aSvcMap.find( nLanguage ) );
    const LangSvcEntries_Spell      *pEntry = aIt != aSvcMap.end() ? aIt->second.get() : NULL;
	if (pEntry)
		aRes = pEntry->aSvcImplNames;

	return aRes;
}


LinguDispatcher::DspType SpellCheckerDispatcher::GetDspType() const
{
	return DSP_SPELL;
}

void SpellCheckerDispatcher::FlushSpellCache()
{
    if (pCache)
        pCache->Flush();
}    

///////////////////////////////////////////////////////////////////////////

