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

#include <hintids.hxx>
#include <unotools/charclass.hxx>
#include <editeng/keepitem.hxx>
#include <svx/svdobj.hxx>
#include <osl/diagnose.h>

#include <fmtcntnt.hxx>
#include <frmfmt.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentDrawModelAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IShellCursorSupplier.hxx>
#include <swundo.hxx>
#include <pam.hxx>
#include <ndtxt.hxx>
#include <UndoCore.hxx>
#include <UndoDelete.hxx>
#include <UndoAttribute.hxx>
#include <rolbck.hxx>
#include <ndgrf.hxx>
#include <ndole.hxx>
#include <grfatr.hxx>
#include <cntfrm.hxx>
#include <flyfrm.hxx>
#include <swcrsr.hxx>
#include <swtable.hxx>
#include <redline.hxx>
#include <docary.hxx>
#include <acorrect.hxx>

#include <strings.hrc>
#include <utility>

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

// INSERT

std::optional<OUString> SwUndoInsert::GetTextFromDoc() const
{
    std::optional<OUString> aResult;

    SwNodeIndex aNd( m_rDoc.GetNodes(), m_nNode);
    SwContentNode* pCNd = aNd.GetNode().GetContentNode();

    if( pCNd->IsTextNode() )
    {
        OUString sText = pCNd->GetTextNode()->GetText();

        sal_Int32 nStart = m_nContent-m_nLen;
        sal_Int32 nLength = m_nLen;

        if (nStart < 0)
        {
            nLength += nStart;
            nStart = 0;
        }

        aResult = sText.copy(nStart, nLength);
    }

    return aResult;
}

void SwUndoInsert::Init()
{
    // consider Redline
    if( m_rDoc.getIDocumentRedlineAccess().IsRedlineOn() )
    {
        m_pRedlData.reset( new SwRedlineData( RedlineType::Insert,
                                       m_rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() ) );
        SetRedlineFlags( m_rDoc.getIDocumentRedlineAccess().GetRedlineFlags() );
    }

    maUndoText = GetTextFromDoc();

    m_bCacheComment = false;
}

SwUndoInsert::SwUndoInsert( const SwNode& rNd, sal_Int32 nCnt,
            sal_Int32 nL,
            const SwInsertFlags nInsertFlags,
            bool bWDelim )
    : SwUndo(SwUndoId::TYPING, rNd.GetDoc()),
        m_nNode( rNd.GetIndex() ), m_nContent(nCnt), m_nLen(nL),
        m_bIsWordDelim( bWDelim ), m_bIsAppend( false )
    , m_bWithRsid(false)
    , m_nInsertFlags(nInsertFlags)
    , m_rDoc(const_cast<SwDoc&>(rNd.GetDoc()))
{
    Init();
}

SwUndoInsert::SwUndoInsert( const SwNode& rNd )
    : SwUndo(SwUndoId::SPLITNODE, rNd.GetDoc()),
        m_nNode( rNd.GetIndex() ), m_nContent(0), m_nLen(1),
        m_bIsWordDelim( false ), m_bIsAppend( true )
    , m_bWithRsid(false)
    , m_nInsertFlags(SwInsertFlags::EMPTYEXPAND)
    , m_rDoc(const_cast<SwDoc&>(rNd.GetDoc()))
{
    Init();
}

// Check if the next Insert can be combined with the current one. If so
// change the length and InsPos. As a result, SwDoc::Insert will not add a
// new object into the Undo list.

bool SwUndoInsert::CanGrouping( sal_Unicode cIns )
{
    if( !m_bIsAppend && m_bIsWordDelim ==
        !GetAppCharClass().isLetterNumeric( OUString( cIns )) )
    {
        m_nLen++;
        m_nContent++;

        if (maUndoText)
            (*maUndoText) += OUStringChar(cIns);

        return true;
    }
    return false;
}

