/**************************************************************
 *
 * 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_sc.hxx"
#include "spelleng.hxx"
#include <com/sun/star/i18n/TextConversionOption.hpp>

#include <memory>

#include "scitems.hxx"
#include <editeng/eeitem.hxx>


#include <editeng/langitem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/editview.hxx>
#include <sfx2/viewfrm.hxx>
#include <vcl/msgbox.hxx>
#include <vcl/svapp.hxx>

#include "spelldialog.hxx"
#include "tabvwsh.hxx"
#include "docsh.hxx"
#include "cell.hxx"
#include "patattr.hxx"
#include "waitoff.hxx"
#include "globstr.hrc"


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

// ============================================================================

namespace {

bool lclHasString( ScDocument& rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, const String& rString )
{
    String aCompStr;
    rDoc.GetString( nCol, nRow, nTab, aCompStr );
    return aCompStr == rString;      //! case-insensitive?
}

} // namespace

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

ScConversionEngineBase::ScConversionEngineBase(
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
        ScDocument* pUndoDoc, ScDocument* pRedoDoc ) :
    ScEditEngineDefaulter( pEnginePoolP ),
    mrViewData( rViewData ),
    mrDocShell( *rViewData.GetDocShell() ),
    mrDoc( *rViewData.GetDocShell()->GetDocument() ),
    maSelState( rViewData ),
    mpUndoDoc( pUndoDoc ),
    mpRedoDoc( pRedoDoc ),
    meCurrLang( LANGUAGE_ENGLISH_US ),
    mbIsAnyModified( false ),
    mbInitialState( true ),
    mbWrappedInTable( false ),
    mbFinished( false )
{
    maSelState.GetCellCursor().GetVars( mnStartCol, mnStartRow, mnStartTab );
    // start with cell A1 in cell/range/multi-selection, will seek to first selected
    if( maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET )
    {
        mnStartCol = 0;
        mnStartRow = 0;
    }
    mnCurrCol = mnStartCol;
    mnCurrRow = mnStartRow;
}

ScConversionEngineBase::~ScConversionEngineBase()
{
}

bool ScConversionEngineBase::FindNextConversionCell()
{
    ScMarkData& rMark = mrViewData.GetMarkData();
    ScTabViewShell* pViewShell = mrViewData.GetViewShell();
    ScBaseCell* pCell = NULL;
    const ScPatternAttr* pPattern = NULL;
    const ScPatternAttr* pLastPattern = NULL;
    ::std::auto_ptr< SfxItemSet > pEditDefaults( new SfxItemSet( GetEmptyItemSet() ) );

    if( IsModified() )
    {
        mbIsAnyModified = true;

        String aNewStr = GetText();

        sal_Bool bMultiTab = (rMark.GetSelectCount() > 1);
        String aVisibleStr;
        if( bMultiTab )
            mrDoc.GetString( mnCurrCol, mnCurrRow, mnStartTab, aVisibleStr );

        for( SCTAB nTab = 0, nTabCount = mrDoc.GetTableCount(); nTab < nTabCount; ++nTab )
        {
            //  #69965# always change the cell on the visible tab,
            //  on the other selected tabs only if they contain the same text

            if( (nTab == mnStartTab) ||
                (bMultiTab && rMark.GetTableSelect( nTab ) &&
                 lclHasString( mrDoc, mnCurrCol, mnCurrRow, nTab, aVisibleStr )) )
            {
                ScAddress aPos( mnCurrCol, mnCurrRow, nTab );
                CellType eCellType = mrDoc.GetCellType( aPos );
                pCell = mrDoc.GetCell( aPos );

                if( mpUndoDoc && pCell )
                {
                    ScBaseCell* pUndoCell = pCell->CloneWithoutNote( *mpUndoDoc );
                    mpUndoDoc->PutCell( aPos, pUndoCell );
                }

                if( eCellType == CELLTYPE_EDIT )
                {
                    if( pCell )
                    {
                        ScEditCell* pEditCell = static_cast< ScEditCell* >( pCell );
                        ::std::auto_ptr< EditTextObject > pEditObj( CreateTextObject() );
                        pEditCell->SetData( pEditObj.get(), GetEditTextObjectPool() );
                    }
                }
                else
                {
                    mrDoc.SetString( mnCurrCol, mnCurrRow, nTab, aNewStr );
                    pCell = mrDoc.GetCell( aPos );
                }

                if( mpRedoDoc && pCell )
                {
                    ScBaseCell* pRedoCell = pCell->CloneWithoutNote( *mpRedoDoc );
                    mpRedoDoc->PutCell( aPos, pRedoCell );
                }

                mrDocShell.PostPaintCell( mnCurrCol, mnCurrRow, nTab );
            }
        }
    }
    pCell = NULL;
    SCCOL nNewCol = mnCurrCol;
    SCROW nNewRow = mnCurrRow;

    if( mbInitialState )
    {
        /*  On very first call, decrement row to let GetNextSpellingCell() find
            the first cell of current range. */
        mbInitialState = false;
        --nNewRow;
    }

    bool bSheetSel = maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET;
    bool bLoop = true;
    bool bFound = false;
    while( bLoop && !bFound )
    {
        bLoop = mrDoc.GetNextSpellingCell( nNewCol, nNewRow, mnStartTab, bSheetSel, rMark );
        if( bLoop )
        {
            FillFromCell( mnCurrCol, mnCurrRow, mnStartTab );

            if( mbWrappedInTable && ((nNewCol > mnStartCol) || ((nNewCol == mnStartCol) && (nNewRow >= mnStartRow))) )
            {
                ShowFinishDialog();
                bLoop = false;
                mbFinished = true;
            }
            else if( nNewCol > MAXCOL )
            {
                // no more cells in the sheet - try to restart at top of sheet

                if( bSheetSel || ((mnStartCol == 0) && (mnStartRow == 0)) )
                {
                    // conversion started at cell A1 or in selection, do not query to restart at top
                    ShowFinishDialog();
                    bLoop = false;
                    mbFinished = true;
                }
                else if( ShowTableWrapDialog() )
                {
                    // conversion started anywhere but in cell A1, user wants to restart
                    nNewRow = MAXROW + 2;
                    mbWrappedInTable = true;
                }
                else
                {
                    bLoop = false;
                    mbFinished = true;
                }
            }
            else
            {
                pPattern = mrDoc.GetPattern( nNewCol, nNewRow, mnStartTab );
                if( pPattern && (pPattern != pLastPattern) )
                {
                    pPattern->FillEditItemSet( pEditDefaults.get() );
                    SetDefaults( *pEditDefaults );
                    pLastPattern = pPattern;
                }

                // language changed?
                const SfxPoolItem* pItem = mrDoc.GetAttr( nNewCol, nNewRow, mnStartTab, ATTR_FONT_LANGUAGE );
                if( const SvxLanguageItem* pLangItem = PTR_CAST( SvxLanguageItem, pItem ) )
                {
                    LanguageType eLang = static_cast< LanguageType >( pLangItem->GetValue() );
                    if( eLang == LANGUAGE_SYSTEM )
                        eLang = Application::GetSettings().GetLanguage();   // never use SYSTEM for spelling
                    if( eLang != meCurrLang )
                    {
                        meCurrLang = eLang;
                        SetDefaultLanguage( eLang );
                    }
                }

                FillFromCell( nNewCol, nNewRow, mnStartTab );

                bFound = bLoop && NeedsConversion();
            }
        }
    }

    if( bFound )
    {
        pViewShell->AlignToCursor( nNewCol, nNewRow, SC_FOLLOW_JUMP );
        pViewShell->SetCursor( nNewCol, nNewRow, sal_True );
        mrViewData.GetView()->MakeEditView( this, nNewCol, nNewRow );
        EditView* pEditView = mrViewData.GetSpellingView();
        // maSelState.GetEditSelection() returns (0,0) if not in edit mode -> ok
        pEditView->SetSelection( maSelState.GetEditSelection() );

        ClearModifyFlag();
        mnCurrCol = nNewCol;
        mnCurrRow = nNewRow;
    }

    return bFound;
}

