/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <spelldialog.hxx>

#include <sfx2/bindings.hxx>
#include <svx/svxids.hrc>
#include <editeng/editstat.hxx>
#include <editeng/editview.hxx>
#include <editeng/unolingu.hxx>
#include <selectionstate.hxx>
#include <osl/diagnose.h>

#include <spelleng.hxx>
#include <tabvwsh.hxx>
#include <docsh.hxx>
#include <scmod.hxx>
#include <editable.hxx>
#include <undoblk.hxx>
#include <gridwin.hxx>
#include <refupdatecontext.hxx>
#include <vcl/svapp.hxx>

SFX_IMPL_CHILDWINDOW_WITHID( ScSpellDialogChildWindow, SID_SPELL_DIALOG )

ScSpellDialogChildWindow::ScSpellDialogChildWindow( vcl::Window* pParentP, sal_uInt16 nId,
        SfxBindings* pBindings, SAL_UNUSED_PARAMETER SfxChildWinInfo* /*pInfo*/ ) :
    svx::SpellDialogChildWindow( pParentP, nId, pBindings ),
    mpViewShell( nullptr ),
    mpViewData( nullptr ),
    mpDocShell( nullptr ),
    mpDoc( nullptr ),
    mbNeedNextObj( false ),
    mbOldIdleEnabled(true)
{
    Init();
}

ScSpellDialogChildWindow::~ScSpellDialogChildWindow()
{
    Reset();
}

SfxChildWinInfo ScSpellDialogChildWindow::GetInfo() const
{
    // tdf#117020: visible gets written to UserData
    // and a value of true means it automatically opens on startup
    SfxChildWinInfo aInfo = svx::SpellDialogChildWindow::GetInfo();
    aInfo.bVisible = false;
    return aInfo;
}

void ScSpellDialogChildWindow::InvalidateSpellDialog()
{
    svx::SpellDialogChildWindow::InvalidateSpellDialog();
}

// protected ------------------------------------------------------------------

svx::SpellPortions ScSpellDialogChildWindow::GetNextWrongSentence( bool /*bRecheck*/ )
{
    svx::SpellPortions aPortions;
    if( mxEngine && mpViewData )
    {
        if( EditView* pEditView = mpViewData->GetSpellingView() )
        {
            // edit engine handles cell iteration internally
            do
            {
                if( mbNeedNextObj )
                    mxEngine->SpellNextDocument();
                mbNeedNextObj = !mxEngine->IsFinished() && !mxEngine->SpellSentence( *pEditView, aPortions );
            }
            while( mbNeedNextObj );
        }
    }
    return aPortions;
}

void ScSpellDialogChildWindow::ApplyChangedSentence( const svx::SpellPortions& rChanged, bool bRecheck )
{
    if( mxEngine && mpViewData )
        if( EditView* pEditView = mpViewData->GetSpellingView() )
        {
            mxEngine->ApplyChangedSentence( *pEditView, rChanged, bRecheck );

            // Reset the spell checking results to clear the markers.
            mpViewData->GetActiveWin()->ResetAutoSpell();
        }
}

void ScSpellDialogChildWindow::GetFocus(bool bForceResume)
{
    SolarMutexGuard aGuard;

    if (bForceResume || IsSelectionChanged())
    {
        Reset();
        InvalidateSpellDialog();
        Init();
    }
}

void ScSpellDialogChildWindow::LoseFocus()
{
}

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

void ScSpellDialogChildWindow::Reset()
{
    if( mpViewShell && (mpViewShell == dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() ))  )
    {
        if( mxEngine && mxEngine->IsAnyModified() )
        {
            const ScAddress& rCursor = mxOldSel->GetCellCursor();
            SCTAB nTab = rCursor.Tab();
            SCCOL nOldCol = rCursor.Col();
            SCROW nOldRow = rCursor.Row();
            SCCOL nNewCol = mpViewData->GetCurX();
            SCROW nNewRow = mpViewData->GetCurY();
            mpDocShell->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoConversion>(
                *mpDocShell, mpViewData->GetMarkData(),
                nOldCol, nOldRow, nTab, std::move(mxUndoDoc),
                nNewCol, nNewRow, nTab, std::move(mxRedoDoc),
                ScConversionParam( SC_CONVERSION_SPELLCHECK ) ) );

            sc::SetFormulaDirtyContext aCxt;
            mpDoc->SetAllFormulasDirty(aCxt);

            mpDocShell->SetDocumentModified();
        }

        mpViewData->SetSpellingView( nullptr );
        mpViewShell->KillEditView( true );
        mpDocShell->PostPaintGridAll();
        mpViewShell->UpdateInputHandler();
        mpDoc->EnableIdle(mbOldIdleEnabled);
    }
    mxEngine.reset();
    mxUndoDoc.reset();
    mxRedoDoc.reset();
    mxOldSel.reset();
    mxOldRangeList.clear();
    mpViewShell = nullptr;
    mpViewData = nullptr;
    mpDocShell = nullptr;
    mpDoc = nullptr;
    mbNeedNextObj = false;
    mbOldIdleEnabled = true;
}