bool SwUndoInsert::CanGrouping( const SwPosition& rPos )
{
    bool bRet = false;
    if( m_nNode == rPos.GetNodeIndex() &&
        m_nContent == rPos.GetContentIndex() )
    {
        // consider Redline
        SwDoc& rDoc = rPos.GetNode().GetDoc();
        if( ( ~RedlineFlags::ShowMask & rDoc.getIDocumentRedlineAccess().GetRedlineFlags() ) ==
            ( ~RedlineFlags::ShowMask & GetRedlineFlags() ) )
        {
            bRet = true;

            // then there is or was still an active Redline:
            // Check if there is another Redline at the InsPosition. If the
            // same exists only once, it can be combined.
            const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
            if( !rTable.empty() )
            {
                SwRedlineData aRData( RedlineType::Insert, rDoc.getIDocumentRedlineAccess().GetRedlineAuthor() );
                const SwContentNode* pIReg = rPos.GetContentNode();
                for(SwRangeRedline* pRedl : rTable)
                {
                    const SwPosition& rIdx = *pRedl->End();
                    if( pIReg == rIdx.GetContentNode() &&
                        m_nContent == rIdx.GetContentIndex() )
                    {
                        if( !pRedl->HasMark() || !m_pRedlData ||
                            *pRedl != *m_pRedlData || *pRedl != aRData )
                        {
                            bRet = false;
                            break;
                        }
                    }
                }
            }
        }
    }
    return bRet;
}

SwUndoInsert::~SwUndoInsert()
{
    if (m_oUndoNodeIndex) // delete the section from UndoNodes array
    {
        // Insert saves the content in IconSection
        SwNodes& rUNds = m_oUndoNodeIndex->GetNodes();
        rUNds.Delete(*m_oUndoNodeIndex,
            rUNds.GetEndOfExtras().GetIndex() - m_oUndoNodeIndex->GetIndex());
        m_oUndoNodeIndex.reset();
    }
    else     // the inserted text
    {
        maText.reset();
    }
    m_pRedlData.reset();
}

void SwUndoInsert::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc *const pTmpDoc = & rContext.GetDoc();
    SwCursor *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());

    if( m_bIsAppend )
    {
        pPam->GetPoint()->Assign(m_nNode);

        if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
        {
            pPam->SetMark();
            pPam->Move( fnMoveBackward );
            pPam->Exchange();
            pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( *pPam, true, RedlineType::Any );
        }
        pPam->DeleteMark();
        pTmpDoc->getIDocumentContentOperations().DelFullPara( *pPam );
        pPam->GetPoint()->SetContent( 0 );
    }
    else
    {
        SwNodeOffset nNd = m_nNode;
        sal_Int32 nCnt = m_nContent;
        if( m_nLen )
        {
            SwNodeIndex aNd( pTmpDoc->GetNodes(), m_nNode);
            SwContentNode* pCNd = aNd.GetNode().GetContentNode();
            SwPaM aPaM( *pCNd, m_nContent );

            aPaM.SetMark();

            SwTextNode * const pTextNode( pCNd->GetTextNode() );
            if ( pTextNode )
            {
                aPaM.GetPoint()->AdjustContent( - m_nLen );
                if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
                    pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any );
                if (m_bWithRsid)
                {
                    // RSID was added: remove any CHARFMT/AUTOFMT that may be
                    // set on the deleted text; EraseText will leave empty
                    // ones behind otherwise
                    pTextNode->DeleteAttributes(RES_TXTATR_AUTOFMT,
                        aPaM.GetPoint()->GetContentIndex(),
                        aPaM.GetMark()->GetContentIndex());
                    pTextNode->DeleteAttributes(RES_TXTATR_CHARFMT,
                        aPaM.GetPoint()->GetContentIndex(),
                        aPaM.GetMark()->GetContentIndex());
                }
                RemoveIdxFromRange( aPaM, false );
                maText = pTextNode->GetText().copy(m_nContent-m_nLen, m_nLen);
                pTextNode->EraseText( *aPaM.GetPoint(), m_nLen );
            }
            else                // otherwise Graphics/OLE/Text/...
            {
                aPaM.Move(fnMoveBackward);
                if( IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
                    pTmpDoc->getIDocumentRedlineAccess().DeleteRedline( aPaM, true, RedlineType::Any );
                RemoveIdxFromRange( aPaM, false );
            }

            nNd = aPaM.GetPoint()->GetNodeIndex();
            nCnt = aPaM.GetPoint()->GetContentIndex();

            if (!maText)
            {
                m_oUndoNodeIndex.emplace(m_rDoc.GetNodes().GetEndOfContent());
                MoveToUndoNds(aPaM, &*m_oUndoNodeIndex);
            }
            m_nNode = aPaM.GetPoint()->GetNodeIndex();
            m_nContent = aPaM.GetPoint()->GetContentIndex();
        }

        // set cursor to Undo range
        pPam->DeleteMark();

        pPam->GetPoint()->Assign( nNd, nCnt );
    }

    maUndoText.reset();
}

