/**************************************************************
 * 
 * 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_sw.hxx"

#include <string.h>

#include <vcl/window.hxx>

#include <wrtsh.hxx>

#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/request.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editdata.hxx>
#include <editeng/outliner.hxx>
#include <editeng/editview.hxx>
#include <editeng/scripttypeitem.hxx>
#include <editeng/langitem.hxx>

#include <svl/languageoptions.hxx>
#include <svtools/langtab.hxx>
#include <svl/slstitm.hxx>
#include <svl/svstdarr.hxx>
#include <svl/stritem.hxx>

#include <ndtxt.hxx>
#include <pam.hxx>
#include <view.hxx>
#include <viewopt.hxx>

#include "swabstdlg.hxx"

#include <vcl/msgbox.hxx>

#include <langhelper.hxx>

using namespace ::com::sun::star;

namespace SwLangHelper
{

    sal_uInt16 GetLanguageStatus( OutlinerView* pOLV, SfxItemSet& rSet )
	{
		ESelection aSelection = pOLV->GetSelection();
		EditView& rEditView=pOLV->GetEditView();
		EditEngine* pEditEngine=rEditView.GetEditEngine();

		// the value of used script types
		const sal_uInt16 nScriptType =pOLV->GetSelectedScriptType();	
		String aScriptTypesInUse( String::CreateFromInt32( nScriptType ) );//pEditEngine->GetScriptType(aSelection)

		SvtLanguageTable aLangTable;
		
		// get keyboard language
		String aKeyboardLang;
		LanguageType nLang = LANGUAGE_DONTKNOW;
                
		Window* pWin = rEditView.GetWindow();
		if(pWin)
			nLang = pWin->GetInputLanguage();
		if (nLang != LANGUAGE_DONTKNOW && nLang != LANGUAGE_SYSTEM)
			aKeyboardLang = aLangTable.GetString( nLang );	

		// get the language that is in use
		const String aMultipleLanguages = String::CreateFromAscii("*");
		String aCurrentLang = aMultipleLanguages;
		SfxItemSet aSet(pOLV->GetAttribs());
		nLang = SwLangHelper::GetCurrentLanguage( aSet,nScriptType );
		if (nLang != LANGUAGE_DONTKNOW)
			aCurrentLang = aLangTable.GetString( nLang );
			   
		// build sequence for status value
		uno::Sequence< ::rtl::OUString > aSeq( 4 );
		aSeq[0] = aCurrentLang;
		aSeq[1] = aScriptTypesInUse;
		aSeq[2] = aKeyboardLang;
		aSeq[3] = SwLangHelper::GetTextForLanguageGuessing( pEditEngine, aSelection );
                
		// set sequence as status value
		SfxStringListItem aItem( SID_LANGUAGE_STATUS );
		aItem.SetStringList( aSeq );
		rSet.Put( aItem, SID_LANGUAGE_STATUS );
        return 0;
	}

    bool SetLanguageStatus( OutlinerView* pOLV, SfxRequest &rReq, SwView &rView, SwWrtShell &rSh )
	{
		bool bRestoreSelection = false;
		SfxItemSet aEditAttr(pOLV->GetAttribs());
		ESelection   aSelection  = pOLV->GetSelection();
		EditView   & rEditView   = pOLV->GetEditView();
		EditEngine * pEditEngine = rEditView.GetEditEngine();

		// get the language
		String aNewLangTxt;
            
		SFX_REQUEST_ARG( rReq, pItem, SfxStringItem, SID_LANGUAGE_STATUS , sal_False );
		if (pItem)
			aNewLangTxt = pItem->GetValue();

		//!! Remember the view frame right now...
		//!! (call to GetView().GetViewFrame() will break if the
		//!! SwTextShell got destroyed meanwhile.)
		SfxViewFrame *pViewFrame = rView.GetViewFrame();
            
        if (aNewLangTxt.EqualsAscii( "*" ))
		{
            // open the dialog "Tools/Options/Language Settings - Language"
			SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
			if (pFact)
			{    
                VclAbstractDialog* pDlg = pFact->CreateVclDialog( rView.GetWindow(), SID_LANGUAGE_OPTIONS ); 
				pDlg->Execute();    
				delete pDlg;
			}
		}
		else
		{
            // setting the new language...
            if (aNewLangTxt.Len() > 0)
			{
                const String aSelectionLangPrefix( String::CreateFromAscii("Current_") );
                const String aParagraphLangPrefix( String::CreateFromAscii("Paragraph_") );
                const String aDocumentLangPrefix( String::CreateFromAscii("Default_") );
                const String aStrNone( String::CreateFromAscii("LANGUAGE_NONE") );
				const String aStrResetLangs( String::CreateFromAscii("RESET_LANGUAGES") );

				xub_StrLen nPos = 0;
                bool bForSelection = true;
                bool bForParagraph = false;
                if (STRING_NOTFOUND != (nPos = aNewLangTxt.Search( aSelectionLangPrefix, 0 )))
                {
                    // ... for the current selection
                    aNewLangTxt = aNewLangTxt.Erase( nPos, aSelectionLangPrefix.Len() );
                    bForSelection = true;
				}
                else if (STRING_NOTFOUND != (nPos = aNewLangTxt.Search( aParagraphLangPrefix , 0 )))
                {
                    // ... for the current paragraph language
                    aNewLangTxt = aNewLangTxt.Erase( nPos, aParagraphLangPrefix.Len() );
                    bForSelection = true;
                    bForParagraph = true;
                }
                else if (STRING_NOTFOUND != (nPos = aNewLangTxt.Search( aDocumentLangPrefix , 0 )))
                {
                    // ... as default document language
                    aNewLangTxt = aNewLangTxt.Erase( nPos, aDocumentLangPrefix.Len() );
                    bForSelection = false;
				}

                if (bForParagraph)
				{
					bRestoreSelection = true;
                    SwLangHelper::SelectPara( rEditView, aSelection );
					aSelection = pOLV->GetSelection();
				}
                if (!bForSelection) // document language to be changed...
                {
                    rSh.StartAction();
                    rSh.LockView( sal_True );
                    rSh.Push();
                    
                    // prepare to apply new language to all text in document
                    rSh.SelAll();
					rSh.ExtendedSelectAll();
                }
                
                if (aNewLangTxt == aStrNone)
                    SwLangHelper::SetLanguage_None( rSh, pOLV, aSelection, bForSelection, aEditAttr );
                else if (aNewLangTxt == aStrResetLangs)
                    SwLangHelper::ResetLanguages( rSh, pOLV, aSelection, bForSelection );
                else
					SwLangHelper::SetLanguage( rSh, pOLV, aSelection, aNewLangTxt, bForSelection, aEditAttr );
                
				// ugly hack, as it seems that EditView/EditEngine does not update their spellchecking marks
				// when setting a new language attribute
				if (bForSelection) 
				{
					const SwViewOption* pVOpt = rView.GetWrtShellPtr()->GetViewOptions();
					sal_uLong nCntrl = pEditEngine->GetControlWord();
					// turn off
					if (!pVOpt->IsOnlineSpell())
						nCntrl &= ~EE_CNTRL_ONLINESPELLING;
					else
						nCntrl &= ~EE_CNTRL_ONLINESPELLING;
					pEditEngine->SetControlWord(nCntrl);

					//turn back on
					if (pVOpt->IsOnlineSpell())
						nCntrl |= EE_CNTRL_ONLINESPELLING;
					else
						nCntrl &= ~EE_CNTRL_ONLINESPELLING;
					pEditEngine->SetControlWord(nCntrl);

					pEditEngine->CompleteOnlineSpelling();
					rEditView.Invalidate();
				}

                if (!bForSelection) 
                {
                    // need to release view and restore selection...
                    rSh.Pop( sal_False );
                    rSh.LockView( sal_False );
                    rSh.EndAction();
                }
			}
		}
        
        // invalidate slot to get the new language displayed
        pViewFrame->GetBindings().Invalidate( rReq.GetSlot() );
        
        rReq.Done();
		return bRestoreSelection;
	}
	

    void SetLanguage( SwWrtShell &rWrtSh, const String &rLangText, bool bIsForSelection, SfxItemSet &rCoreSet )
	{
		SetLanguage( rWrtSh, 0 , ESelection(), rLangText, bIsForSelection, rCoreSet );
	}

    void SetLanguage( SwWrtShell &rWrtSh, OutlinerView* pOLV, ESelection aSelection, const String &rLangText, bool bIsForSelection, SfxItemSet &rCoreSet )
	{
		const LanguageType nLang = SvtLanguageTable().GetType( rLangText );
		if (nLang != LANGUAGE_DONTKNOW)
		{
			sal_uInt16 nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLang );
	        
            EditEngine* pEditEngine = pOLV ? pOLV->GetEditView().GetEditEngine() : NULL;
            DBG_ASSERT( !pOLV || pEditEngine, "OutlinerView without EditEngine???" );

			//get ScriptType
			sal_uInt16 nLangWhichId = 0;
			bool bIsSingleScriptType = true;
			switch (nScriptType)
			{
				case SCRIPTTYPE_LATIN :    nLangWhichId = pEditEngine ? EE_CHAR_LANGUAGE : RES_CHRATR_LANGUAGE; break;
				case SCRIPTTYPE_ASIAN :    nLangWhichId = pEditEngine ? EE_CHAR_LANGUAGE_CJK : RES_CHRATR_CJK_LANGUAGE; break;
				case SCRIPTTYPE_COMPLEX :  nLangWhichId = pEditEngine ? EE_CHAR_LANGUAGE_CTL : RES_CHRATR_CTL_LANGUAGE; break;
				default: 
					bIsSingleScriptType = false;
					DBG_ERROR( "unexpected case" );
			}
			if (bIsSingleScriptType)
			{
                // change language for selection or paragraph
                // (for paragraph is handled by previosuly having set the selection to the
                // whole paragraph)
				if (bIsForSelection)
				{
					// apply language to current selection
					if (pEditEngine)
					{
						rCoreSet.Put( SvxLanguageItem( nLang, nLangWhichId ));
						pEditEngine->QuickSetAttribs( rCoreSet, aSelection);
					}
					else
					{
						rWrtSh.GetCurAttr( rCoreSet );
						rCoreSet.Put( SvxLanguageItem( nLang, nLangWhichId ));
						rWrtSh.SetAttrSet( rCoreSet );
					}
				}
				else // change language for all text
				{
					// set document default language
					switch (nLangWhichId)
					{
						 case EE_CHAR_LANGUAGE :      nLangWhichId = RES_CHRATR_LANGUAGE; break;
						 case EE_CHAR_LANGUAGE_CJK :  nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
						 case EE_CHAR_LANGUAGE_CTL :  nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
					}
					rWrtSh.SetDefault( SvxLanguageItem( nLang, nLangWhichId ) );
	            
					// #i102191: hard set respective language attribute in text document
					// (for all text in the document - which should be selected by now...)
					rWrtSh.SetAttrItem( SvxLanguageItem( nLang, nLangWhichId ) );
				}
			}
		}
	}

    void SetLanguage_None( SwWrtShell &rWrtSh, bool bIsForSelection, SfxItemSet &rCoreSet )
	{
        SetLanguage_None( rWrtSh,0,ESelection(),bIsForSelection,rCoreSet );
	}

    void SetLanguage_None( SwWrtShell &rWrtSh, OutlinerView* pOLV, ESelection aSelection, bool bIsForSelection, SfxItemSet &rCoreSet )
	{
	    // EditEngine IDs
	    const sal_uInt16 aLangWhichId_EE[3] =
	    {
		    EE_CHAR_LANGUAGE,
		    EE_CHAR_LANGUAGE_CJK,
		    EE_CHAR_LANGUAGE_CTL
	    };

	    // Writewr IDs
	    const sal_uInt16 aLangWhichId_Writer[3] =
	    {
		    RES_CHRATR_LANGUAGE,
		    RES_CHRATR_CJK_LANGUAGE,
		    RES_CHRATR_CTL_LANGUAGE
	    };

		if (bIsForSelection)
		{
            // change language for selection or paragraph
            // (for paragraph is handled by previosuly having set the selection to the
            // whole paragraph)

            EditEngine* pEditEngine = pOLV ? pOLV->GetEditView().GetEditEngine() : NULL;
            DBG_ASSERT( !pOLV || pEditEngine, "OutlinerView without EditEngine???" );
            if (pEditEngine)
			{
				for (sal_uInt16 i = 0; i < 3; ++i)
					rCoreSet.Put( SvxLanguageItem( LANGUAGE_NONE, aLangWhichId_EE[i] ));
				pEditEngine->QuickSetAttribs( rCoreSet, aSelection);
			}
			else
			{
				rWrtSh.GetCurAttr( rCoreSet );
				for (sal_uInt16 i = 0; i < 3; ++i)
					rCoreSet.Put( SvxLanguageItem( LANGUAGE_NONE, aLangWhichId_Writer[i] ));
				rWrtSh.SetAttrSet( rCoreSet );
			}
		}
		else // change language for all text
		{
			SvUShortsSort aAttribs;
			for (sal_uInt16 i = 0; i < 3; ++i)
			{
				rWrtSh.SetDefault( SvxLanguageItem( LANGUAGE_NONE, aLangWhichId_Writer[i] ) );
				aAttribs.Insert( aLangWhichId_Writer[i] );
			}

			// set all language attributes to default
			// (for all text in the document - which should be selected by now...)
			rWrtSh.ResetAttr( &aAttribs );
		}
	}

	void ResetLanguages( SwWrtShell &rWrtSh, bool bIsForSelection )
	{
		ResetLanguages( rWrtSh, 0 , ESelection(), bIsForSelection );
	}

	void ResetLanguages( SwWrtShell &rWrtSh, OutlinerView* pOLV, ESelection aSelection, bool bIsForSelection )
	{
        (void) bIsForSelection;
        (void) aSelection;

        // reset language for current selection.
        // The selection should already have been expanded to the whole paragraph or 
        // to all text in the document if those are the ranges where to reset 
        // the language attributes

		if (pOLV)
		{
            EditView &rEditView = pOLV->GetEditView();
            rEditView.RemoveAttribs( true, EE_CHAR_LANGUAGE );
            rEditView.RemoveAttribs( true, EE_CHAR_LANGUAGE_CJK );
            rEditView.RemoveAttribs( true, EE_CHAR_LANGUAGE_CTL );
		}
		else
		{
		    SvUShortsSort aAttribs;
		    aAttribs.Insert( RES_CHRATR_LANGUAGE );
		    aAttribs.Insert( RES_CHRATR_CJK_LANGUAGE );
		    aAttribs.Insert( RES_CHRATR_CTL_LANGUAGE );
		    rWrtSh.ResetAttr( &aAttribs );
		}
    }


	/// @returns : the language for the selected text that is set for the
	///     specified attribute (script type).
	///     If there are more than one languages used LANGUAGE_DONTKNOW will be returned.
	/// @param nLangWhichId : one of
	///     RES_CHRATR_LANGUAGE, RES_CHRATR_CJK_LANGUAGE, RES_CHRATR_CTL_LANGUAGE,
	LanguageType GetLanguage( SwWrtShell &rSh, sal_uInt16 nLangWhichId )
	{
		SfxItemSet aSet( rSh.GetAttrPool(), nLangWhichId, nLangWhichId );
		rSh.GetCurAttr( aSet );

		return GetLanguage(aSet,nLangWhichId);
	}

	LanguageType GetLanguage( SfxItemSet aSet, sal_uInt16 nLangWhichId )
	{

		LanguageType nLang = LANGUAGE_SYSTEM;

		const SfxPoolItem *pItem = 0;
		SfxItemState nState = aSet.GetItemState( nLangWhichId, sal_True, &pItem );
		if (nState > SFX_ITEM_DEFAULT && pItem)
		{
			// the item is set and can be used
			nLang = (dynamic_cast< const SvxLanguageItem* >(pItem))->GetLanguage();
		}
		else if (nState == SFX_ITEM_DEFAULT)
		{
			// since the attribute is not set: retrieve the default value 
			nLang = (dynamic_cast< const SvxLanguageItem& >(aSet.GetPool()->GetDefaultItem( nLangWhichId ))).GetLanguage();
		}
		else if (nState == SFX_ITEM_DONTCARE)
		{
			// there is more than one language...
			nLang = LANGUAGE_DONTKNOW;
		}
		DBG_ASSERT( nLang != LANGUAGE_SYSTEM, "failed to get the language?" );

		return nLang;
	}

	/// @returns: the language in use for the selected text.
	///     'In use' means the language(s) matching the script type(s) of the
	///     selected text. Or in other words, the language a spell checker would use.
	///     If there is more than one language LANGUAGE_DONTKNOW will be returned.
	LanguageType GetCurrentLanguage( SwWrtShell &rSh )
	{
		// get all script types used in current selection
		const sal_uInt16 nScriptType = rSh.GetScriptType();

		//set language attribute to use according to the script type
		sal_uInt16 nLangWhichId = 0;
		bool bIsSingleScriptType = true;
		switch (nScriptType)
		{
			 case SCRIPTTYPE_LATIN :    nLangWhichId = RES_CHRATR_LANGUAGE; break;
			 case SCRIPTTYPE_ASIAN :    nLangWhichId = RES_CHRATR_CJK_LANGUAGE; break;
			 case SCRIPTTYPE_COMPLEX :  nLangWhichId = RES_CHRATR_CTL_LANGUAGE; break;
			 default: bIsSingleScriptType = false; break;
		}

		// get language according to the script type(s) in use
		LanguageType nCurrentLang = LANGUAGE_SYSTEM;
		if (bIsSingleScriptType)
			nCurrentLang = GetLanguage( rSh, nLangWhichId );
		else
		{
			// check if all script types are set to LANGUAGE_NONE and return
			// that if this is the case. Otherwise, having multiple script types
			// in use always means there are several languages in use...
			const sal_uInt16 aScriptTypes[3] =
			{
				RES_CHRATR_LANGUAGE,
				RES_CHRATR_CJK_LANGUAGE,
				RES_CHRATR_CTL_LANGUAGE
			};
			nCurrentLang = LANGUAGE_NONE;
			for (sal_uInt16 i = 0; i < 3; ++i)
			{
				LanguageType nTmpLang = GetLanguage( rSh, aScriptTypes[i] );
				if (nTmpLang != LANGUAGE_NONE)
				{
					nCurrentLang = LANGUAGE_DONTKNOW;
					break;
				}
			}
		}
		DBG_ASSERT( nCurrentLang != LANGUAGE_SYSTEM, "failed to get the language?" );

		return nCurrentLang;
	}

	/// @returns: the language in use for the selected text.
	///     'In use' means the language(s) matching the script type(s) of the
	///     selected text. Or in other words, the language a spell checker would use.
	///     If there is more than one language LANGUAGE_DONTKNOW will be returned.
    LanguageType GetCurrentLanguage( SfxItemSet aSet, sal_uInt16 nScriptType )
	{
		//set language attribute to use according to the script type
		sal_uInt16 nLangWhichId = 0;
		bool bIsSingleScriptType = true;
		switch (nScriptType)
		{
			 case SCRIPTTYPE_LATIN :    nLangWhichId = EE_CHAR_LANGUAGE; break;
			 case SCRIPTTYPE_ASIAN :    nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
			 case SCRIPTTYPE_COMPLEX :  nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
			 default: bIsSingleScriptType = false;
		}
	    
		// get language according to the script type(s) in use
		LanguageType nCurrentLang = LANGUAGE_SYSTEM;
		if (bIsSingleScriptType)
			nCurrentLang = GetLanguage( aSet, nLangWhichId );
		else
		{
			// check if all script types are set to LANGUAGE_NONE and return
			// that if this is the case. Otherwise, having multiple script types
			// in use always means there are several languages in use...
			const sal_uInt16 aScriptTypes[3] =
			{
				EE_CHAR_LANGUAGE,
				EE_CHAR_LANGUAGE_CJK,
				EE_CHAR_LANGUAGE_CTL
			};
			nCurrentLang = LANGUAGE_NONE;
			for (sal_uInt16 i = 0; i < 3; ++i)
			{
				LanguageType nTmpLang = GetLanguage( aSet, aScriptTypes[i] );
				if (nTmpLang != LANGUAGE_NONE)
				{
					nCurrentLang = LANGUAGE_DONTKNOW;
					break;
				}    
			}
		}
		DBG_ASSERT( nCurrentLang != LANGUAGE_SYSTEM, "failed to get the language?" );

		return nCurrentLang;
	}

	String GetTextForLanguageGuessing( SwWrtShell &rSh )
	{
		// string for guessing language
		String aText;
		SwPaM *pCrsr = rSh.GetCrsr();
		SwTxtNode *pNode = pCrsr->GetNode()->GetTxtNode();
		if (pNode)
		{
			aText = pNode->GetTxt();
			if (aText.Len() > 0)
			{
				xub_StrLen nStt = 0;
				xub_StrLen nEnd = pCrsr->GetPoint()->nContent.GetIndex();
				// at most 100 chars to the left...
				nStt = nEnd > 100 ? nEnd - 100 : 0;
				// ... and 100 to the right of the cursor position
				nEnd = aText.Len() - nEnd > 100 ? nEnd + 100 : aText.Len();
				aText = aText.Copy( nStt, nEnd - nStt );
			}
		}
		return aText;
	}

    String GetTextForLanguageGuessing( EditEngine* rEditEngine, ESelection aDocSelection )
	{
		// string for guessing language
		String aText;
	    
		aText = rEditEngine->GetText(aDocSelection);
		if (aText.Len() > 0)
		{
			xub_StrLen nStt = 0;
			xub_StrLen nEnd = aDocSelection.nEndPos;
			// at most 100 chars to the left...
			nStt = nEnd > 100 ? nEnd - 100 : 0;
			// ... and 100 to the right of the cursor position
			nEnd = aText.Len() - nEnd > 100 ? nEnd + 100 : aText.Len();
			aText = aText.Copy( nStt, nEnd - nStt );
		}
	    
		return aText;
	}


	void SelectPara( EditView &rEditView, const ESelection &rCurSel )
	{
		ESelection aParaSel( rCurSel.nStartPara, 0, rCurSel.nStartPara, USHRT_MAX );
		rEditView.SetSelection( aParaSel );
	}

	void SelectCurrentPara( SwWrtShell &rWrtSh )
	{
		// select current para
		if (!rWrtSh.IsSttPara())
			rWrtSh.MovePara( fnParaCurr, fnParaStart );
		if (!rWrtSh.HasMark())
			rWrtSh.SetMark();
		rWrtSh.SwapPam();
		if (!rWrtSh.IsEndPara())
			rWrtSh.MovePara( fnParaCurr, fnParaEnd );
	#if OSL_DEBUG_LEVEL > 1
		String aSelTxt;
		rWrtSh.GetSelectedText( aSelTxt );
		(void) aSelTxt;
	#endif
	}
}