void ScSpellDialogChildWindow::Init()
{
    if( mpViewShell )
        return;
    if( (mpViewShell = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() )) == nullptr  )
        return;

    mpViewData = &mpViewShell->GetViewData();

    // exit edit mode - TODO support spelling in edit mode
    if( mpViewData->HasEditView( mpViewData->GetActivePart() ) )
        ScModule::get()->InputEnterHandler();

    mxOldSel.reset( new ScSelectionState( *mpViewData ) );

    mpDocShell = mpViewData->GetDocShell();
    mpDoc = &mpDocShell->GetDocument();

    const ScAddress& rCursor = mxOldSel->GetCellCursor();
    SCCOL nCol = rCursor.Col();
    SCROW nRow = rCursor.Row();
    SCTAB nTab = rCursor.Tab();

    ScMarkData& rMarkData = mpViewData->GetMarkData();

    mxOldRangeList = new ScRangeList;
    rMarkData.FillRangeListWithMarks(mxOldRangeList.get(), true);

    rMarkData.MarkToMulti();

    switch( mxOldSel->GetSelectionType() )
    {
        case SC_SELECTTYPE_NONE:
        case SC_SELECTTYPE_SHEET:
        {
            // test if there is something editable
            ScEditableTester aTester = ScEditableTester::CreateAndTestSelection(*mpDoc, rMarkData);
            if( !aTester.IsEditable() )
            {
                // #i85751# Don't show an ErrorMessage here, because the vcl
                // parent of the InfoBox is not fully initialized yet.
                // This leads to problems in the modality behaviour of the
                // ScSpellDialogChildWindow.

                //mpViewShell->ErrorMessage( aTester.GetMessageId() );
                return;
            }
        }
        break;

        // edit mode exited, see TODO above
//        case SC_SELECTTYPE_EDITCELL:
//        break;

        default:
            OSL_FAIL( "ScSpellDialogChildWindow::Init - unknown selection type" );
    }

    mbOldIdleEnabled = mpDoc->IsIdleEnabled();
    mpDoc->EnableIdle(false);   // stop online spelling

    // *** create Undo/Redo documents *** -------------------------------------

    mxUndoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) );
    mxUndoDoc->InitUndo( *mpDoc, nTab, nTab );
    mxRedoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) );
    mxRedoDoc->InitUndo( *mpDoc, nTab, nTab );

    if ( rMarkData.GetSelectCount() > 1 )
    {
        for (const auto& rTab : rMarkData)
        {
            if( rTab != nTab )
            {
                mxUndoDoc->AddUndoTab( rTab, rTab );
                mxRedoDoc->AddUndoTab( rTab, rTab );
            }
        }
    }

    // *** create and init the edit engine *** --------------------------------

    mxEngine.reset( new ScSpellingEngine(
        mpDoc->GetEditEnginePool(), *mpViewData, mxUndoDoc.get(), mxRedoDoc.get(), LinguMgr::GetSpellChecker() ) );
    mxEngine->SetRefDevice( mpViewData->GetActiveWin()->GetOutDev() );

    mpViewShell->MakeEditView(*mxEngine, nCol, nRow);
    EditView* pEditView = mpViewData->GetEditView( mpViewData->GetActivePart() );
    mpViewData->SetSpellingView( pEditView );
    tools::Rectangle aRect( Point( 0, 0 ), Point( 0, 0 ) );
    pEditView->SetOutputArea( aRect );
    mxEngine->SetControlWord( EEControlBits::USECHARATTRIBS );
    mxEngine->EnableUndo( false );
    mxEngine->SetPaperSize( aRect.GetSize() );
    mxEngine->SetTextCurrentDefaults( OUString() );
    mxEngine->ClearModifyFlag();

    mbNeedNextObj = true;
}

bool ScSpellDialogChildWindow::IsSelectionChanged()
{
    if (!mxOldRangeList || !mpViewShell
        || (mpViewShell != dynamic_cast<ScTabViewShell*>(SfxViewShell::Current())))
        return true;

    if( EditView* pEditView = mpViewData->GetSpellingView() )
        if (&pEditView->getEditEngine() != mxEngine.get())
            return true;

    ScRangeList aCurrentRangeList;
    mpViewData->GetMarkData().FillRangeListWithMarks(&aCurrentRangeList, true);

    return (*mxOldRangeList != aCurrentRangeList);
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
