/* -*- 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 <sal/config.h>

#include <comphelper/servicehelper.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <o3tl/safeint.hxx>
#include <svl/listener.hxx>
#include <svx/svdobj.hxx>
#include <utility>
#include <vcl/svapp.hxx>

#include <anchoredobject.hxx>
#include <swtypes.hxx>
#include <hintids.hxx>
#include <IMark.hxx>
#include <frmfmt.hxx>
#include <doc.hxx>
#include <IDocumentUndoRedo.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <textboxhelper.hxx>
#include <ndtxt.hxx>
#include <unocrsr.hxx>
#include <unotextcursor.hxx>
#include <swundo.hxx>
#include <rootfrm.hxx>
#include <ftnidx.hxx>
#include <pam.hxx>
#include <swtblfmt.hxx>
#include <docsh.hxx>
#include <pagedesc.hxx>
#include <cntfrm.hxx>
#include <flyfrm.hxx>
#include <flyfrms.hxx>
#include <unoparaframeenum.hxx>
#include <unofootnote.hxx>
#include <unotextbodyhf.hxx>
#include <unotextrange.hxx>
#include <unoparagraph.hxx>
#include <unomap.hxx>
#include <unoport.hxx>
#include <unocrsrhelper.hxx>
#include <unotbl.hxx>
#include <fmtanchr.hxx>
#include <flypos.hxx>
#include <txtftn.hxx>
#include <fmtftn.hxx>
#include <fmtcntnt.hxx>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
#include <unoframe.hxx>
#include <fmthdft.hxx>
#include <fmtflcnt.hxx>
#include <vector>
#include <sortedobjs.hxx>
#include <frameformats.hxx>
#include <algorithm>
#include <iterator>
#include <unotxdoc.hxx>

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

namespace sw {

void DeepCopyPaM(SwPaM const & rSource, SwPaM & rTarget)
{
    rTarget = rSource;

    if (rSource.GetNext() == &rSource)
        return;

    SwPaM *pPam = const_cast<SwPaM*>(rSource.GetNext());
    do
    {
        // create new PaM
        SwPaM *const pNew = new SwPaM(*pPam, nullptr);
        // insert into ring
        pNew->MoveTo(&rTarget);
        pPam = pPam->GetNext();
    }
    while (pPam != &rSource);
}

} // namespace sw

namespace {

struct FrameClientSortListLess
{
    bool operator() (FrameClientSortListEntry const& r1,
                     FrameClientSortListEntry const& r2) const
    {
        return  (r1.nIndex <  r2.nIndex)
            || ((r1.nIndex == r2.nIndex) && (r1.nOrder < r2.nOrder));
    }
};

    void lcl_CollectFrameAtNodeWithLayout(const SwContentFrame* pCFrame,
            FrameClientSortList_t& rFrames,
            const RndStdIds nAnchorType)
    {
        auto pObjs = pCFrame->GetDrawObjs();
        if(!pObjs)
            return;
        for(const auto pAnchoredObj : *pObjs)
        {
            SwFrameFormat* pFormat = pAnchoredObj->GetFrameFormat();
            // Filter out textboxes, which are not interesting at a UNO level.
            if(SwTextBoxHelper::isTextBox(pFormat, RES_FLYFRMFMT))
                continue;

            if (nAnchorType == RndStdIds::FLY_AT_PARA)
            {
                auto pFlyAtContentFrame = dynamic_cast<SwFlyAtContentFrame*>(pAnchoredObj);
                if (pFlyAtContentFrame && pFlyAtContentFrame->IsFollow())
                {
                    // We're collecting frame formats, ignore non-master fly frames to prevent
                    // duplication.
                    continue;
                }
            }

            if(pFormat->GetAnchor().GetAnchorId() == nAnchorType)
            {
                const sal_Int32 nIdx = pFormat->GetAnchor().GetAnchorContentOffset();
                const auto nOrder = pFormat->GetAnchor().GetOrder();
                rFrames.emplace_back(nIdx, nOrder, std::make_unique<sw::FrameClient>(pFormat));
            }
        }
    }
}


void CollectFrameAtNode( const SwNode& rNd,
                         FrameClientSortList_t& rFrames,
                         const bool bAtCharAnchoredObjs )
{
    // _bAtCharAnchoredObjs:
    // <true>: at-character anchored objects are collected
    // <false>: at-paragraph anchored objects are collected

    // search all borders, images, and OLEs that are connected to the paragraph
    const SwDoc& rDoc = rNd.GetDoc();

    const auto nChkType = bAtCharAnchoredObjs ? RndStdIds::FLY_AT_CHAR : RndStdIds::FLY_AT_PARA;
    const SwContentFrame* pCFrame;
    const SwContentNode* pCNd;
    if( rDoc.getIDocumentLayoutAccess().GetCurrentViewShell() &&
        nullptr != (pCNd = rNd.GetContentNode()) &&
        nullptr != (pCFrame = pCNd->getLayoutFrame( rDoc.getIDocumentLayoutAccess().GetCurrentLayout())) )
    {
        lcl_CollectFrameAtNodeWithLayout(pCFrame, rFrames, nChkType);
    }
    else
    {
        for(sw::SpzFrameFormat* pSpz: *rDoc.GetSpzFrameFormats())
        {
            const SwFormatAnchor& rAnchor = pSpz->GetAnchor();
            const SwNode* pAnchorNode;
            if( rAnchor.GetAnchorId() == nChkType &&
                nullptr != (pAnchorNode = rAnchor.GetAnchorNode()) &&
                    *pAnchorNode == rNd )
            {

                // OD 2004-05-07 #i28701# - determine insert position for
                // sorted <rFrameArr>
                const sal_Int32 nIndex = rAnchor.GetAnchorContentOffset();
                sal_uInt32 nOrder = rAnchor.GetOrder();

                rFrames.emplace_back(nIndex, nOrder, std::make_unique<sw::FrameClient>(pSpz));
            }
        }
        std::sort(rFrames.begin(), rFrames.end(), FrameClientSortListLess());
    }
}

UnoActionContext::UnoActionContext(SwDoc *const pDoc)
    : m_pDoc(pDoc)
{
    SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    if (pRootFrame)
    {
        pRootFrame->StartAllAction();
    }
}

UnoActionContext::~UnoActionContext() COVERITY_NOEXCEPT_FALSE
{
    // Doc may already have been removed here
    if (m_pDoc)
    {
        SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
        if (pRootFrame)
        {
            pRootFrame->EndAllAction();
        }
    }
}

static void lcl_RemoveImpl(SwDoc *const pDoc)
{
    assert(pDoc);
    SwRootFrame *const pRootFrame = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
    if (pRootFrame)
    {
        pRootFrame->UnoRemoveAllActions();
    }
}

UnoActionRemoveContext::UnoActionRemoveContext(SwDoc *const pDoc)
    : m_pDoc(pDoc)
{
    lcl_RemoveImpl(m_pDoc);
}

static SwDoc * lcl_IsNewStyleTable(SwUnoTableCursor const& rCursor)
{
    SwTableNode *const pTableNode = rCursor.GetPointNode().FindTableNode();
    return (pTableNode && !pTableNode->GetTable().IsNewModel())
        ? &rCursor.GetDoc()
        : nullptr;
}

UnoActionRemoveContext::UnoActionRemoveContext(SwUnoTableCursor const& rCursor)
    : m_pDoc(lcl_IsNewStyleTable(rCursor))
{
    // this insanity is only necessary for old-style tables
    // because SwRootFrame::MakeTableCursors() creates the table cursor for these
    if (m_pDoc)
    {
        lcl_RemoveImpl(m_pDoc);
    }
}

UnoActionRemoveContext::~UnoActionRemoveContext() COVERITY_NOEXCEPT_FALSE
{
    if (m_pDoc)
    {
        SwRootFrame *const pRootFrame = m_pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
        if (pRootFrame)
        {
            pRootFrame->UnoRestoreAllActions();
        }
    }
}

void SwUnoCursorHelper::SetCursorAttr(SwPaM & rPam,
        const SfxItemSet& rSet,
        const SetAttrMode nAttrMode, const bool bTableMode)
{
    const SetAttrMode nFlags = nAttrMode | SetAttrMode::APICALL;
    SwDoc& rDoc = rPam.GetDoc();
    //StartEndAction
    UnoActionContext aAction(&rDoc);
    if (rPam.GetNext() != &rPam)    // Ring of Cursors
    {
        rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSATTR, nullptr);

        for(SwPaM& rCurrent : rPam.GetRingContainer())
        {
            if (rCurrent.HasMark() &&
                ( bTableMode ||
                  (*rCurrent.GetPoint() != *rCurrent.GetMark()) ))
            {
                rDoc.getIDocumentContentOperations().InsertItemSet(rCurrent, rSet, nFlags);
            }
        }

        rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::INSATTR, nullptr);
    }
    else
    {
        rDoc.getIDocumentContentOperations().InsertItemSet( rPam, rSet, nFlags );
    }

    if( rSet.GetItemState( RES_PARATR_OUTLINELEVEL, false ) >= SfxItemState::DEFAULT )
    {
        SwTextNode * pTmpNode = rPam.GetPointNode().GetTextNode();
        if ( pTmpNode )
        {
            rPam.GetDoc().GetNodes().UpdateOutlineNode( *pTmpNode );
        }
    }
}

// #i63870#
// split third parameter <bCurrentAttrOnly> into new parameters <bOnlyTextAttr>
// and <bGetFromChrFormat> to get better control about resulting <SfxItemSet>
void SwUnoCursorHelper::GetCursorAttr(SwPaM & rPam,
        SfxItemSet & rSet, const bool bOnlyTextAttr, const bool bGetFromChrFormat)
{
    static constexpr sal_Int32 nMaxLookup = 1000;
    SfxItemSet aSet( *rSet.GetPool(), rSet.GetRanges() );
    SfxItemSet *pSet = &rSet;
    for(SwPaM& rCurrent : rPam.GetRingContainer())
    {
        SwPosition const & rStart( *rCurrent.Start() );
        SwPosition const & rEnd( *rCurrent.End() );
        const SwNodeOffset nSttNd = rStart.GetNodeIndex();
        const SwNodeOffset nEndNd = rEnd  .GetNodeIndex();

        if (nEndNd - nSttNd >= SwNodeOffset(nMaxLookup))
        {
            rSet.ClearItem();
            return;// uno::Any();
        }

        // the first node inserts the values into the get set
        // all other nodes merge their values into the get set
        for (SwNodeOffset n = nSttNd; n <= nEndNd; ++n)
        {
            SwNode *const pNd = rPam.GetDoc().GetNodes()[ n ];
            switch (pNd->GetNodeType())
            {
                case SwNodeType::Text:
                {
                    const sal_Int32 nStart = (n == nSttNd)
                        ? rStart.GetContentIndex() : 0;
                    const sal_Int32 nEnd   = (n == nEndNd)
                        ? rEnd.GetContentIndex()
                        : pNd->GetTextNode()->GetText().getLength();
                    pNd->GetTextNode()->GetParaAttr(*pSet, nStart, nEnd, bOnlyTextAttr, bGetFromChrFormat);
                }
                break;

                case SwNodeType::Grf:
                case SwNodeType::Ole:
                    static_cast<SwContentNode*>(pNd)->GetAttr( *pSet );
                break;

                default:
                    continue; // skip this node
            }

            if (pSet != &rSet)
            {
                rSet.MergeValues( aSet );
            }
            else
            {
                pSet = &aSet;
            }

            if (aSet.Count())
            {
                aSet.ClearItem();
            }
        }
    }
}

namespace {

struct SwXParagraphEnumerationImpl final : public SwXParagraphEnumeration
{
    uno::Reference< text::XText > const m_xParentText;
    const CursorType m_eCursorType;
    /// Start node of the cell _or_ table the enumeration belongs to.
    /// Used to restrict the movement of the UNO cursor to the cell and its
    /// embedded tables.
    SwStartNode const*const m_pOwnStartNode;
    SwTable const*const m_pOwnTable;
    const SwNodeOffset m_nEndIndex;
    sal_Int32 m_nFirstParaStart;
    sal_Int32 m_nLastParaEnd;
    bool m_bFirstParagraph;
    uno::Reference< text::XTextContent > m_xNextPara;
    sw::UnoCursorPointer m_pCursor;

    SwXParagraphEnumerationImpl(
            uno::Reference< text::XText > xParent,
            const std::shared_ptr<SwUnoCursor>& pCursor,
            const CursorType eType,
            SwStartNode const*const pStartNode, SwTable const*const pTable)
        : m_xParentText(std::move( xParent ))
        , m_eCursorType( eType )
        // remember table and start node for later travelling
        // (used in export of tables in tables)
        , m_pOwnStartNode( pStartNode )
        // for import of tables in tables we have to remember the actual
        // table and start node of the current position in the enumeration.
        , m_pOwnTable( pTable )
        , m_nEndIndex( pCursor->End()->GetNodeIndex() )
        , m_nFirstParaStart( -1 )
        , m_nLastParaEnd( -1 )
        , m_bFirstParagraph( true )
        , m_pCursor(pCursor)
    {
        OSL_ENSURE(m_xParentText.is(), "SwXParagraphEnumeration: no parent?");
        assert( !((CursorType::SelectionInTable == eType)
                      || (CursorType::TableText == eType))
            || (m_pOwnTable && m_pOwnStartNode));

        if ((CursorType::Selection == m_eCursorType) ||
            (CursorType::SelectionInTable == m_eCursorType))
        {
            SwUnoCursor & rCursor = GetCursor();
            rCursor.Normalize();
            m_nFirstParaStart = rCursor.GetPoint()->GetContentIndex();
            m_nLastParaEnd = rCursor.GetMark()->GetContentIndex();
            rCursor.DeleteMark();
        }
    }

    virtual ~SwXParagraphEnumerationImpl() override
        { m_pCursor.reset(nullptr); }
    virtual void SAL_CALL release() noexcept override
    {
        SolarMutexGuard g;
        OWeakObject::release();
    }

    // XServiceInfo
    virtual OUString SAL_CALL getImplementationName() override
        { return u"SwXParagraphEnumeration"_ustr; }
    virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName) override
        { return cppu::supportsService(this, rServiceName); };
    virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
        { return {u"com.sun.star.text.ParagraphEnumeration"_ustr}; };

    // XEnumeration
    virtual sal_Bool SAL_CALL hasMoreElements() override;
    virtual css::uno::Any SAL_CALL nextElement() override;

    SwUnoCursor& GetCursor()
        { return *m_pCursor; }
    /// @throws container::NoSuchElementException
    /// @throws lang::WrappedTargetException
    /// @throws uno::RuntimeException
    uno::Reference< text::XTextContent > NextElement_Impl();

    /**
     * Determines if the last element in the enumeration should be ignored or
     * not.
     */
    bool IgnoreLastElement(SwUnoCursor& rCursor, bool bMovedFromTable);
};

}