void SwUndoInsert::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc *const pTmpDoc = & rContext.GetDoc();
    SwCursor *const pPam(& rContext.GetCursorSupplier().CreateNewShellCursor());
    pPam->DeleteMark();

    if( m_bIsAppend )
    {
        pPam->GetPoint()->Assign( m_nNode - 1 );
        pTmpDoc->getIDocumentContentOperations().AppendTextNode( *pPam->GetPoint() );

        pPam->SetMark();
        pPam->Move( fnMoveBackward );
        pPam->Exchange();

        if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
        {
            RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags();
            pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore);
            pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData, *pPam ), true);
            pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
        }
        else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
                !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
            pTmpDoc->getIDocumentRedlineAccess().SplitRedline( *pPam );

        pPam->DeleteMark();
    }
    else
    {
        pPam->GetPoint()->Assign( m_nNode );
        SwContentNode *const pCNd =
            pPam->GetPoint()->GetNode().GetContentNode();
        pPam->GetPoint()->SetContent( m_nContent );

        if( m_nLen )
        {
            ::std::optional<SwNodeIndex> oMvBkwrd = MovePtBackward(*pPam);

            if (maText)
            {
                SwTextNode *const pTextNode = pCNd->GetTextNode();
                OSL_ENSURE( pTextNode, "where is my textnode ?" );
                OUString const ins(
                    pTextNode->InsertText( *maText, *pPam->GetMark(),
                    m_nInsertFlags) );
                assert(ins.getLength() == maText->getLength()); // must succeed
                maText.reset();
                if (m_bWithRsid) // re-insert RSID
                {
                    SwPaM pam(*pPam->GetMark(), nullptr); // mark -> point
                    pTmpDoc->UpdateRsid(pam, ins.getLength());
                }
            }
            else
            {
                // re-insert content again (first detach m_oUndoNodeIndex!)
                SwNodeOffset const nMvNd = m_oUndoNodeIndex->GetIndex();
                m_oUndoNodeIndex.reset();
                MoveFromUndoNds(*pTmpDoc, nMvNd, *pPam->GetMark());
            }
            m_nNode = pPam->GetMark()->GetNodeIndex();
            m_nContent = pPam->GetMark()->GetContentIndex();

            MovePtForward(*pPam, ::std::move(oMvBkwrd));
            pPam->Exchange();
            if( m_pRedlData && IDocumentRedlineAccess::IsRedlineOn( GetRedlineFlags() ))
            {
                RedlineFlags eOld = pTmpDoc->getIDocumentRedlineAccess().GetRedlineFlags();
                pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern(eOld & ~RedlineFlags::Ignore);
                pTmpDoc->getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( *m_pRedlData,
                                            *pPam ), true);
                pTmpDoc->getIDocumentRedlineAccess().SetRedlineFlags_intern( eOld );
            }
            else if( !( RedlineFlags::Ignore & GetRedlineFlags() ) &&
                    !pTmpDoc->getIDocumentRedlineAccess().GetRedlineTable().empty() )
                pTmpDoc->getIDocumentRedlineAccess().SplitRedline(*pPam);
        }
    }

    maUndoText = GetTextFromDoc();
}

void SwUndoInsert::RepeatImpl(::sw::RepeatContext & rContext)
{
    if( !m_nLen )
        return;

    SwDoc & rDoc = rContext.GetDoc();
    SwNodeIndex aNd( rDoc.GetNodes(), m_nNode );
    SwContentNode* pCNd = aNd.GetNode().GetContentNode();

    if( !m_bIsAppend && 1 == m_nLen )       // >1 than always Text, otherwise Graphics/OLE/Text/...
    {
        SwPaM aPaM( *pCNd, m_nContent );
        aPaM.SetMark();
        aPaM.Move(fnMoveBackward);
        pCNd = aPaM.GetPointContentNode();
    }

// What happens with the possible selected range ???

    switch( pCNd->GetNodeType() )
    {
    case SwNodeType::Text:
        if( m_bIsAppend )
        {
            rDoc.getIDocumentContentOperations().AppendTextNode( *rContext.GetRepeatPaM().GetPoint() );
        }
        else
        {
            OUString const aText( pCNd->GetTextNode()->GetText() );
            ::sw::GroupUndoGuard const undoGuard(rDoc.GetIDocumentUndoRedo());
            rDoc.getIDocumentContentOperations().InsertString( rContext.GetRepeatPaM(),
                aText.copy(m_nContent - m_nLen, m_nLen) );
        }
        break;
    case SwNodeType::Grf:
        {
            SwGrfNode* pGrfNd = static_cast<SwGrfNode*>(pCNd);
            OUString sFile;
            OUString sFilter;
            if( pGrfNd->IsGrfLink() )
                pGrfNd->GetFileFilterNms( &sFile, &sFilter );

            rDoc.getIDocumentContentOperations().InsertGraphic(
                                rContext.GetRepeatPaM(), sFile, sFilter,
                                &pGrfNd->GetGrf(),
                                nullptr/* Graphics collection*/, nullptr, nullptr );
        }
        break;

    case SwNodeType::Ole:
        {
            // StarView does not yet provide an option to copy a StarOBJ
            SwOLEObj& rSwOLE = static_cast<SwOLENode*>(pCNd)->GetOLEObj();

            // temporary storage until object is inserted
            // TODO/MBA: seems that here a physical copy is done - not as in drawing layer! Testing!
            // TODO/LATER: Copying through the container would copy the replacement image as well
            comphelper::EmbeddedObjectContainer aCnt;
            OUString aName = aCnt.CreateUniqueObjectName();
            if (aCnt.StoreEmbeddedObject(rSwOLE.GetOleRef(), aName, true, OUString(), OUString()))
            {
                uno::Reference < embed::XEmbeddedObject > aNew = aCnt.GetEmbeddedObject( aName );
                rDoc.getIDocumentContentOperations().InsertEmbObject(
                    rContext.GetRepeatPaM(),
                    svt::EmbeddedObjectRef( aNew,
                        static_cast<SwOLENode*>(pCNd)->GetAspect() ),
                    nullptr );
            }

            break;
        }

    default: break;
    }
}