void ScConversionEngineBase::RestoreCursorPos()
{
    const ScAddress& rPos = maSelState.GetCellCursor();
    mrViewData.GetViewShell()->SetCursor( rPos.Col(), rPos.Row() );
}

bool ScConversionEngineBase::ShowTableWrapDialog()
{
    // default: no dialog, always restart at top
    return true;
}

void ScConversionEngineBase::ShowFinishDialog()
{
    // default: no dialog
}

// private --------------------------------------------------------------------

void ScConversionEngineBase::FillFromCell( SCCOL nCol, SCROW nRow, SCTAB nTab )
{
    CellType eCellType;
    mrDoc.GetCellType( nCol, nRow, nTab, eCellType );

    switch( eCellType )
    {
        case CELLTYPE_STRING:
        {
            String aText;
            mrDoc.GetString( nCol, nRow, nTab, aText );
            SetText( aText );
        }
        break;
        case CELLTYPE_EDIT:
        {
            ScBaseCell* pCell = NULL;
            mrDoc.GetCell( nCol, nRow, nTab, pCell );
            if( pCell )
            {
                const EditTextObject* pNewEditObj = NULL;
                static_cast< ScEditCell* >( pCell )->GetData( pNewEditObj );
                if( pNewEditObj )
                    SetText( *pNewEditObj );
            }
        }
        break;
        default:
            SetText( EMPTY_STRING );
    }
}

// ============================================================================

ScSpellingEngine::ScSpellingEngine(
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
        ScDocument* pUndoDoc, ScDocument* pRedoDoc,
        XSpellCheckerRef xSpeller ) :
    ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc )
{
    SetSpeller( xSpeller );
}