rtl::Reference<SwXParagraphEnumeration> SwXParagraphEnumeration::Create(
    uno::Reference< text::XText > const& xParent,
    const std::shared_ptr<SwUnoCursor>& pCursor,
    const CursorType eType,
    SwTableBox const*const pTableBox)
{
    SwStartNode const* pStartNode(nullptr);
    SwTable const* pTable(nullptr);
    assert((eType == CursorType::TableText) == (pTableBox != nullptr));
    switch (eType)
    {
        case CursorType::TableText:
        {
            pStartNode = pTableBox->GetSttNd();
            pTable = & pStartNode->FindTableNode()->GetTable();
            break;
        }
        case CursorType::SelectionInTable:
        {
            SwTableNode const*const pTableNode(
                pCursor->GetPoint()->GetNode().FindTableNode());
            pStartNode = pTableNode;
            pTable = & pTableNode->GetTable();
            break;
        }
        default:
            break;
    }
    return new SwXParagraphEnumerationImpl(xParent, pCursor, eType, pStartNode, pTable);
}

sal_Bool SAL_CALL
SwXParagraphEnumerationImpl::hasMoreElements()
{
    SolarMutexGuard aGuard;
    return m_bFirstParagraph || m_xNextPara.is();
}

//!! compare to SwShellTableCursor::FillRects() in viscrs.cxx
static SwTableNode *
lcl_FindTopLevelTable(
        SwTableNode *const pTableNode, SwTable const*const pOwnTable)
{
    // find top-most table in current context (section) level

    SwTableNode * pLast = pTableNode;
    for (SwTableNode* pTmp = pLast;
         pTmp != nullptr  &&  &pTmp->GetTable() != pOwnTable;  /* we must not go up higher than the own table! */
         pTmp = pTmp->StartOfSectionNode()->FindTableNode() )
    {
        pLast = pTmp;
    }
    return pLast;
}

