/**************************************************************
 * 
 * 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_editeng.hxx"
#include<rtl/ustring.hxx>
#include <tools/shl.hxx>
#include <vcl/wrkwin.hxx>
#include <vcl/svapp.hxx>
#include <vcl/msgbox.hxx>
#include <tools/debug.hxx>
#include <svtools/langtab.hxx>

#ifndef __RSC
#include <tools/errinf.hxx>
#endif
#include <editeng/unolingu.hxx>
#include <linguistic/lngprops.hxx>
#include <com/sun/star/frame/XStorable.hpp>

#include <map>

#include <editeng/svxenum.hxx>
#include <editeng/splwrap.hxx>      // Der Wrapper
#include <editeng/edtdlg.hxx>      
#include <editeng/eerdll.hxx>
#include <editeng/editrids.hrc>
#include <editeng/editids.hrc>
#include <editeng/editerr.hxx>

#define WAIT_ON() if(pWin != NULL) { pWin->EnterWait(); }

#define WAIT_OFF() if(pWin != NULL) { pWin->LeaveWait(); }

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::linguistic2;


// misc functions ---------------------------------------------

void SvxPrepareAutoCorrect( String &rOldText, String &rNewText )
{
	// This function should be used to strip (or add) trailing '.' from
	// the strings before passing them on to the autocorrect function in
	// order that the autocorrect function will hopefully
	// works properly with normal words and abbreviations (with trailing '.')
	// independ of if they are at the end of the sentence or not.
	//
	// rOldText: text to be replaced
	// rNewText: replacement text

	xub_StrLen	nOldLen = rOldText.Len(),
				nNewLen = rNewText.Len();
	if (nOldLen && nNewLen)
	{
		sal_Bool bOldHasDot = sal_Unicode( '.' ) == rOldText.GetChar( nOldLen - 1 ),
			 bNewHasDot = sal_Unicode( '.' ) == rNewText.GetChar( nNewLen - 1 );
		if (bOldHasDot && !bNewHasDot
			/*this is: !(bOldHasDot && bNewHasDot) && bOldHasDot*/)
			rOldText.Erase( nOldLen - 1 );
	}
}

// -----------------------------------------------------------------------

#define SVX_LANG_NEED_CHECK			0
#define SVX_LANG_OK					1
#define SVX_LANG_MISSING			2
#define SVX_LANG_MISSING_DO_WARN	3

#define SVX_FLAGS_NEW


struct lt_LanguageType
{
    bool operator()( LanguageType n1, LanguageType n2 ) const
    {
        return n1 < n2;
    }
};

typedef std::map< LanguageType, sal_uInt16, lt_LanguageType >   LangCheckState_map_t;

static LangCheckState_map_t & GetLangCheckState()
{
    static LangCheckState_map_t aLangCheckState;
    return aLangCheckState;
}

void SvxSpellWrapper::ShowLanguageErrors()
{
    // display message boxes for languages not available for
    // spellchecking or hyphenation
    LangCheckState_map_t &rLCS = GetLangCheckState();
    LangCheckState_map_t::iterator aIt( rLCS.begin() );
    while (aIt != rLCS.end())
	{
        LanguageType nLang = aIt->first;
        sal_uInt16   nVal  = aIt->second;
		sal_uInt16 nTmpSpell = nVal & 0x00FF;
		sal_uInt16 nTmpHyph  = (nVal >> 8) & 0x00FF;

		if (SVX_LANG_MISSING_DO_WARN == nTmpSpell)
		{
			String aErr( SvtLanguageTable::GetLanguageString( nLang ) );
			ErrorHandler::HandleError(
				*new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) );
			nTmpSpell = SVX_LANG_MISSING;
		}
		if (SVX_LANG_MISSING_DO_WARN == nTmpHyph)
		{
			String aErr( SvtLanguageTable::GetLanguageString( nLang ) );
			ErrorHandler::HandleError(
				*new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) );
			nTmpHyph = SVX_LANG_MISSING;
		}

        rLCS[ nLang ] = (nTmpHyph << 8) | nTmpSpell;
        ++aIt;
	}

}