SwRewriter SwUndoInsert::GetRewriter() const
{
    SwRewriter aResult;
    std::optional<OUString> aStr;
    bool bDone = false;

    if (maText)
        aStr = maText;
    else if (maUndoText)
        aStr = maUndoText;

    if (aStr)
    {
        OUString aString = ShortenString(DenoteSpecialCharacters(*aStr),
                                       nUndoStringLength,
                                       SwResId(STR_LDOTS));

        aResult.AddRule(UndoArg1, aString);

        bDone = true;
    }

    if ( ! bDone )
    {
        aResult.AddRule(UndoArg1, u"??"_ustr);
    }

    return aResult;
}

bool SwUndoInsert::IsIndependent(const SwUndoInsert& rOther) const
{
    return m_nNode != rOther.m_nNode;
}

class SwUndoReplace::Impl
    : private SwUndoSaveContent
{
    OUString m_sOld;
    OUString m_sIns;
    SwNodeOffset m_nSttNd, m_nEndNd, m_nOffset;
    sal_Int32 m_nSttCnt, m_nEndCnt, m_nSetPos, m_nSelEnd;
    bool m_bSplitNext : 1;
    bool m_bRegExp : 1;
    // metadata references for paragraph and following para (if m_bSplitNext)
    std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoStart;
    std::shared_ptr< ::sfx2::MetadatableUndo > m_pMetadataUndoEnd;

public:
    Impl(SwPaM const& rPam, OUString aIns, bool const bRegExp);

    void UndoImpl( ::sw::UndoRedoContext & );
    void RedoImpl( ::sw::UndoRedoContext & );

    void SetEnd(SwPaM const& rPam);

    OUString const& GetOld() const { return m_sOld; }
    OUString const& GetIns() const { return m_sIns; }
};

SwUndoReplace::SwUndoReplace(SwPaM const& rPam,
        OUString const& rIns, bool const bRegExp)
    : SwUndo( SwUndoId::REPLACE, rPam.GetDoc() )
    , m_pImpl(std::make_unique<Impl>(rPam, rIns, bRegExp))
{
}

SwUndoReplace::~SwUndoReplace()
{
}

void SwUndoReplace::UndoImpl(::sw::UndoRedoContext & rContext)
{
    m_pImpl->UndoImpl(rContext);
}

void SwUndoReplace::RedoImpl(::sw::UndoRedoContext & rContext)
{
    m_pImpl->RedoImpl(rContext);
}