static bool
lcl_CursorIsInSection(
        SwUnoCursor const*const pUnoCursor, SwStartNode const*const pOwnStartNode)
{
    // returns true if the cursor is in the section (or in a sub section!)
    // represented by pOwnStartNode

    bool bRes = true;
    if (pUnoCursor && pOwnStartNode)
    {
        const SwEndNode * pOwnEndNode = pOwnStartNode->EndOfSectionNode();
        bRes = pOwnStartNode->GetIndex() <= pUnoCursor->Start()->GetNodeIndex() &&
               pUnoCursor->End()->GetNodeIndex() <= pOwnEndNode->GetIndex();
    }
    return bRes;
}

bool SwXParagraphEnumerationImpl::IgnoreLastElement(SwUnoCursor& rCursor, bool bMovedFromTable)
{
    // Ignore the last element of a selection enumeration if this is a stub
    // paragraph (directly after table, selection ends at paragraph start).

    if (rCursor.Start()->GetNodeIndex() != m_nEndIndex)
        return false;

    if (m_eCursorType != CursorType::Selection)
        return false;

    if (!bMovedFromTable)
        return false;

    return m_nLastParaEnd == 0;
}

uno::Reference< text::XTextContent >
SwXParagraphEnumerationImpl::NextElement_Impl()
{
    SwUnoCursor& rUnoCursor = GetCursor();

    // check for exceeding selections
    if (!m_bFirstParagraph &&
        ((CursorType::Selection == m_eCursorType) ||
         (CursorType::SelectionInTable == m_eCursorType)))
    {
        SwPosition* pStart = rUnoCursor.Start();
        auto aNewCursor(rUnoCursor.GetDoc().CreateUnoCursor(*pStart));
        // one may also go into tables here
        if (CursorType::SelectionInTable != m_eCursorType)
        {
            aNewCursor->SetRemainInSection( false );
        }

        // os 2005-01-14: This part is only necessary to detect movements out
        // of a selection; if there is no selection we don't have to care
        SwTableNode *const pTableNode = aNewCursor->GetPointNode().FindTableNode();
        bool bMovedFromTable = false;
        if (CursorType::SelectionInTable != m_eCursorType && pTableNode)
        {
            aNewCursor->GetPoint()->Assign( pTableNode->EndOfSectionIndex() );
            aNewCursor->Move(fnMoveForward, GoInNode);
            bMovedFromTable = true;
        }
        else
        {
            aNewCursor->MovePara(GoNextPara, fnParaStart);
        }
        if (m_nEndIndex < aNewCursor->Start()->GetNodeIndex())
        {
            return nullptr;
        }

        if (IgnoreLastElement(*aNewCursor, bMovedFromTable))
        {
            return nullptr;
        }
    }

    bool bInTable = false;
    if (!m_bFirstParagraph)
    {
        rUnoCursor.SetRemainInSection( false );
        // what to do if already in a table?
        SwTableNode * pTableNode = rUnoCursor.GetPointNode().FindTableNode();
        pTableNode = lcl_FindTopLevelTable( pTableNode, m_pOwnTable );
        if (pTableNode && (&pTableNode->GetTable() != m_pOwnTable))
        {
            // this is a foreign table: go to end
            rUnoCursor.GetPoint()->Assign( pTableNode->EndOfSectionIndex() );
            if (!rUnoCursor.Move(fnMoveForward, GoInNode))
            {
                return nullptr;
            }
            bInTable = true;
        }
    }

    uno::Reference< text::XTextContent >  xRef;
    // the cursor must remain in the current section or a subsection
    // before AND after the movement...
    if (lcl_CursorIsInSection( &rUnoCursor, m_pOwnStartNode ) &&
        (m_bFirstParagraph || bInTable ||
        (rUnoCursor.MovePara(GoNextPara, fnParaStart) &&
            lcl_CursorIsInSection( &rUnoCursor, m_pOwnStartNode ))))
    {
        if (m_eCursorType == CursorType::Selection || m_eCursorType == CursorType::SelectionInTable)
        {
            // This is a selection, check if the cursor would go past the end
            // of the selection.
            if (rUnoCursor.Start()->GetNodeIndex() > m_nEndIndex)
                return nullptr;
        }

        SwPosition* pStart = rUnoCursor.Start();
        const sal_Int32 nFirstContent =
            m_bFirstParagraph ? m_nFirstParaStart : -1;
        const sal_Int32 nLastContent =
            (m_nEndIndex == pStart->GetNodeIndex()) ? m_nLastParaEnd : -1;

        // position in a table, or in a simple paragraph?
        SwTableNode * pTableNode = rUnoCursor.GetPointNode().FindTableNode();
        pTableNode = lcl_FindTopLevelTable( pTableNode, m_pOwnTable );
        if (/*CursorType::TableText != eCursorType && CursorType::SelectionInTable != eCursorType && */
            pTableNode && (&pTableNode->GetTable() != m_pOwnTable))
        {
            // this is a foreign table
            SwFrameFormat* pTableFormat = pTableNode->GetTable().GetFrameFormat();
            xRef = SwXTextTable::CreateXTextTable(pTableFormat);
        }
        else
        {
            text::XText *const pText = m_xParentText.get();
            xRef = SwXParagraph::CreateXParagraph(rUnoCursor.GetDoc(),
                pStart->GetNode().GetTextNode(),
                static_cast<SwXText*>(pText), nFirstContent, nLastContent);
        }
    }

    return xRef;
}

uno::Any SAL_CALL SwXParagraphEnumerationImpl::nextElement()
{
    SolarMutexGuard aGuard;
    if (m_bFirstParagraph)
    {
        m_xNextPara = NextElement_Impl();
        m_bFirstParagraph = false;
    }
    const uno::Reference< text::XTextContent > xRef = m_xNextPara;
    if (!xRef.is())
    {
        throw container::NoSuchElementException();
    }
    m_xNextPara = NextElement_Impl();

    uno::Any aRet;
    aRet <<= xRef;
    return aRet;
}

void SwXTextRange::InvalidateImpl()
{
    if (m_pMark)
    {
        m_rDoc.getIDocumentMarkAccess()->deleteMark(m_pMark);
        m_pMark = nullptr;
    }
    m_pTableOrSectionFormat = nullptr;
    moSvtListener->EndListeningAll();
}

void SwXTextRange::SetMark(::sw::mark::MarkBase& rMark)
{
    moSvtListener->EndListeningAll();
    m_pTableOrSectionFormat = nullptr;
    m_pMark = &rMark;
    moSvtListener->StartListening(rMark.GetNotifier());
}

void SwXTextRange::MySvtListener::Notify(const SfxHint& rHint)
{
    if(rHint.GetId() == SfxHintId::Dying)
    {
        EndListeningAll();
        mrTextRange.m_pTableOrSectionFormat = nullptr;
        mrTextRange.m_pMark = nullptr;
    }
}

SwXTextRange::SwXTextRange(SwPaM const & rPam,
        const uno::Reference< text::XText > & xParent,
        const enum RangePosition eRange,
        bool const isInCell)
    : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR))
    , m_eRangePosition(eRange)
    , m_isRangeInCell(isInCell)
    , m_rDoc(rPam.GetDoc())
    , m_xParentText(xParent)
    , m_pTableOrSectionFormat(nullptr)
    , m_pMark(nullptr)
    , moSvtListener(std::in_place, *this)
{
    assert(m_eRangePosition != RANGE_IS_TABLE && m_eRangePosition != RANGE_IS_SECTION);
    SetPositions(rPam);
}