SvxSpellWrapper::~SvxSpellWrapper()
{
}

/*--------------------------------------------------------------------
 *	Beschreibung: Ctor, die Pruefreihenfolge wird festgelegt
 *
 *  !bStart && !bOtherCntnt:	BODY_END,	BODY_START,	OTHER
 *  !bStart && bOtherCntnt:		OTHER,		BODY
 *  bStart && !bOtherCntnt:		BODY_END,	OTHER
 *  bStart && bOtherCntnt:		OTHER
 *
 --------------------------------------------------------------------*/

SvxSpellWrapper::SvxSpellWrapper( Window* pWn,
	Reference< XSpellChecker1 >  &xSpellChecker,
	const sal_Bool bStart, const sal_Bool bIsAllRight,
	const sal_Bool bOther, const sal_Bool bRevAllow ) :

	pWin		( pWn ),
	xSpell		( xSpellChecker ),
	bOtherCntnt	( bOther ),
	bDialog		( sal_False ),
	bHyphen		( sal_False ),
	bAuto		( sal_False ),
	bStartChk	( bOther ),
    bRevAllowed ( bRevAllow ),
    bAllRight   ( bIsAllRight )
{
	Reference< beans::XPropertySet >  xProp( SvxGetLinguPropertySet() );
	sal_Bool bWrapReverse = xProp.is() ?
		*(sal_Bool*)xProp->getPropertyValue(
			::rtl::OUString::createFromAscii(UPN_IS_WRAP_REVERSE) ).getValue()
		: sal_False;
	bReverse = bRevAllow && bWrapReverse;
	bStartDone = bOther || ( !bReverse && bStart );
	bEndDone   = bReverse && bStart && !bOther;
}

// -----------------------------------------------------------------------

SvxSpellWrapper::SvxSpellWrapper( Window* pWn,
		Reference< XHyphenator >  &xHyphenator,
		const sal_Bool bStart, const sal_Bool bOther ) :
	pWin		( pWn ),
	xHyph		( xHyphenator ),
	bOtherCntnt	( bOther ),
	bDialog		( sal_False ),
	bHyphen		( sal_False ),
	bAuto		( sal_False ),
	bReverse	( sal_False ),
	bStartDone	( bOther || ( !bReverse && bStart ) ),
	bEndDone	( bReverse && bStart && !bOther ),
	bStartChk	( bOther ),
    bRevAllowed ( sal_False ),
    bAllRight   ( sal_True )
{
}

// -----------------------------------------------------------------------

sal_Int16 SvxSpellWrapper::CheckSpellLang(
		Reference< XSpellChecker1 > xSpell, sal_Int16 nLang)
{
    LangCheckState_map_t &rLCS = GetLangCheckState();

    LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) );
    sal_uInt16 nVal = aIt == rLCS.end() ? SVX_LANG_NEED_CHECK : aIt->second;

    if (aIt == rLCS.end())
        rLCS[ nLang ] = nVal;

	if (SVX_LANG_NEED_CHECK == (nVal & 0x00FF))
	{
		sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN;
		if (xSpell.is()  &&  xSpell->hasLanguage( nLang ))
			nTmpVal = SVX_LANG_OK;
		nVal &= 0xFF00;
		nVal |= nTmpVal;

        rLCS[ nLang ] = nVal;
	}

    return (sal_Int16) nVal;
}