void ScSpellingEngine::ConvertAll( EditView& rEditView )
{
    EESpellState eState = EE_SPELL_OK;
    if( FindNextConversionCell() )
        eState = rEditView.StartSpeller( static_cast< sal_Bool >( sal_True ) );

    DBG_ASSERT( eState != EE_SPELL_NOSPELLER, "ScSpellingEngine::Convert - no spell checker" );
    if( eState == EE_SPELL_NOLANGUAGE )
    {
        Window* pParent = GetDialogParent();
        ScWaitCursorOff aWaitOff( pParent );
        InfoBox( pParent, ScGlobal::GetRscString( STR_NOLANGERR ) ).Execute();
    }
}

sal_Bool ScSpellingEngine::SpellNextDocument()
{
    return FindNextConversionCell();
}

bool ScSpellingEngine::NeedsConversion()
{
    return HasSpellErrors() != EE_SPELL_OK;
}

bool ScSpellingEngine::ShowTableWrapDialog()
{
    Window* pParent = GetDialogParent();
    ScWaitCursorOff aWaitOff( pParent );
    MessBox aMsgBox( pParent, WinBits( WB_YES_NO | WB_DEF_YES ),
        ScGlobal::GetRscString( STR_MSSG_DOSUBTOTALS_0 ),
        ScGlobal::GetRscString( STR_SPELLING_BEGIN_TAB) );
    return aMsgBox.Execute() == RET_YES;
}

void ScSpellingEngine::ShowFinishDialog()
{
    Window* pParent = GetDialogParent();
    ScWaitCursorOff aWaitOff( pParent );
    InfoBox( pParent, ScGlobal::GetRscString( STR_SPELLING_STOP_OK ) ).Execute();
}

Window* ScSpellingEngine::GetDialogParent()
{
    sal_uInt16 nWinId = ScSpellDialogChildWindow::GetChildWindowId();
    SfxViewFrame* pViewFrm = mrViewData.GetViewShell()->GetViewFrame();
    if( pViewFrm->HasChildWindow( nWinId ) )
        if( SfxChildWindow* pChild = pViewFrm->GetChildWindow( nWinId ) )
            if( Window* pWin = pChild->GetWindow() )
                if( pWin->IsVisible() )
                    return pWin;

    // fall back to standard dialog parent
    return mrDocShell.GetActiveDialogParent();
}

// ============================================================================

ScConversionParam::ScConversionParam( ScConversionType eConvType ) :
    meConvType( eConvType ),
    meSourceLang( LANGUAGE_NONE ),
    meTargetLang( LANGUAGE_NONE ),
    mnOptions( 0 ),
    mbUseTargetFont( false ),
    mbIsInteractive( false )
{
}

ScConversionParam::ScConversionParam( ScConversionType eConvType,
        LanguageType eLang, sal_Int32 nOptions, bool bIsInteractive ) :
    meConvType( eConvType ),
    meSourceLang( eLang ),
    meTargetLang( eLang ),
    mnOptions( nOptions ),
    mbUseTargetFont( false ),
    mbIsInteractive( bIsInteractive )
{
    if (LANGUAGE_KOREAN == eLang)
        mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER;
}

ScConversionParam::ScConversionParam( ScConversionType eConvType,
        LanguageType eSourceLang, LanguageType eTargetLang, const Font& rTargetFont,
        sal_Int32 nOptions, bool bIsInteractive ) :
    meConvType( eConvType ),
    meSourceLang( eSourceLang ),
    meTargetLang( eTargetLang ),
    maTargetFont( rTargetFont ),
    mnOptions( nOptions ),
    mbUseTargetFont( true ),
    mbIsInteractive( bIsInteractive )
{
    if (LANGUAGE_KOREAN == meSourceLang && LANGUAGE_KOREAN == meTargetLang)
        mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER;
}

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

ScTextConversionEngine::ScTextConversionEngine(
        SfxItemPool* pEnginePoolP, ScViewData& rViewData,
        const ScConversionParam& rConvParam,
        ScDocument* pUndoDoc, ScDocument* pRedoDoc ) :
    ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc ),
    maConvParam( rConvParam )
{
}

void ScTextConversionEngine::ConvertAll( EditView& rEditView )
{
    if( FindNextConversionCell() )
    {
        rEditView.StartTextConversion(
            maConvParam.GetSourceLang(), maConvParam.GetTargetLang(), maConvParam.GetTargetFont(),
            maConvParam.GetOptions(), maConvParam.IsInteractive(), sal_True );
        // #i34769# restore initial cursor position
        RestoreCursorPos();
    }
}

sal_Bool ScTextConversionEngine::ConvertNextDocument()
{
    return FindNextConversionCell();
}

bool ScTextConversionEngine::NeedsConversion()
{
    return HasConvertibleTextPortion( maConvParam.GetSourceLang() );
}

// ============================================================================