SwXTextRange::SwXTextRange(SwTableFormat& rTableFormat)
    : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR))
    , m_eRangePosition(RANGE_IS_TABLE)
    , m_isRangeInCell(false)
    , m_rDoc(rTableFormat.GetDoc())
    , m_pTableOrSectionFormat(&rTableFormat)
    , m_pMark(nullptr)
    , moSvtListener(std::in_place, *this)
{
    moSvtListener->StartListening(rTableFormat.GetNotifier());
    SwTable *const pTable = SwTable::FindTable( &rTableFormat );
    SwTableNode *const pTableNode = pTable->GetTableNode();
    SwPaM aPam( *pTableNode );

    SetPositions( aPam );
}

SwXTextRange::SwXTextRange(SwSectionFormat& rSectionFormat)
    : m_rPropSet(*aSwMapProvider.GetPropertySet(PROPERTY_MAP_TEXT_CURSOR))
    , m_eRangePosition(RANGE_IS_SECTION)
    , m_isRangeInCell(false)
    , m_rDoc(rSectionFormat.GetDoc())
    , m_pTableOrSectionFormat(&rSectionFormat)
    , m_pMark(nullptr)
    , moSvtListener(std::in_place, *this)
{
    moSvtListener->StartListening(rSectionFormat.GetNotifier());
    // no SetPositions here for now
}

SwXTextRange::~SwXTextRange()
{
    // have to destruct SvtListener with SolarMutex held
    SolarMutexGuard aGuard;
    InvalidateImpl(); // delete bookmark under SolarMutex
    moSvtListener.reset();
}

void SwXTextRange::Invalidate()
{
    InvalidateImpl();
}

void SwXTextRange::SetPositions(const SwPaM& rPam)
{
    InvalidateImpl();
    IDocumentMarkAccess* const pMA = m_rDoc.getIDocumentMarkAccess();
    auto pMark = pMA->makeMark(rPam, SwMarkName(), IDocumentMarkAccess::MarkType::UNO_BOOKMARK, sw::mark::InsertMode::New);
    if (pMark)
        SetMark(*pMark);
}

static void DeleteTable(SwDoc & rDoc, SwTable& rTable)
{
    SwSelBoxes aSelBoxes;
    for (auto& rBox : rTable.GetTabSortBoxes())
    {
        aSelBoxes.insert(rBox);
    }
    // note: if the table is the content in the section, this will create
    // a new text node - that's desirable here
    rDoc.DeleteRowCol(aSelBoxes, SwDoc::RowColMode::DeleteProtected);
}

void SwXTextRange::DeleteAndInsert(
        std::u16string_view aText, ::sw::DeleteAndInsertMode const eMode)
{
    if (RANGE_IS_TABLE == m_eRangePosition)
    {
        // setString on table not allowed
        throw uno::RuntimeException(u"not possible for table"_ustr);
    }

    const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent());
    SwCursor aCursor(aPos, nullptr);

    UnoActionContext aAction(&m_rDoc);

    if (RANGE_IS_SECTION == m_eRangePosition)
    {
        SwSectionNode const* pSectionNode = m_pTableOrSectionFormat ?
            static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat)->GetSectionNode() :
            nullptr;
        if (!pSectionNode)
        {
            throw uno::RuntimeException(u"disposed?"_ustr);
        }
        m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr);
        SwNodeIndex const start(*pSectionNode);
        SwNodeIndex const end(*start.GetNode().EndOfSectionNode());

        // delete tables at start
        for (SwNodeIndex i = start; i < end; )
        {
            SwNode & rNode(i.GetNode());
            if (rNode.IsSectionNode())
            {
                ++i;
                continue;
            }
            else if (SwTableNode *const pTableNode = rNode.GetTableNode())
            {
                DeleteTable(m_rDoc, pTableNode->GetTable());
                // where does that leave index? presumably behind?
            }
            else
            {
                assert(rNode.IsTextNode());
                break;
            }
        }
        // delete tables at end
        for (SwNodeIndex i = end; start <= i; )
        {
            --i;
            SwNode & rNode(i.GetNode());
            if (rNode.IsEndNode())
            {
                if (SwTableNode *const pTableNode = rNode.StartOfSectionNode()->GetTableNode())
                {
                    DeleteTable(m_rDoc, pTableNode->GetTable());
                }
                else
                {
                    assert(rNode.StartOfSectionNode()->IsSectionNode());
                    continue;
                }
            }
            else
            {
                assert(rNode.IsTextNode());
                break;
            }
        }
        // now there should be a text node at the start and end of it!
        aCursor.GetPoint()->Assign(start);
        aCursor.Move( fnMoveForward, GoInContent );
        assert(aCursor.GetPoint()->GetNode() <= end.GetNode());
        aCursor.SetMark();
        aCursor.GetPoint()->Assign(end);
        aCursor.Move( fnMoveBackward, GoInContent );
        assert(start <= aCursor.GetPoint()->GetNode());
    }
    else
    {
        if (!GetPositions(aCursor))
            return;
        m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr);
    }

    if (aCursor.HasMark())
    {
        m_rDoc.getIDocumentContentOperations().DeleteAndJoin(aCursor,
            (!aText.empty() || eMode & ::sw::DeleteAndInsertMode::ForceReplace) ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default);
    }

    if (!aText.empty())
    {
        SwUnoCursorHelper::DocInsertStringSplitCR(
            m_rDoc, aCursor, aText, bool(eMode & ::sw::DeleteAndInsertMode::ForceExpandHints));

        SwUnoCursorHelper::SelectPam(aCursor, true);
        aCursor.Left(aText.size());
    }
    SetPositions(aCursor);
    m_rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr);
}

OUString SAL_CALL
SwXTextRange::getImplementationName()
{
    return u"SwXTextRange"_ustr;
}

sal_Bool SAL_CALL SwXTextRange::supportsService(const OUString& rServiceName)
{
    return cppu::supportsService(this, rServiceName);
}

uno::Sequence< OUString > SAL_CALL
SwXTextRange::getSupportedServiceNames()
{
    return {
        u"com.sun.star.text.TextRange"_ustr,
        u"com.sun.star.style.CharacterProperties"_ustr,
        u"com.sun.star.style.CharacterPropertiesAsian"_ustr,
        u"com.sun.star.style.CharacterPropertiesComplex"_ustr,
        u"com.sun.star.style.ParagraphProperties"_ustr,
        u"com.sun.star.style.ParagraphPropertiesAsian"_ustr,
        u"com.sun.star.style.ParagraphPropertiesComplex"_ustr
    };
}

uno::Reference< text::XText > SAL_CALL
SwXTextRange::getText()
{
    SolarMutexGuard aGuard;

    if (!m_xParentText.is() && m_pTableOrSectionFormat)
    {
        std::optional<SwPosition> oPosition;
        if (m_eRangePosition == RANGE_IS_TABLE)
        {
            SwTable const*const pTable = SwTable::FindTable( m_pTableOrSectionFormat );
            SwTableNode const*const pTableNode = pTable->GetTableNode();
            oPosition.emplace(*pTableNode);
        }
        else
        {
            assert(m_eRangePosition == RANGE_IS_SECTION);
            auto const pSectFormat(static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat));
            oPosition.emplace(pSectFormat->GetContent().GetContentIdx()->GetNode());
        }
        m_xParentText = ::sw::CreateParentXText(m_rDoc, *oPosition);
    }
    OSL_ENSURE(m_xParentText.is(), "SwXTextRange::getText: no text");
    return m_xParentText;
}