sal_Int16 SvxSpellWrapper::CheckHyphLang(
		Reference< XHyphenator >  xHyph, sal_Int16 nLang)
{
    LangCheckState_map_t &rLCS = GetLangCheckState();

    LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) );
    sal_uInt16 nVal = aIt == rLCS.end() ? 0 : aIt->second;

    if (aIt == rLCS.end())
        rLCS[ nLang ] = nVal;

	if (SVX_LANG_NEED_CHECK == ((nVal >> 8) & 0x00FF))
	{
		sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN;
		if (xHyph.is()  &&  xHyph->hasLocale( SvxCreateLocale( nLang ) ))
			nTmpVal = SVX_LANG_OK;
		nVal &= 0x00FF;
		nVal |= nTmpVal << 8;

        rLCS[ nLang ] = nVal;
	}

    return (sal_Int16) nVal;
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::SpellStart( SvxSpellArea /*eSpell*/ )
{	// Hier muessen die notwendigen Vorbereitungen fuer SpellContinue
}	// im uebergebenen Bereich getroffen werden.

// -----------------------------------------------------------------------


sal_Bool SvxSpellWrapper::HasOtherCnt()
{
	return sal_False; // Gibt es ueberhaupt einen Sonderbereich?
}

// -----------------------------------------------------------------------