SwRewriter
MakeUndoReplaceRewriter(sal_uLong const occurrences,
        OUString const& sOld, OUString const& sNew)
{
    SwRewriter aResult;

    if (1 < occurrences)
    {
        aResult.AddRule(UndoArg1, OUString::number(occurrences));
        aResult.AddRule(UndoArg2, SwResId(STR_OCCURRENCES_OF));

        OUString aTmpStr =
            SwResId(STR_START_QUOTE)
            + ShortenString(sOld, nUndoStringLength, SwResId(STR_LDOTS))
            + SwResId(STR_END_QUOTE);
        aResult.AddRule(UndoArg3, aTmpStr);
    }
    else if (1 == occurrences)
    {
        {
            // #i33488 #
            OUString aTmpStr =
                SwResId(STR_START_QUOTE)
                + ShortenString(sOld, nUndoStringLength, SwResId(STR_LDOTS))
                + SwResId(STR_END_QUOTE);
            aResult.AddRule(UndoArg1, aTmpStr);
        }

        aResult.AddRule(UndoArg2, SwResId(STR_YIELDS));

        {
            // #i33488 #
            OUString aTmpStr =
                SwResId(STR_START_QUOTE)
                + ShortenString(sNew, nUndoStringLength, SwResId(STR_LDOTS))
                + SwResId(STR_END_QUOTE);
            aResult.AddRule(UndoArg3, aTmpStr);
        }
    }

    return aResult;
}

SwRewriter SwUndoReplace::GetRewriter() const
{
    return MakeUndoReplaceRewriter(1, m_pImpl->GetOld(), m_pImpl->GetIns());
}

void SwUndoReplace::SetEnd(SwPaM const& rPam)
{
    m_pImpl->SetEnd(rPam);
}

SwUndoReplace::Impl::Impl(
        SwPaM const& rPam, OUString aIns, bool const bRegExp)
    : m_sIns(std::move( aIns ))
    , m_nOffset( 0 )
    , m_bRegExp(bRegExp)
{

    auto [pStart, pEnd] = rPam.StartEnd(); // SwPosition*

    m_nSttNd = m_nEndNd = pStart->GetNodeIndex();
    m_nSttCnt = pStart->GetContentIndex();
    m_nSelEnd = m_nEndCnt = pEnd->GetContentIndex();

    m_bSplitNext = m_nSttNd != pEnd->GetNodeIndex();

    SwTextNode* pNd = pStart->GetNode().GetTextNode();
    assert(pNd && "Dude, where's my TextNode?");

    m_pHistory.reset( new SwHistory );
    DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace);

    m_nSetPos = m_pHistory->Count();

    SwNodeOffset nNewPos = pStart->GetNodeIndex();
    m_nOffset = m_nSttNd - nNewPos;

    if ( pNd->GetpSwpHints() )
    {
        m_pHistory->CopyAttr( pNd->GetpSwpHints(), nNewPos, 0,
                            pNd->GetText().getLength(), true );
    }

    if ( m_bSplitNext )
    {
        if( pNd->HasSwAttrSet() )
            m_pHistory->CopyFormatAttr( *pNd->GetpSwAttrSet(), nNewPos );
        m_pHistory->AddColl(pNd->GetTextColl(), nNewPos, SwNodeType::Text);

        SwTextNode* pNext = pEnd->GetNode().GetTextNode();
        SwNodeOffset nTmp = pNext->GetIndex();
        m_pHistory->CopyAttr( pNext->GetpSwpHints(), nTmp, 0,
                            pNext->GetText().getLength(), true );
        if( pNext->HasSwAttrSet() )
            m_pHistory->CopyFormatAttr( *pNext->GetpSwAttrSet(), nTmp );
        m_pHistory->AddColl(pNext->GetTextColl(),nTmp, SwNodeType::Text);
        // METADATA: store
        m_pMetadataUndoStart = pNd  ->CreateUndo();
        m_pMetadataUndoEnd   = pNext->CreateUndo();
    }

    if( !m_pHistory->Count() )
    {
        m_pHistory.reset();
    }

    const sal_Int32 nECnt = m_bSplitNext ? pNd->GetText().getLength()
        : pEnd->GetContentIndex();
    m_sOld = pNd->GetText().copy( m_nSttCnt, nECnt - m_nSttCnt );
}