uno::Reference< text::XTextRange > SAL_CALL
SwXTextRange::getStart()
{
    SolarMutexGuard aGuard;

    rtl::Reference< SwXTextRange >  xRet;
    ::sw::mark::MarkBase const * const pBkmk = m_pMark;
    if (!m_xParentText.is())
    {
        getText();
    }
    if(pBkmk)
    {
        SwPaM aPam(pBkmk->GetMarkStart());
        xRet = new SwXTextRange(aPam, m_xParentText);
    }
    else if (RANGE_IS_TABLE == m_eRangePosition)
    {
        // start and end are this, if it's a table
        xRet = this;
    }
    else if (RANGE_IS_SECTION == m_eRangePosition
            && m_pTableOrSectionFormat)
    {
        auto const pSectFormat(static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat));
        SwPaM aPaM(*pSectFormat->GetContent().GetContentIdx());
        aPaM.Move( fnMoveForward, GoInContent );
        assert(aPaM.GetPoint()->GetNode() < *pSectFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionNode());
        xRet = new SwXTextRange(aPaM, m_xParentText);
    }
    else
    {
        throw uno::RuntimeException(u"disposed?"_ustr);
    }
    return xRet;
}

uno::Reference< text::XTextRange > SAL_CALL
SwXTextRange::getEnd()
{
    SolarMutexGuard aGuard;

    rtl::Reference< SwXTextRange >  xRet;
    ::sw::mark::MarkBase const * const pBkmk = m_pMark;
    if (!m_xParentText.is())
    {
        getText();
    }
    if(pBkmk)
    {
        SwPaM aPam(pBkmk->GetMarkEnd());
        xRet = new SwXTextRange(aPam, m_xParentText);
    }
    else if (RANGE_IS_TABLE == m_eRangePosition)
    {
        // start and end are this, if it's a table
        xRet = this;
    }
    else if (RANGE_IS_SECTION == m_eRangePosition
            && m_pTableOrSectionFormat)
    {
        auto const pSectFormat(static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat));
        SwPaM aPaM(*pSectFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionNode());
        aPaM.Move( fnMoveBackward, GoInContent );
        assert(*pSectFormat->GetContent().GetContentIdx() < aPaM.GetPoint()->GetNode());
        xRet = new SwXTextRange(aPaM, m_xParentText);
    }
    else
    {
        throw uno::RuntimeException(u"disposed?"_ustr);
    }
    return xRet;
}

OUString SAL_CALL SwXTextRange::getString()
{
    SolarMutexGuard aGuard;

    OUString sRet;
    // for tables there is no bookmark, thus also no text
    // one could export the table as ASCII here maybe?
    SwPaM aPaM(GetDoc().GetNodes());
    if (GetPositions(aPaM, sw::TextRangeMode::AllowNonTextNode) && aPaM.HasMark())
    {
        SwUnoCursorHelper::GetTextFromPam(aPaM, sRet);
    }
    return sRet;
}

void SAL_CALL SwXTextRange::setString(const OUString& rString)
{
    SolarMutexGuard aGuard;

    // tdf#158198 avoid deleting bookmark via setString on its anchor
    DeleteAndInsert(rString, RANGE_IS_BOOKMARK == m_eRangePosition
                                ? ::sw::DeleteAndInsertMode::ForceReplace
                                : ::sw::DeleteAndInsertMode::Default);
}

bool SwXTextRange::GetPositions(SwPaM& rToFill, ::sw::TextRangeMode const eMode) const
{
    if (RANGE_IS_SECTION == m_eRangePosition)
    {
        if (auto const pSectFormat = static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat))
        {
            if (eMode == ::sw::TextRangeMode::AllowNonTextNode)
            {
                SwNodeIndex const*const pSectionNode(pSectFormat->GetContent().GetContentIdx());
                assert(pSectionNode);
                assert(pSectionNode->GetNodes().IsDocNodes());
                rToFill.GetPoint()->Assign( pSectionNode->GetNode(), SwNodeOffset(1) );
                rToFill.SetMark();
                rToFill.GetMark()->Assign( *pSectionNode->GetNode().EndOfSectionNode(), SwNodeOffset(-1) );
                if (const SwContentNode* pCNd = rToFill.GetMark()->GetContentNode())
                    rToFill.GetMark()->AssignEndIndex(*pCNd);
                return true;
            }
            else
            {
                SwPaM aPaM(*pSectFormat->GetContent().GetContentIdx());
                aPaM.Move(fnMoveForward, GoInContent);
                assert(aPaM.GetPoint()->GetNode() < *pSectFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionNode());
                aPaM.SetMark();
                *aPaM.GetPoint() = SwPosition(*pSectFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionNode());
                aPaM.Move(fnMoveBackward, GoInContent);
                assert(*pSectFormat->GetContent().GetContentIdx() < aPaM.GetPoint()->GetNode());
                // tdf#149555 if there is no table involved, only nested
                // sections, then PaM is valid
                if (aPaM.GetPoint()->GetNode().FindTableNode()
                    == aPaM.GetMark()->GetNode().FindTableNode())
                {
                    rToFill = aPaM;
                    return true;
                }
            }
        }
    }
    ::sw::mark::MarkBase const * const pBkmk = m_pMark;
    if(pBkmk)
    {
        *rToFill.GetPoint() = pBkmk->GetMarkPos();
        if(pBkmk->IsExpanded())
        {
            rToFill.SetMark();
            *rToFill.GetMark() = pBkmk->GetOtherMarkPos();
        }
        else
        {
            rToFill.DeleteMark();
        }
        return true;
    }
    return false;
}

sal_Int16 SwXTextRange::compareRegionStarts(SwXTextRange& rhs)
{
    std::optional<SwPaM> oPam1, oPam2;
    GetStartPaM(oPam1);
    rhs.GetStartPaM(oPam2);

    sal_Int16 nCompare = 0;
    SwPosition const*const pStart1 = oPam1->Start();
    SwPosition const*const pStart2 = oPam2->Start();
    if (*pStart1 < *pStart2)
    {
        nCompare = 1;
    }
    else if (*pStart1 > *pStart2)
    {
        nCompare = -1;
    }
    else
    {
        OSL_ENSURE(*pStart1 == *pStart2,
                "SwPositions should be equal here");
        nCompare = 0;
    }

    return nCompare;
}

void SwXTextRange::GetStartPaM(std::optional<SwPaM>& roPaM)
{
    ::sw::mark::MarkBase const * const pBkmk = m_pMark;
    if (!m_xParentText.is())
    {
        getText();
    }
    if(pBkmk)
    {
        roPaM.emplace(pBkmk->GetMarkStart());
    }
    else if (RANGE_IS_TABLE == m_eRangePosition)
    {
        // start and end are this, if it's a table
        roPaM.emplace(GetDoc().GetNodes());
        GetPositions(*roPaM, sw::TextRangeMode::RequireTextNode);
    }
    else if (RANGE_IS_SECTION == m_eRangePosition
            && m_pTableOrSectionFormat)
    {
        auto const pSectFormat(static_cast<SwSectionFormat const*>(m_pTableOrSectionFormat));
        roPaM.emplace(*pSectFormat->GetContent().GetContentIdx());
        roPaM->Move( fnMoveForward, GoInContent );
        assert(roPaM->GetPoint()->GetNode() < *pSectFormat->GetContent().GetContentIdx()->GetNode().EndOfSectionNode());
    }
    else
    {
        throw uno::RuntimeException(u"disposed?"_ustr);
    }
}