sal_Bool SvxSpellWrapper::SpellMore()
{
	return sal_False; // Sollen weitere Dokumente geprueft werden?
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::SpellEnd()
{	// Bereich ist abgeschlossen, ggf. Aufraeumen

    // display error for last language not found
    ShowLanguageErrors();
}

// -----------------------------------------------------------------------


sal_Bool SvxSpellWrapper::SpellContinue()
{
	return sal_False;
}

// -----------------------------------------------------------------------

void SvxSpellWrapper::AutoCorrect( const String&, const String& )
{
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::ScrollArea()
{	// Scrollarea einstellen
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::ChangeWord( const String&, const sal_uInt16 )
{	// Wort ersetzen
}

// -----------------------------------------------------------------------


String SvxSpellWrapper::GetThesWord()
{
	// Welches Wort soll nachgeschlagen werden?
	return String();
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::ChangeThesWord( const String& )
{
	// Wort wg. Thesaurus ersetzen
}

// -----------------------------------------------------------------------

void SvxSpellWrapper::StartThesaurus( const String &rWord, sal_uInt16 nLanguage )
{
	Reference< XThesaurus >  xThes( SvxGetThesaurus() );
	if (!xThes.is())
	{
		InfoBox( pWin, EE_RESSTR( RID_SVXSTR_HMERR_THESAURUS ) ).Execute();
		return;
	}

	WAIT_ON();	// while looking up for initial word
	EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
	AbstractThesaurusDialog* pDlg = pFact->CreateThesaurusDialog( pWin, xThes, rWord, nLanguage );
	WAIT_OFF();
	if ( pDlg->Execute()== RET_OK )
	{
		ChangeThesWord( pDlg->GetWord() );
	}
	delete pDlg;
}

// -----------------------------------------------------------------------

void SvxSpellWrapper::ReplaceAll( const String &, sal_Int16 )
{	// Wort aus der Replace-Liste ersetzen
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::SetLanguage( const sal_uInt16 )
{	// Sprache aendern
}

// -----------------------------------------------------------------------


void SvxSpellWrapper::InsertHyphen( const sal_uInt16 )
{	// Hyphen einfuegen bzw. loeschen
}

// -----------------------------------------------------------------------
// Pruefung der Dokumentbereiche in der durch die Flags angegebenen Reihenfolge


void SvxSpellWrapper::SpellDocument( )
{
	if ( bOtherCntnt )
	{
		bReverse = sal_False;
		SpellStart( SVX_SPELL_OTHER );
	}
	else
	{
		bStartChk = bReverse;
		SpellStart( bReverse ? SVX_SPELL_BODY_START : SVX_SPELL_BODY_END );
	}

	if ( FindSpellError() )
	{
		Reference< XSpellAlternatives >  	xAlt( GetLast(), UNO_QUERY );
		Reference< XHyphenatedWord > 		xHyphWord( GetLast(), UNO_QUERY );

		Window *pOld = pWin;
		bDialog = sal_True;
		if (xHyphWord.is())
		{
			EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
			AbstractHyphenWordDialog* pDlg = pFact->CreateHyphenWordDialog( pWin, 
							xHyphWord->getWord(),
							SvxLocaleToLanguage( xHyphWord->getLocale() ),
							xHyph, this );
			pWin = pDlg->GetWindow();
			pDlg->Execute();
			delete pDlg;
		}
		bDialog = sal_False;
		pWin = pOld;
	};
}

// -----------------------------------------------------------------------
// Naechsten Bereich auswaehlen


sal_Bool SvxSpellWrapper::SpellNext( )
{
	Reference< beans::XPropertySet >  xProp( SvxGetLinguPropertySet() );
	sal_Bool bWrapReverse = xProp.is() ?
			*(sal_Bool*)xProp->getPropertyValue(
				::rtl::OUString::createFromAscii(UPN_IS_WRAP_REVERSE) ).getValue()
			: sal_False;
	sal_Bool bActRev = bRevAllowed && bWrapReverse;

	// bActRev ist die Richtung nach dem Spellen, bReverse die am Anfang.
	if( bActRev == bReverse )
	{   						// Keine Richtungsaenderung, also ist
		if( bStartChk )         // der gewuenschte Bereich ( bStartChk )
			bStartDone = sal_True;  // vollstaendig abgearbeitet.
		else
			bEndDone = sal_True;
	}
	else if( bReverse == bStartChk ) // Bei einer Richtungsaenderung kann
	{ 						   // u.U. auch ein Bereich abgearbeitet sein.
		if( bStartChk )        // Sollte der vordere Teil rueckwaerts gespellt
			bEndDone = sal_True;   // werden und wir kehren unterwegs um, so ist
		else				   // der hintere Teil abgearbeitet (und umgekehrt).
			bStartDone = sal_True;
	}

	bReverse = bActRev;
	if( bOtherCntnt && bStartDone && bEndDone ) // Dokument komplett geprueft?
	{
		if ( SpellMore() )  // ein weiteres Dokument pruefen?
		{
			bOtherCntnt = sal_False;
			bStartDone = !bReverse;
			bEndDone  = bReverse;
			SpellStart( SVX_SPELL_BODY );
			return sal_True;
		}
		return sal_False;
	}

	sal_Bool bGoOn = sal_False;

	if ( bOtherCntnt )
	{
		bStartChk = sal_False;
		SpellStart( SVX_SPELL_BODY );
		bGoOn = sal_True;
	}
	else if ( bStartDone && bEndDone )
	{
		sal_Bool bIsSpellSpecial = xProp.is() ?
			*(sal_Bool*)xProp->getPropertyValue(
				::rtl::OUString::createFromAscii(UPN_IS_SPELL_SPECIAL) ).getValue()
			: sal_False;
		// Bodybereich erledigt, Frage nach Sonderbereich
		if( !IsHyphen() && bIsSpellSpecial && HasOtherCnt() )
		{
			SpellStart( SVX_SPELL_OTHER );
			bOtherCntnt = bGoOn = sal_True;
		}
		else if ( SpellMore() )  // ein weiteres Dokument pruefen?
		{
			bOtherCntnt = sal_False;
			bStartDone = !bReverse;
			bEndDone  = bReverse;
			SpellStart( SVX_SPELL_BODY );
			return sal_True;
		}
	}
	else
	{
		// Ein BODY_Bereich erledigt, Frage nach dem anderen BODY_Bereich
		WAIT_OFF();

// Sobald im Dialog das DontWrapAround gesetzt werden kann, kann der
// folgende #ifdef-Zweig aktiviert werden ...
#ifdef USED
		sal_Bool bDontWrapAround = IsHyphen() ?
			pSpell->GetOptions() & DONT_WRAPAROUND :
			pSpell->GetHyphOptions() & HYPH_DONT_WRAPAROUND;
		if( bDontWrapAround )
#else
		sal_uInt16 nResId = bReverse ? RID_SVXQB_BW_CONTINUE : RID_SVXQB_CONTINUE;
		QueryBox aBox( pWin, EditResId( nResId ) );
		if ( aBox.Execute() != RET_YES )
#endif

		{
			// Verzicht auf den anderen Bereich, ggf. Frage nach Sonderbereich
			WAIT_ON();
			bStartDone = bEndDone = sal_True;
			return SpellNext();
		}
		else
		{
			bStartChk = !bStartDone;
			SpellStart( bStartChk ? SVX_SPELL_BODY_START : SVX_SPELL_BODY_END );
			bGoOn = sal_True;
		}
		WAIT_ON();
	}
	return bGoOn;
}

// -----------------------------------------------------------------------

Reference< XDictionary >  SvxSpellWrapper::GetAllRightDic() const
{
    Reference< XDictionary >  xDic;

	Reference< XDictionaryList >  xDicList( SvxGetDictionaryList() );
	if (xDicList.is())
	{
		Sequence< Reference< XDictionary >  > aDics( xDicList->getDictionaries() );
		const Reference< XDictionary >  *pDic = aDics.getConstArray();
		sal_Int32 nCount = aDics.getLength();

		sal_Int32 i = 0;
		while (!xDic.is()  &&  i < nCount)
		{
            Reference< XDictionary >  xTmp( pDic[i], UNO_QUERY );
			if (xTmp.is())
			{
				if ( xTmp->isActive() &&
					 xTmp->getDictionaryType() != DictionaryType_NEGATIVE &&
                     SvxLocaleToLanguage( xTmp->getLocale() ) == LANGUAGE_NONE )
				{
					Reference< frame::XStorable >  xStor( xTmp, UNO_QUERY );
					if (xStor.is() && xStor->hasLocation() && !xStor->isReadonly())
					{
						xDic = xTmp;
					}
				}
			}
			++i;
		}

		if (!xDic.is())
		{
			xDic = SvxGetOrCreatePosDic( xDicList );
			if (xDic.is())
				xDic->setActive( sal_True );
		}
	}

	return xDic;
}

// -----------------------------------------------------------------------

sal_Bool SvxSpellWrapper::FindSpellError()
{
    ShowLanguageErrors();

 	Reference< XInterface > 	xRef;

	WAIT_ON();
	sal_Bool bSpell = sal_True;

    Reference< XDictionary >  xAllRightDic;
	if (IsAllRight())
		xAllRightDic = GetAllRightDic();

	while ( bSpell )
	{
		SpellContinue();

		Reference< XSpellAlternatives >  	xAlt( GetLast(), UNO_QUERY );
		Reference< XHyphenatedWord > 		xHyphWord( GetLast(), UNO_QUERY );

		if (xAlt.is())
		{
			if (IsAllRight() && xAllRightDic.is())
			{
				xAllRightDic->add( xAlt->getWord(), sal_False, ::rtl::OUString() );
			}
			else
			{
				// look up in ChangeAllList for misspelled word
                Reference< XDictionary >    xChangeAllList(
						SvxGetChangeAllList(), UNO_QUERY );
				Reference< XDictionaryEntry > 	xEntry;
				if (xChangeAllList.is())
					xEntry = xChangeAllList->getEntry( xAlt->getWord() );

				if (xEntry.is())
				{
					// replace word without asking
					ReplaceAll( xEntry->getReplacementText(),
								SvxLocaleToLanguage( xAlt->getLocale() ) );
				}
				else
					bSpell = sal_False;
			}
		}
		else if (xHyphWord.is())
			bSpell = sal_False;
		else
		{
			SpellEnd();
			bSpell = SpellNext();
		}
	}
	WAIT_OFF();
	return GetLast().is();
}