void SwUndoReplace::Impl::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc& rDoc = rContext.GetDoc();
    SwCursor & rPam(rContext.GetCursorSupplier().CreateNewShellCursor());
    rPam.DeleteMark();

    SwTextNode* pNd = rDoc.GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode();
    OSL_ENSURE( pNd, "Dude, where's my TextNode?" );

    SwAutoCorrExceptWord* pACEWord = rDoc.GetAutoCorrExceptWord();
    if( pACEWord )
    {
        if ((1 == m_sIns.getLength()) && (1 == m_sOld.getLength()))
        {
            SwPosition aPos( *pNd, m_nSttCnt );
            pACEWord->CheckChar( aPos, m_sOld[ 0 ] );
        }
        rDoc.SetAutoCorrExceptWord( nullptr );
    }

    // don't look at m_sIns for deletion, maybe it was not completely inserted
    {
        rPam.GetPoint()->Assign(*pNd, m_nSttCnt );
        rPam.SetMark();
        rPam.GetPoint()->Assign( m_nSttNd - m_nOffset, m_nSttNd == m_nEndNd ? m_nEndCnt : pNd->Len());

        // replace only in start node, without regex
        bool const ret = rDoc.getIDocumentContentOperations().ReplaceRange(rPam, m_sOld, false);
        assert(ret); (void)ret;
        if (m_nSttNd != m_nEndNd)
        {   // in case of regex inserting paragraph breaks, join nodes...
            assert(rPam.GetMark()->GetContentIndex() == rPam.GetMark()->GetNode().GetTextNode()->Len());
            rPam.GetPoint()->Assign( m_nEndNd - m_nOffset, m_nEndCnt );
            rDoc.getIDocumentContentOperations().DeleteAndJoin(rPam);
        }
        if (*rPam.GetMark() == *rPam.GetPoint())
            rPam.DeleteMark();
        else
            rPam.Normalize(false);
        pNd = rDoc.GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode();
        OSL_ENSURE( pNd, "Dude, where's my TextNode?" );
    }

    if( m_bSplitNext )
    {
        assert(pNd && m_nSttCnt + m_sOld.getLength() <= pNd->Len());
        SwPosition aPos(*pNd, m_nSttCnt + m_sOld.getLength());
        rDoc.getIDocumentContentOperations().SplitNode( aPos, false );
        pNd->RestoreMetadata(m_pMetadataUndoEnd);
        pNd = rDoc.GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode();
        // METADATA: restore
        pNd->RestoreMetadata(m_pMetadataUndoStart);
    }

    if( m_pHistory )
    {
        if( pNd->GetpSwpHints() )
            pNd->ClearSwpHintsArr( true );

        m_pHistory->TmpRollback( rDoc, m_nSetPos, false );
        if ( m_nSetPos ) // there were footnotes/FlyFrames
        {
            // are there others than these?
            if( m_nSetPos < m_pHistory->Count() )
            {
                // than save those attributes as well
                SwHistory aHstr;
                aHstr.Move( 0, m_pHistory.get(), m_nSetPos );
                m_pHistory->Rollback( rDoc );
                m_pHistory->Move( 0, &aHstr );
            }
            else
            {
                m_pHistory->Rollback( rDoc );
                m_pHistory.reset();
            }
        }
    }
}

void SwUndoReplace::Impl::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwCursor & rPam(rContext.GetCursorSupplier().CreateNewShellCursor());
    rPam.DeleteMark();
    rPam.GetPoint()->Assign( m_nSttNd, m_nSttCnt );

    rPam.SetMark();
    if( m_bSplitNext )
        rPam.GetPoint()->Assign( m_nSttNd + 1 );
    rPam.GetPoint()->SetContent( m_nSelEnd );

    if( m_pHistory )
    {
        auto xSave = std::make_unique<SwHistory>();
        std::swap(m_pHistory, xSave);

        DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace);
        m_nSetPos = m_pHistory->Count();

        std::swap(xSave, m_pHistory);
        m_pHistory->Move(0, xSave.get());
    }
    else
    {
        m_pHistory.reset( new SwHistory );
        DelContentIndex(*rPam.GetMark(), *rPam.GetPoint(), DelContentType::AllMask | DelContentType::Replace);
        m_nSetPos = m_pHistory->Count();
        if( !m_nSetPos )
        {
            m_pHistory.reset();
        }
    }

    rDoc.getIDocumentContentOperations().ReplaceRange( rPam, m_sIns, m_bRegExp );
    if (*rPam.GetMark() == *rPam.GetPoint())
        rPam.DeleteMark();
    else
        rPam.Normalize(false);
}

void SwUndoReplace::Impl::SetEnd(SwPaM const& rPam)
{
    const SwPosition* pEnd = rPam.End();
    m_nEndNd = m_nOffset + pEnd->GetNodeIndex();
    m_nEndCnt = pEnd->GetContentIndex();
}

SwUndoReRead::SwUndoReRead( const SwPaM& rPam, const SwGrfNode& rGrfNd )
    : SwUndo( SwUndoId::REREAD, rPam.GetDoc() ), mnPosition( rPam.GetPoint()->GetNodeIndex() )
{
    SaveGraphicData( rGrfNd );
}

SwUndoReRead::~SwUndoReRead()
{
}