namespace sw {

static bool XTextRangeToSwPaMImpl( SwUnoInternalPaM & rToFill,
        const SwDoc* pDoc,
        const SwPaM* pUnoCursor);

bool XTextRangeToSwPaM( SwUnoInternalPaM & rToFill,
        const uno::Reference<text::XTextRange> & xTextRange,
        ::sw::TextRangeMode const eMode)
{
    SwXTextRange* pRange = dynamic_cast<SwXTextRange*>(xTextRange.get());
    if(pRange && &pRange->GetDoc() == &rToFill.GetDoc())
    {
        return pRange->GetPositions(rToFill, eMode);
    }
    if (SwXParagraph* pPara = dynamic_cast<SwXParagraph*>(xTextRange.get()))
    {
        return pPara->SelectPaM(rToFill);
    }

    SwXHeadFootText* pHeadText
        = eMode == TextRangeMode::AllowTableNode ? dynamic_cast<SwXHeadFootText*>(xTextRange.get()) : nullptr;

    // if it's a text then create a temporary cursor there and re-use
    // the pCursor variable
    // #i108489#: Reference in outside scope to keep cursor alive
    rtl::Reference< SwXTextCursor > xTextCursor;
    OTextCursorHelper* pCursor;
    if (pHeadText)
    {
        // if it is a header / footer text, and eMode == TextRangeMode::AllowTableNode
        // then set the cursor to the beginning of the text
        // if it is started with a table then set into the table
        xTextCursor = pHeadText->CreateTextCursor(true);
        xTextCursor->gotoEnd(true);
        pCursor = xTextCursor.get();
        pCursor->GetPaM()->Normalize();
    }
    else if (SwXText* pText = dynamic_cast<SwXText*>(xTextRange.get()))
    {
        xTextCursor = pText->createXTextCursor();
        xTextCursor->gotoEnd(true);
        pCursor = xTextCursor.get();
    }
    else
    {
        pCursor = dynamic_cast<OTextCursorHelper*>(xTextRange.get());
    }
    SwDoc* pDoc = nullptr;
    const SwPaM* pUnoCursor = nullptr;
    if (pCursor)
    {
        pDoc = pCursor->GetDoc();
        pUnoCursor = pCursor->GetPaM();
    }
    else if (SwXTextPortion* pPortion = dynamic_cast<SwXTextPortion*>(xTextRange.get()))
    {
        pDoc = &pPortion->GetCursor().GetDoc();
        pUnoCursor = &pPortion->GetCursor();
    }
    return XTextRangeToSwPaMImpl(rToFill, pDoc, pUnoCursor);
}

static bool XTextRangeToSwPaMImpl( SwUnoInternalPaM & rToFill,
        const SwDoc* pDoc,
        const SwPaM* pUnoCursor)
{
    bool bRet = false;
    if (pUnoCursor && pDoc == &rToFill.GetDoc())
    {
        OSL_ENSURE(!pUnoCursor->IsMultiSelection(),
                "what to do about rings?");
        bRet = true;
        *rToFill.GetPoint() = *pUnoCursor->GetPoint();
        if (pUnoCursor->HasMark())
        {
            rToFill.SetMark();
            *rToFill.GetMark() = *pUnoCursor->GetMark();
        }
        else
            rToFill.DeleteMark();
    }
    return bRet;
}

static bool
lcl_IsStartNodeInFormat(const bool bHeader, SwStartNode const *const pSttNode,
    SwFrameFormat const*const pFrameFormat, SwFrameFormat*& rpFormat)
{
    bool bRet = false;
    const SfxItemSet& rSet = pFrameFormat->GetAttrSet();
    const SfxPoolItem* pItem;
    if (SfxItemState::SET == rSet.GetItemState(
            bHeader ? sal_uInt16(RES_HEADER) : sal_uInt16(RES_FOOTER),
            true, &pItem))
    {
        SfxPoolItem *const pItemNonConst(const_cast<SfxPoolItem *>(pItem));
        SwFrameFormat *const pHeadFootFormat = bHeader ?
            static_cast<SwFormatHeader*>(pItemNonConst)->GetHeaderFormat() :
            static_cast<SwFormatFooter*>(pItemNonConst)->GetFooterFormat();
        if (pHeadFootFormat)
        {
            const SwFormatContent& rFlyContent = pHeadFootFormat->GetContent();
            // tdf#146248 avoid Undo crash at shared first page
            if ( !rFlyContent.GetContentIdx() )
                return false;
            const SwNode& rNode = rFlyContent.GetContentIdx()->GetNode();
            SwStartNode const*const pCurSttNode = rNode.FindStartNodeByType(
                bHeader ? SwHeaderStartNode : SwFooterStartNode);
            if (pCurSttNode && (pCurSttNode == pSttNode))
            {
                rpFormat = pHeadFootFormat;
                bRet = true;
            }
        }
    }
    return bRet;
}

} // namespace sw

rtl::Reference< SwXTextRange >
SwXTextRange::CreateXTextRange(
    SwDoc & rDoc, const SwPosition& rPos, const SwPosition *const pMark,
    RangePosition const eRange)
{
    const uno::Reference<text::XText> xParentText(
            ::sw::CreateParentXText(rDoc, rPos));
    const auto pNewCursor(rDoc.CreateUnoCursor(rPos));
    if(pMark)
    {
        pNewCursor->SetMark();
        *pNewCursor->GetMark() = *pMark;
    }
    const bool isCell( dynamic_cast<SwXCell*>(xParentText.get()) );
    return new SwXTextRange(*pNewCursor, xParentText,
            eRange, isCell);
}

namespace sw {

css::uno::Reference< SwXText >
CreateParentXText(SwDoc & rDoc, const SwPosition& rPos)
{
    css::uno::Reference< SwXText > xParentText;
    SwStartNode* pSttNode = rPos.GetNode().StartOfSectionNode();
    while(pSttNode && pSttNode->IsSectionNode())
    {
        pSttNode = pSttNode->StartOfSectionNode();
    }
    SwStartNodeType eType = pSttNode ? pSttNode->GetStartNodeType() : SwNormalStartNode;
    switch(eType)
    {
        case SwTableBoxStartNode:
        {
            SwTableNode const*const pTableNode = pSttNode->FindTableNode();
            SwFrameFormat *const pTableFormat =
                pTableNode->GetTable().GetFrameFormat();
            SwTableBox *const  pBox = pSttNode->GetTableBox();

            xParentText = pBox
                ? SwXCell::CreateXCell( pTableFormat, pBox )
                : new SwXCell( pTableFormat, *pSttNode );
        }
        break;
        case SwFlyStartNode:
        {
            SwFrameFormat *const pFormat = pSttNode->GetFlyFormat();
            if (nullptr != pFormat)
            {
                xParentText = SwXTextFrame::CreateXTextFrame(rDoc, pFormat);
            }
        }
        break;
        case SwHeaderStartNode:
        case SwFooterStartNode:
        {
            const bool bHeader = (SwHeaderStartNode == eType);
            const size_t nPDescCount = rDoc.GetPageDescCnt();
            for(size_t i = 0; i < nPDescCount; i++)
            {
                const SwPageDesc& rDesc = rDoc.GetPageDesc( i );

                const SwFrameFormat* pFrameFormatMaster = &rDesc.GetMaster();
                const SwFrameFormat* pFrameFormatLeft = &rDesc.GetLeft();
                const SwFrameFormat* pFrameFormatFirstMaster = &rDesc.GetFirstMaster();
                const SwFrameFormat* pFrameFormatFirstLeft = &rDesc.GetFirstLeft();

                SwFrameFormat* pHeadFootFormat = nullptr;
                if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatMaster,
                          pHeadFootFormat))
                {
                    if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatLeft,
                             pHeadFootFormat))
                    {
                        if (!lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatFirstMaster,
                             pHeadFootFormat))
                        {
                             lcl_IsStartNodeInFormat(bHeader, pSttNode, pFrameFormatFirstLeft, pHeadFootFormat);
                        }
                    }
                }

                if (pHeadFootFormat)
                {
                    xParentText = SwXHeadFootText::CreateXHeadFootText(
                            *pHeadFootFormat, bHeader);
                }
            }
        }
        break;
        case SwFootnoteStartNode:
        {
            const size_t nFootnoteCnt = rDoc.GetFootnoteIdxs().size();
            for (size_t n = 0; n < nFootnoteCnt; ++n )
            {
                const SwTextFootnote* pTextFootnote = rDoc.GetFootnoteIdxs()[ n ];
                const SwFormatFootnote& rFormatFootnote = pTextFootnote->GetFootnote();
                assert(pTextFootnote == rFormatFootnote.GetTextFootnote());
                assert(&pTextFootnote->GetStartNode()->GetNode() == pTextFootnote->GetStartNode()->GetNode().
                                    FindStartNodeByType(SwFootnoteStartNode));

                if (pSttNode == &pTextFootnote->GetStartNode()->GetNode())
                {
                    xParentText = SwXFootnote::CreateXFootnote(rDoc,
                            &const_cast<SwFormatFootnote&>(rFormatFootnote));
                    break;
                }
            }
        }
        break;
        default:
        {
            if (SwDocShell *const pDocSh = rDoc.GetDocShell())
            {
                // then it is the body text
                const rtl::Reference<SwXTextDocument> xModel = pDocSh->GetBaseModel();
                xParentText = xModel->getBodyText();
            }
        }
    }
    OSL_ENSURE(xParentText.is(), "no parent text?");
    return xParentText;
}

} // namespace sw

uno::Reference< container::XEnumeration > SAL_CALL
SwXTextRange::createContentEnumeration(const OUString& rServiceName)
{
    SolarMutexGuard g;

    if ( rServiceName != "com.sun.star.text.TextContent" )
    {
        throw uno::RuntimeException(u"unsupported service"_ustr);
    }

    if (!m_pMark)
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent());
    const auto pNewCursor(m_rDoc.CreateUnoCursor(aPos));
    if (!GetPositions(*pNewCursor))
    {
        throw uno::RuntimeException(u"range has no positions"_ustr);
    }

    return SwXParaFrameEnumeration::Create(*pNewCursor, PARAFRAME_PORTION_TEXTRANGE);
}

uno::Reference< container::XEnumeration > SAL_CALL
SwXTextRange::createEnumeration()
{
    SolarMutexGuard g;

    if (!m_pMark)
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    const SwPosition aPos(GetDoc().GetNodes().GetEndOfContent());
    auto pNewCursor(m_rDoc.CreateUnoCursor(aPos));
    if (!GetPositions(*pNewCursor))
    {
        throw uno::RuntimeException(u"range has no positions"_ustr);
    }
    if (!m_xParentText.is())
    {
        getText();
    }

    const CursorType eSetType = m_isRangeInCell
            ? CursorType::SelectionInTable : CursorType::Selection;
    return SwXParagraphEnumeration::Create(m_xParentText, pNewCursor, eSetType);
}

uno::Type SAL_CALL SwXTextRange::getElementType()
{
    return cppu::UnoType<text::XTextRange>::get();
}

sal_Bool SAL_CALL SwXTextRange::hasElements()
{
    return true;
}

uno::Sequence< OUString > SAL_CALL
SwXTextRange::getAvailableServiceNames()
{
    uno::Sequence<OUString> aRet { u"com.sun.star.text.TextContent"_ustr };
    return aRet;
}

uno::Reference< beans::XPropertySetInfo > SAL_CALL
SwXTextRange::getPropertySetInfo()
{
    SolarMutexGuard aGuard;

    static uno::Reference< beans::XPropertySetInfo > xRef =
        m_rPropSet.getPropertySetInfo();
    return xRef;
}

void SAL_CALL
SwXTextRange::setPropertyValue(
        const OUString& rPropertyName, const uno::Any& rValue)
{
    SolarMutexGuard aGuard;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    SwUnoCursorHelper::SetPropertyValue(aPaM, m_rPropSet,
            rPropertyName, rValue);
}

uno::Any SAL_CALL
SwXTextRange::getPropertyValue(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    return SwUnoCursorHelper::GetPropertyValue(aPaM, m_rPropSet,
            rPropertyName);
}

void SAL_CALL
SwXTextRange::addPropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextRange::addPropertyChangeListener(): not implemented");
}

void SAL_CALL
SwXTextRange::removePropertyChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextRange::removePropertyChangeListener(): not implemented");
}

void SAL_CALL
SwXTextRange::addVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextRange::addVetoableChangeListener(): not implemented");
}

void SAL_CALL
SwXTextRange::removeVetoableChangeListener(
        const OUString& /*rPropertyName*/,
        const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/)
{
    OSL_FAIL("SwXTextRange::removeVetoableChangeListener(): not implemented");
}

beans::PropertyState SAL_CALL
SwXTextRange::getPropertyState(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    return SwUnoCursorHelper::GetPropertyState(aPaM, m_rPropSet,
            rPropertyName);
}

uno::Sequence< beans::PropertyState > SAL_CALL
SwXTextRange::getPropertyStates(const uno::Sequence< OUString >& rPropertyName)
{
    SolarMutexGuard g;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    return SwUnoCursorHelper::GetPropertyStates(aPaM, m_rPropSet,
            rPropertyName);
}

void SAL_CALL SwXTextRange::setPropertyToDefault(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    SwUnoCursorHelper::SetPropertyToDefault(aPaM, m_rPropSet,
            rPropertyName);
}

uno::Any SAL_CALL
SwXTextRange::getPropertyDefault(const OUString& rPropertyName)
{
    SolarMutexGuard aGuard;

    if (!m_pMark && (m_eRangePosition != RANGE_IS_SECTION || !m_pTableOrSectionFormat))
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    GetPositions(aPaM, ::sw::TextRangeMode::AllowNonTextNode);
    return SwUnoCursorHelper::GetPropertyDefault(aPaM, m_rPropSet,
            rPropertyName);
}

void SAL_CALL
SwXTextRange::makeRedline(
    const OUString& rRedlineType,
    const uno::Sequence< beans::PropertyValue >& rRedlineProperties )
{
    SolarMutexGuard aGuard;

    if (!m_pMark)
    {
        throw uno::RuntimeException(u"range has no mark (table?)"_ustr);
    }
    SwPaM aPaM(GetDoc().GetNodes());
    SwXTextRange::GetPositions(aPaM);
    SwUnoCursorHelper::makeRedline( aPaM, rRedlineType, rRedlineProperties );
}

namespace {

struct SwXTextRangesImpl final : public SwXTextRanges
{

    // XServiceInfo
    virtual OUString SAL_CALL getImplementationName() override
        { return u"SwXTextRanges"_ustr; };
    virtual sal_Bool SAL_CALL supportsService( const OUString& rServiceName) override
        { return cppu::supportsService(this, rServiceName); };
    virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
        { return { u"com.sun.star.text.TextRanges"_ustr }; };

    // XElementAccess
    virtual css::uno::Type SAL_CALL getElementType() override
        { return cppu::UnoType<text::XTextRange>::get(); };
    virtual sal_Bool SAL_CALL hasElements() override
        { return getCount() > 0; };
    // XIndexAccess
    virtual sal_Int32 SAL_CALL getCount() override;
    virtual css::uno::Any SAL_CALL getByIndex(sal_Int32 nIndex) override;

    explicit SwXTextRangesImpl(SwPaM *const pPaM)
    {
        if (pPaM)
        {
            m_pUnoCursor.reset(pPaM->GetDoc().CreateUnoCursor(*pPaM->GetPoint()));
            ::sw::DeepCopyPaM(*pPaM, *GetCursor());
        }
        MakeRanges();
    }
    virtual void SAL_CALL release() noexcept override
    {
        SolarMutexGuard g;
        OWeakObject::release();
    }
    virtual SwUnoCursor* GetCursor() override
        { return &(*m_pUnoCursor); };
    void MakeRanges();
    std::vector< rtl::Reference<SwXTextRange> > m_Ranges;
    sw::UnoCursorPointer m_pUnoCursor;
};

}

void SwXTextRangesImpl::MakeRanges()
{
    if (!GetCursor())
        return;

    for(SwPaM& rTmpCursor : GetCursor()->GetRingContainer())
    {
        const rtl::Reference<SwXTextRange> xRange(
                SwXTextRange::CreateXTextRange(
                    rTmpCursor.GetDoc(),
                    *rTmpCursor.GetPoint(), rTmpCursor.GetMark()));
        if (xRange.is())
        {
            m_Ranges.push_back(xRange);
        }
    }
}

rtl::Reference<SwXTextRanges> SwXTextRanges::Create(SwPaM *const pPaM)
    { return new SwXTextRangesImpl(pPaM); }

/*
 *  Text positions
 * Up to the first access to a text position, only a SwCursor is stored.
 * Afterwards, an array with uno::Reference<XTextPosition> will be created.
 */

sal_Int32 SAL_CALL SwXTextRangesImpl::getCount()
{
    SolarMutexGuard aGuard;
    return static_cast<sal_Int32>(m_Ranges.size());
}