void SwUndoReRead::SetAndSave(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    SwGrfNode* pGrfNd = rDoc.GetNodes()[ mnPosition ]->GetGrfNode();

    if( !pGrfNd )
        return ;

    // cache the old values
    std::optional<Graphic> oOldGrf(moGraphic);
    std::optional<OUString> aOldNm = maNm;
    MirrorGraph nOldMirr = mnMirror;
    // since all of them are cleared/modified by SaveGraphicData:
    SaveGraphicData( *pGrfNd );

    if( aOldNm )
    {
        pGrfNd->ReRead( *aOldNm, maFltr ? *maFltr : OUString() );
    }
    else
    {
        pGrfNd->ReRead( OUString(), OUString(), oOldGrf ? &*oOldGrf : nullptr );
    }

    if( MirrorGraph::Dont != nOldMirr )
        pGrfNd->SetAttr( SwMirrorGrf() );

    rContext.SetSelections(pGrfNd->GetFlyFormat(), nullptr);
}

void SwUndoReRead::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SetAndSave(rContext);
}

void SwUndoReRead::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SetAndSave(rContext);
}

void SwUndoReRead::SaveGraphicData( const SwGrfNode& rGrfNd )
{
    if( rGrfNd.IsGrfLink() )
    {
        maNm = OUString();
        maFltr = OUString();
        rGrfNd.GetFileFilterNms(&*maNm, &*maFltr);
        moGraphic.reset();
    }
    else
    {
        moGraphic.emplace( rGrfNd.GetGrf(true) );
        maNm.reset();
        maFltr.reset();
    }
    mnMirror = rGrfNd.GetSwAttrSet().GetMirrorGrf().GetValue();
}

SwUndoInsertLabel::SwUndoInsertLabel( const SwLabelType eTyp,
                                      OUString aText,
                                      OUString aSeparator,
                                      OUString aNumberSeparator,
                                      const bool bBef,
                                      const sal_uInt16 nInitId,
                                      UIName aCharacterStyle,
                                      const bool bCpyBorder,
                                      const SwDoc& rDoc )
    : SwUndo( SwUndoId::INSERTLABEL, rDoc ),
      m_sText(std::move( aText )),
      m_sSeparator(std::move( aSeparator )),
      m_sNumberSeparator(std::move( aNumberSeparator )),//#i61007# order of captions
      m_sCharacterStyle(std::move( aCharacterStyle )),
      m_nFieldId( nInitId ),
      m_eType( eTyp ),
      m_nLayerId( 0 ),
      m_bBefore( bBef ),
      m_bCopyBorder( bCpyBorder )
{
    m_bUndoKeep = false;
    OBJECT.pUndoFly = nullptr;
    OBJECT.pUndoAttr = nullptr;
}

SwUndoInsertLabel::~SwUndoInsertLabel()
{
    if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType )
    {
        delete OBJECT.pUndoFly;
        delete OBJECT.pUndoAttr;
    }
    else
        delete NODE.pUndoInsNd;
}

void SwUndoInsertLabel::UndoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType )
    {
        OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" );
        SwFrameFormat* pFormat;
        SdrObject *pSdrObj = nullptr;
        if( OBJECT.pUndoAttr &&
            nullptr != (pFormat = static_cast<SwFrameFormat*>(OBJECT.pUndoAttr->GetFormat( rDoc ))) &&
            ( SwLabelType::Draw != m_eType ||
              nullptr != (pSdrObj = pFormat->FindSdrObject()) ) )
        {
            OBJECT.pUndoAttr->UndoImpl(rContext);
            OBJECT.pUndoFly->UndoImpl(rContext);
            if( SwLabelType::Draw == m_eType )
            {
                pSdrObj->SetLayer( m_nLayerId );
            }
        }
    }
    else if( NODE.nNode )
    {
        if ( m_eType == SwLabelType::Table && m_bUndoKeep )
        {
            SwTableNode *pNd = rDoc.GetNodes()[
                        rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode();
            if ( pNd )
                pNd->GetTable().GetFrameFormat()->ResetFormatAttr( RES_KEEP );
        }
        SwPaM aPam( rDoc.GetNodes().GetEndOfContent() );
        aPam.GetPoint()->Assign( NODE.nNode );
        aPam.SetMark();
        aPam.GetPoint()->Assign( NODE.nNode + 1 );
        NODE.pUndoInsNd = new SwUndoDelete(aPam, SwDeleteFlags::Default, true);
    }
}