uno::Any SAL_CALL SwXTextRangesImpl::getByIndex(sal_Int32 nIndex)
{
    SolarMutexGuard aGuard;
    if ((nIndex < 0) || (o3tl::make_unsigned(nIndex) >= m_Ranges.size()))
        throw lang::IndexOutOfBoundsException();
    uno::Any ret(uno::Reference<text::XTextRange>(m_Ranges.at(nIndex)));
    return ret;
}

void SwUnoCursorHelper::SetString(SwCursor & rCursor, std::u16string_view aString)
{
    // Start/EndAction
    SwDoc& rDoc = rCursor.GetDoc();
    UnoActionContext aAction(&rDoc);
    rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr);
    if (rCursor.HasMark())
    {
        rDoc.getIDocumentContentOperations().DeleteAndJoin(rCursor);
    }
    if (!aString.empty())
    {
        const bool bSuccess( SwUnoCursorHelper::DocInsertStringSplitCR(
                    rDoc, rCursor, aString, false ) );
        OSL_ENSURE( bSuccess, "DocInsertStringSplitCR" );
        SwUnoCursorHelper::SelectPam(rCursor, true);
        rCursor.Left(aString.size());
    }
    rDoc.GetIDocumentUndoRedo().EndUndo(SwUndoId::INSERT, nullptr);
}

namespace {

struct SwXParaFrameEnumerationImpl final : public SwXParaFrameEnumeration
{
    // XServiceInfo
    virtual OUString SAL_CALL getImplementationName() override
        { return u"SwXParaFrameEnumeration"_ustr; };
    virtual sal_Bool SAL_CALL supportsService(const OUString& rServiceName) override
        { return cppu::supportsService(this, rServiceName); };
    virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override
        { return {u"com.sun.star.util.ContentEnumeration"_ustr}; };

    // XEnumeration
    virtual sal_Bool SAL_CALL hasMoreElements() override;
    virtual css::uno::Any SAL_CALL nextElement() override;

    SwXParaFrameEnumerationImpl(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat);
    virtual void SAL_CALL release() noexcept override
    {
        SolarMutexGuard g;
        OWeakObject::release();
    }
    SwUnoCursor& GetCursor()
        { return *m_pUnoCursor; }
    void PurgeFrameClients()
    {
        if(!m_pUnoCursor)
        {
            m_vFrames.clear();
            m_xNextObject = nullptr;
        }
        else
        {
            // removing orphaned Clients
            std::erase_if(m_vFrames,
                    [] (std::unique_ptr<sw::FrameClient>& rEntry) -> bool { return !rEntry->GetRegisteredIn(); });
        }
    }
    void FillFrame();
    bool CreateNextObject();
    uno::Reference< text::XTextContent > m_xNextObject;
    std::deque< std::unique_ptr<sw::FrameClient> > m_vFrames;
    ::sw::UnoCursorPointer m_pUnoCursor;
};

}

rtl::Reference<SwXParaFrameEnumeration> SwXParaFrameEnumeration::Create(const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode, SwFrameFormat* const pFormat)
    { return new SwXParaFrameEnumerationImpl(rPaM, eParaFrameMode, pFormat); }

SwXParaFrameEnumerationImpl::SwXParaFrameEnumerationImpl(
        const SwPaM& rPaM, const enum ParaFrameMode eParaFrameMode,
        SwFrameFormat* const pFormat)
    : m_pUnoCursor(rPaM.GetDoc().CreateUnoCursor(*rPaM.GetPoint()))
{
    if (rPaM.HasMark())
    {
        GetCursor().SetMark();
        *GetCursor().GetMark() = *rPaM.GetMark();
    }
    if (PARAFRAME_PORTION_PARAGRAPH == eParaFrameMode)
    {
        FrameClientSortList_t vFrames;
        ::CollectFrameAtNode(rPaM.GetPoint()->GetNode(), vFrames, false);
        std::transform(vFrames.begin(), vFrames.end(),
            std::back_inserter(m_vFrames),
            [] (FrameClientSortListEntry& rEntry) { return std::move(rEntry.pFrameClient); });
    }
    else if (pFormat)
    {
        m_vFrames.push_back(std::make_unique<sw::FrameClient>(pFormat));
    }
    else if ((PARAFRAME_PORTION_CHAR == eParaFrameMode) ||
             (PARAFRAME_PORTION_TEXTRANGE == eParaFrameMode))
    {
        if (PARAFRAME_PORTION_TEXTRANGE == eParaFrameMode)
        {
            //get all frames that are bound at paragraph or at character
            for(const SwPosFlyFrame& rFlyFrame : rPaM.GetDoc().GetAllFlyFormats(&GetCursor(), false, true))
            {
                const auto pFrameFormat = const_cast<SwFrameFormat*>(&rFlyFrame.GetFormat());
                m_vFrames.push_back(std::make_unique<sw::FrameClient>(pFrameFormat));
            }
        }
        FillFrame();
    }
}

// Search for a FLYCNT text attribute at the cursor point and fill the frame
// into the array
void SwXParaFrameEnumerationImpl::FillFrame()
{
    if(!m_pUnoCursor->GetPointNode().IsTextNode())
        return;
    // search for objects at the cursor - anchored at/as char
    const auto pTextAttr = m_pUnoCursor->GetPointNode().GetTextNode()->GetTextAttrForCharAt(
            m_pUnoCursor->GetPoint()->GetContentIndex(), RES_TXTATR_FLYCNT);
    if(!pTextAttr)
        return;
    const SwFormatFlyCnt& rFlyCnt = pTextAttr->GetFlyCnt();
    SwFrameFormat* const pFrameFormat = rFlyCnt.GetFrameFormat();
    m_vFrames.push_back(std::make_unique<sw::FrameClient>(pFrameFormat));
}

bool SwXParaFrameEnumerationImpl::CreateNextObject()
{
    if (m_vFrames.empty())
        return false;

    m_xNextObject.set(FrameClientToXTextContent(m_vFrames.front().get()));
    m_vFrames.pop_front();
    return m_xNextObject.is();
}

uno::Reference<text::XTextContent> FrameClientToXTextContent(sw::FrameClient* pClient)
{
    assert(pClient);

    uno::Reference<text::XTextContent> xRet;
    SwFrameFormat* const pFormat = static_cast<SwFrameFormat*>(pClient->GetRegisteredIn());
    // the format should be valid here, otherwise the client
    // would have been removed by PurgeFrameClients
    // check for a shape first
    if(pFormat->Which() == RES_DRAWFRMFMT)
    {
        SdrObject* pObject(nullptr);
        pFormat->CallSwClientNotify(sw::FindSdrObjectHint(pObject));
        if(pObject)
            xRet.set(pObject->getUnoShape(), uno::UNO_QUERY);
    }
    else
    {
        const SwNodeIndex* pIdx = pFormat->GetContent().GetContentIdx();
        assert(pIdx && "where is the index?");
        SwNode const* const pNd = pIdx->GetNodes()[pIdx->GetIndex() + 1];

        if (!pNd->IsNoTextNode())
        {
            xRet = static_cast<SwXFrame*>(SwXTextFrame::CreateXTextFrame(
                        pFormat->GetDoc(), pFormat).get());
        }
        else if (pNd->IsGrfNode())
        {
            xRet.set(SwXTextGraphicObject::CreateXTextGraphicObject(
                        pFormat->GetDoc(), pFormat));
        }
        else
        {
            assert(pNd->IsOLENode());
            xRet.set(SwXTextEmbeddedObject::CreateXTextEmbeddedObject(
                        pFormat->GetDoc(), pFormat));
        }
    }
    return xRet;
}

sal_Bool SAL_CALL
SwXParaFrameEnumerationImpl::hasMoreElements()
{
    SolarMutexGuard aGuard;
    PurgeFrameClients();
    return m_xNextObject.is() || CreateNextObject();
}

uno::Any SAL_CALL SwXParaFrameEnumerationImpl::nextElement()
{
    SolarMutexGuard aGuard;
    PurgeFrameClients();
    if (!m_xNextObject.is() && !m_vFrames.empty())
        CreateNextObject();
    if (!m_xNextObject.is())
        throw container::NoSuchElementException();
    uno::Any aRet;
    aRet <<= m_xNextObject;
    m_xNextObject = nullptr;
    return aRet;
}

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