void SwUndoInsertLabel::RedoImpl(::sw::UndoRedoContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();

    if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType )
    {
        OSL_ENSURE( OBJECT.pUndoAttr && OBJECT.pUndoFly, "Pointer not initialized" );
        SwFrameFormat* pFormat;
        SdrObject *pSdrObj = nullptr;
        if( OBJECT.pUndoAttr &&
            nullptr != (pFormat = static_cast<SwFrameFormat*>(OBJECT.pUndoAttr->GetFormat( rDoc ))) &&
            ( SwLabelType::Draw != m_eType ||
              nullptr != (pSdrObj = pFormat->FindSdrObject()) ) )
        {
            OBJECT.pUndoFly->RedoImpl(rContext);
            OBJECT.pUndoAttr->RedoImpl(rContext);
            if( SwLabelType::Draw == m_eType )
            {
                pSdrObj->SetLayer( m_nLayerId );
                if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetHellId() )
                    pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetHeavenId() );
                // OD 02.07.2003 #108784#
                else if( pSdrObj->GetLayer() == rDoc.getIDocumentDrawModelAccess().GetInvisibleHellId() )
                    pSdrObj->SetLayer( rDoc.getIDocumentDrawModelAccess().GetInvisibleHeavenId() );
            }
        }
    }
    else if( NODE.pUndoInsNd )
    {
        if ( m_eType == SwLabelType::Table && m_bUndoKeep )
        {
            SwTableNode *pNd = rDoc.GetNodes()[
                        rDoc.GetNodes()[NODE.nNode-1]->StartOfSectionIndex()]->GetTableNode();
            if ( pNd )
                pNd->GetTable().GetFrameFormat()->SetFormatAttr( SvxFormatKeepItem(true, RES_KEEP) );
        }
        NODE.pUndoInsNd->UndoImpl(rContext);
        delete NODE.pUndoInsNd;
        NODE.pUndoInsNd = nullptr;
    }
}

void SwUndoInsertLabel::RepeatImpl(::sw::RepeatContext & rContext)
{
    SwDoc & rDoc = rContext.GetDoc();
    const SwPosition& rPos = *rContext.GetRepeatPaM().GetPoint();

    SwNodeOffset nIdx(0);

    SwContentNode* pCNd = rPos.GetNode().GetContentNode();
    if( pCNd )
        switch( m_eType )
        {
        case SwLabelType::Table:
            {
                const SwTableNode* pTNd = pCNd->FindTableNode();
                if( pTNd )
                    nIdx = pTNd->GetIndex();
            }
            break;

        case SwLabelType::Fly:
        case SwLabelType::Object:
            {
                SwFlyFrame* pFly;
                SwContentFrame *pCnt = pCNd->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout() );
                if( pCnt && nullptr != ( pFly = pCnt->FindFlyFrame() ) )
                    nIdx = pFly->GetFormat()->GetContent().GetContentIdx()->GetIndex();
            }
            break;
        case SwLabelType::Draw:
            break;
        }

    if( nIdx )
    {
        rDoc.InsertLabel( m_eType, m_sText, m_sSeparator, m_sNumberSeparator, m_bBefore,
            m_nFieldId, nIdx, m_sCharacterStyle, m_bCopyBorder );
    }
}

SwRewriter SwUndoInsertLabel::GetRewriter() const
{
    return CreateRewriter(m_sText);
}

SwRewriter SwUndoInsertLabel::CreateRewriter(const OUString &rStr)
{
    SwRewriter aRewriter;

    OUString aTmpStr;

    if (!rStr.isEmpty())
    {
        aTmpStr =
            SwResId(STR_START_QUOTE)
            + ShortenString(rStr, nUndoStringLength, SwResId(STR_LDOTS))
            + SwResId(STR_END_QUOTE);
    }

    aRewriter.AddRule(UndoArg1, aTmpStr);

    return aRewriter;
}

void SwUndoInsertLabel::SetFlys( SwFrameFormat& rOldFly, SfxItemSet const & rChgSet,
                                SwFrameFormat& rNewFly )
{
    if( SwLabelType::Object == m_eType || SwLabelType::Draw == m_eType )
    {
        SwUndoFormatAttrHelper aTmp( rOldFly, false );
        rOldFly.SetFormatAttr( rChgSet );
        if ( aTmp.GetUndo() )
        {
            OBJECT.pUndoAttr = aTmp.ReleaseUndo().release();
        }
        OBJECT.pUndoFly = new SwUndoInsLayFormat( &rNewFly, SwNodeOffset(0), 0 );
    }
}

void SwUndoInsertLabel::SetDrawObj( SdrLayerID nLId )
{
    if( SwLabelType::Draw == m_eType )
    {
        m_nLayerId = nLId;
    }
}

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