/* -*- 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 <memory>
#include <sal/config.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>

#include <cstddef>

#include <hintids.hxx>
#include <hints.hxx>

#include <svl/cintitem.hxx>
#include <svl/stritem.hxx>
#include <fmtanchr.hxx>
#include <fmtfld.hxx>
#include <redline.hxx>
#include <pam.hxx>
#include <doc.hxx>
#include <IDocumentFieldsAccess.hxx>
#include <IDocumentRedlineAccess.hxx>
#include <IDocumentLayoutAccess.hxx>
#include <IDocumentMarkAccess.hxx>
#include <ndtxt.hxx>
#include <fldbas.hxx>
#include <docufld.hxx>
#include <txtfld.hxx>
#include <tox.hxx>
#include <expfld.hxx>
#include <bookmark.hxx>
#include <fltshell.hxx>
#include <rdfhelper.hxx>
#include <utility>

using namespace com::sun::star;

static SwContentNode* GetContentNode(SwDoc& rDoc, SwPosition& rPos, bool bNext)
{
    SwContentNode * pCNd = rPos.GetNode().GetContentNode();
    if(!pCNd && nullptr == (pCNd = bNext ? rDoc.GetNodes().GoNext(&rPos)
                                     : SwNodes::GoPrevious(&rPos)))
    {
        pCNd = bNext ? SwNodes::GoPrevious(&rPos)
                     : rDoc.GetNodes().GoNext(&rPos);
        OSL_ENSURE(pCNd, "no ContentNode found");
    }
    return pCNd;
}

static OUString lcl_getTypePath(OUString& rType)
{
    OUString aRet;
    if (rType.startsWith("urn:bails"))
    {
        rType = "urn:bails";
        aRet = "tscp/bails.rdf";
    }
    return aRet;
}

// Stack entry for all text attributes
SwFltStackEntry::SwFltStackEntry(const SwPosition& rStartPos, std::unique_ptr<SfxPoolItem> pHt)
    : m_aMkPos(rStartPos)
    , m_aPtPos(rStartPos)
    , m_pAttr( std::move(pHt) )
    , m_isAnnotationOnEnd(false)
{
    m_bOld    = false;    // used for marking Attributes *before* skipping field results
    m_bOpen = true;       // lock the attribute --> may first
    m_bConsumedByField = false;
}

SwFltStackEntry::~SwFltStackEntry()
{
    // Although attribute got passed as pointer, it gets deleted here
}

void SwFltStackEntry::SetEndPos(const SwPosition& rEndPos)
{
    // Release attribute and keep track of end
    // Everything with sal_uInt16s, lest the inserting of new text at
    // the cursor position moves the attribute's range
    // That's not the desired behavior!
    m_bOpen = false;                  // release and remember END
    m_aPtPos.FromSwPosition(rEndPos);
}

bool SwFltStackEntry::MakeRegion(SwDoc& rDoc, SwPaM& rRegion, RegionMode const eCheck,
    const SwFltPosition &rMkPos, const SwFltPosition &rPtPos,
    sal_uInt16 nWhich)
{
    // does this range actually contain something?
    // empty range is allowed if at start of empty paragraph
    // fields are special: never have range, so leave them
    SwNodeOffset nMk = rMkPos.m_nNode.GetIndex() + 1;
    const SwNodes& rMkNodes = rMkPos.m_nNode.GetNodes();
    if (nMk >= rMkNodes.Count())
        return false;
    SwContentNode *const pContentNode(rMkNodes[nMk]->GetContentNode());
    if (rMkPos == rPtPos &&
        ((0 != rPtPos.m_nContent) || (pContentNode && (0 != pContentNode->Len())))
        && ( RES_TXTATR_FIELD != nWhich
             && RES_TXTATR_ANNOTATION != nWhich
             && RES_TXTATR_INPUTFIELD != nWhich ))
    {
        return false;
    }
    // The content indices always apply to the node!
    rRegion.GetPoint()->Assign( rMkPos.m_nNode.GetIndex() + 1 );
    SwContentNode* pCNd = GetContentNode(rDoc, *rRegion.GetPoint(), true);

    SAL_WARN_IF(pCNd->Len() < rMkPos.m_nContent, "sw.ww8",
        "invalid content index " << rMkPos.m_nContent << " but text node has only " << pCNd->Len());
    rRegion.GetPoint()->SetContent( std::min<sal_Int32>(rMkPos.m_nContent, pCNd->Len()) );
    rRegion.SetMark();
    if (rMkPos.m_nNode != rPtPos.m_nNode)
    {
        SwNodeOffset n = rPtPos.m_nNode.GetIndex() + 1;
        SwNodes& rNodes = rRegion.GetPoint()->GetNodes();
        if (n >= rNodes.Count())
            return false;
        rRegion.GetPoint()->Assign(n);
        pCNd = GetContentNode(rDoc, *rRegion.GetPoint(), false);
    }
    SAL_WARN_IF(pCNd->Len() < rPtPos.m_nContent, "sw.ww8",
        "invalid content index " << rPtPos.m_nContent << " but text node has only " << pCNd->Len());
    rRegion.GetPoint()->SetContent( std::min<sal_Int32>(rPtPos.m_nContent, pCNd->Len()) );
    OSL_ENSURE( CheckNodesRange( rRegion.Start()->GetNode(),
                             rRegion.End()->GetNode(), true ),
             "attribute or similar crosses section-boundaries" );
    bool bRet = true;
    if (eCheck & RegionMode::CheckNodes)
    {
        bRet &= CheckNodesRange(rRegion.Start()->GetNode(),
                                rRegion.End()->GetNode(), true);
    }
    if (eCheck & RegionMode::CheckFieldmark)
    {
        bRet &= !sw::mark::IsFieldmarkOverlap(rRegion);
    }
    return bRet;
}

bool SwFltStackEntry::MakeRegion(SwDoc& rDoc, SwPaM& rRegion, RegionMode eCheck) const
{
    return MakeRegion(rDoc, rRegion, eCheck, m_aMkPos, m_aPtPos, m_pAttr->Which());
}

SwFltControlStack::SwFltControlStack(SwDoc& rDo, sal_uLong nFieldFl)
    : m_nFieldFlags(nFieldFl), m_rDoc(rDo), m_bIsEndStack(false)
{
}

SwFltControlStack::~SwFltControlStack()
{
    OSL_ENSURE(m_Entries.empty(), "There are still Attributes on the stack");
}

// MoveAttrs() is meant to address the following problem:
// When a field like "set variable" is set through the stack, the text
// is shifted by one \xff character, which makes all subsequent
// attribute positions invalid.
// After setting the attribute in the doc, MoveAttrs() needs to be
// called in order to push all attribute positions to the right in the
// same paragraph further out by one character.
void SwFltControlStack::MoveAttrs(const SwPosition& rPos, MoveAttrsMode eMode)
{
    SwNodeOffset nPosNd = rPos.GetNodeIndex();
    sal_uInt16 nPosCt = rPos.GetContentIndex() - 1;

    for (size_t i = 0, nCnt = m_Entries.size(); i < nCnt; ++i)
    {
        SwFltStackEntry& rEntry = *m_Entries[i];
        if (
            (rEntry.m_aMkPos.m_nNode.GetIndex()+1 == nPosNd) &&
            (rEntry.m_aMkPos.m_nContent >= nPosCt)
           )
        {
            rEntry.m_aMkPos.m_nContent++;
            OSL_ENSURE( rEntry.m_aMkPos.m_nContent
                <= m_rDoc.GetNodes()[nPosNd]->GetContentNode()->Len(),
                    "Attribute ends after end of line" );
        }
        if (
            (rEntry.m_aPtPos.m_nNode.GetIndex()+1 == nPosNd) &&
            (rEntry.m_aPtPos.m_nContent >= nPosCt)
           )
        {
            if (    !rEntry.m_isAnnotationOnEnd
                ||  rEntry.m_aPtPos.m_nContent > nPosCt)
            {
                assert(!(rEntry.m_isAnnotationOnEnd && rEntry.m_aPtPos.m_nContent > nPosCt));
                if (    eMode == MoveAttrsMode::POSTIT_INSERTED
                    &&  rEntry.m_aPtPos.m_nContent == nPosCt
                    &&  rEntry.m_pAttr->Which() == RES_FLTR_ANNOTATIONMARK)
                {
                    rEntry.m_isAnnotationOnEnd = true;
                    eMode = MoveAttrsMode::DEFAULT; // only set 1 flag
                }
                rEntry.m_aPtPos.m_nContent++;
                OSL_ENSURE( rEntry.m_aPtPos.m_nContent
                    <= m_rDoc.GetNodes()[nPosNd]->GetContentNode()->Len(),
                        "Attribute ends after end of line" );
            }
        }
    }
}

void SwFltControlStack::MarkAllAttrsOld()
{
    size_t nCnt = m_Entries.size();
    for (size_t i=0; i < nCnt; ++i)
        m_Entries[i]->m_bOld = true;
}

namespace
{
    bool couldExtendEntry(const SwFltStackEntry *pExtendCandidate,
        const SfxPoolItem& rAttr)
    {
        return (pExtendCandidate &&
                !pExtendCandidate->m_bConsumedByField &&
                //if we bring character attributes into the fold we need to both
                //a) consider RES_CHRATR_FONTSIZE and RES_CHRATR_FONT wrt Word's CJK/CTL variants
                //b) consider crossing table cell boundaries (tdf#102334)
                isPARATR_LIST(rAttr.Which()) &&
                *(pExtendCandidate->m_pAttr) == rAttr);
    }
}

void SwFltControlStack::NewAttr(const SwPosition& rPos, const SfxPoolItem& rAttr)
{
    sal_uInt16 nWhich = rAttr.Which();
    // Set end position of potentially equal attributes on stack, so
    // as to avoid having them accumulate
    SwFltStackEntry *pExtendCandidate = SetAttr(rPos, nWhich);
    if (couldExtendEntry(pExtendCandidate, rAttr))
    {
        //Here we optimize by seeing if there is an attribute uncommitted
        //to the document which

        //a) has the same value as this attribute
        //b) is already open, or ends at the same place as where we're starting
        //from. If so we merge it with this one and elide adding another
        //to the stack
        pExtendCandidate->SetEndPos(rPos);
        pExtendCandidate->m_bOpen=true;
    }
    else
    {
        SwFltStackEntry *pTmp = new SwFltStackEntry(rPos, std::unique_ptr<SfxPoolItem>(rAttr.Clone()) );
        m_Entries.push_back(std::unique_ptr<SwFltStackEntry>(pTmp));
    }
}

void SwFltControlStack::DeleteAndDestroy(Entries::size_type nCnt)
{
    OSL_ENSURE(nCnt < m_Entries.size(), "Out of range!");
    if (nCnt < m_Entries.size())
    {
        auto aElement = m_Entries.begin() + nCnt;
        m_Entries.erase(aElement);
    }
}

// SwFltControlStack::StealAttr() removes attributes of the given type
// from the stack. Allowed as nAttrId: 0 meaning any, or a specific
// type.  This makes them disappear from the doc structure. Only
// attributes from the same paragraph as rPos are removed. Used for
// graphic apos -> images.
void SwFltControlStack::StealAttr(const SwNode& rNode)
{
    size_t nCnt = m_Entries.size();

    while (nCnt)
    {
        nCnt --;
        SwFltStackEntry& rEntry = *m_Entries[nCnt];
        if (rEntry.m_aPtPos.m_nNode.GetIndex()+1 == rNode.GetIndex())
        {
            DeleteAndDestroy(nCnt);     // delete from the stack
        }
    }
}

// SwFltControlStack::KillUnlockedAttr() removes all attributes from
// the stack, which are assigned to an rPos. This makes them disappear
// from the doc structure. Used in WW import for ignoring attributes
// assigned to the 0x0c section break symbol.
void SwFltControlStack::KillUnlockedAttrs(const SwPosition& rPos)
{
    SwFltPosition aFltPos(rPos);

    size_t nCnt = m_Entries.size();
    while( nCnt )
    {
        nCnt --;
        SwFltStackEntry& rEntry = *m_Entries[nCnt];
        if(    !rEntry.m_bOld
            && !rEntry.m_bOpen
            && (rEntry.m_aMkPos == aFltPos)
            && (rEntry.m_aPtPos == aFltPos))
        {
            DeleteAndDestroy( nCnt ); // remove from stack
        }
    }
}

// Unlock all locked attributes and move to the end, all others will
// be applied to the document and removed from the stack.
// Returns if there were any selected attributes on the stack
SwFltStackEntry* SwFltControlStack::SetAttr(const SwPosition& rPos,
    sal_uInt16 nAttrId, bool bTstEnd, tools::Long nHand,
    bool consumedByField)
{
    SwFltStackEntry *pRet = nullptr;

    SwFltPosition aFltPos(rPos);

    OSL_ENSURE(!nAttrId ||
        (POOLATTR_BEGIN <= nAttrId && POOLATTR_END > nAttrId) ||
        (RES_FLTRATTR_BEGIN <= nAttrId && RES_FLTRATTR_END > nAttrId),
        "Wrong id for attribute");

    auto aI = m_Entries.begin();
    while (aI != m_Entries.end())
    {
        bool bLastEntry = aI == m_Entries.end() - 1;

        SwFltStackEntry& rEntry = **aI;
        if (rEntry.m_bOpen)
        {
            // set end of attribute
            bool bF = false;
            if (!nAttrId )
            {
                bF = true;
            }
            else if (nAttrId == rEntry.m_pAttr->Which())
            {
                if( nAttrId != RES_FLTR_BOOKMARK && nAttrId != RES_FLTR_ANNOTATIONMARK && nAttrId != RES_FLTR_RDFMARK )
                {
                    // query handle
                    bF = true;
                }
                else if (nAttrId == RES_FLTR_BOOKMARK && nHand == static_cast<SwFltBookmark*>(rEntry.m_pAttr.get())->GetHandle())
                {
                    bF = true;
                }
                else if (nAttrId == RES_FLTR_ANNOTATIONMARK && nHand == static_cast<CntUInt16Item*>(rEntry.m_pAttr.get())->GetValue())
                {
                    bF = true;
                }
                else if (nAttrId == RES_FLTR_RDFMARK && nHand == static_cast<SwFltRDFMark*>(rEntry.m_pAttr.get())->GetHandle())
                {
                    bF = true;
                }
            }
            if (bF)
            {
                rEntry.m_bConsumedByField = consumedByField;
                rEntry.SetEndPos(rPos);
                if (bLastEntry && nAttrId == rEntry.m_pAttr->Which())
                {
                    //potential candidate for merging with an identical
                    //property beginning at rPos
                    pRet = &rEntry;
                }
            }
            ++aI;
            continue;
        }

        // if the end position is equal to the cursor position, then
        // refrain from applying it; there needs to be following text,
        // except at the very end. (attribute expansion !!)
        // Never apply end stack except at document ending
        if (bTstEnd)
        {
            if (m_bIsEndStack)
            {
                ++aI;
                continue;
            }

            //defer inserting this attribute into the document until
            //we advance to the next node, or finish processing the document
            if (rEntry.m_aPtPos.m_nNode.GetIndex() == aFltPos.m_nNode.GetIndex())
            {
                if (bLastEntry && nAttrId == rEntry.m_pAttr->Which() &&
                    rEntry.m_aPtPos.m_nContent == aFltPos.m_nContent)
                {
                    //potential candidate for merging with an identical
                    //property beginning at rPos
                    pRet = &rEntry;
                }

                ++aI;
                continue;
            }
        }
        SetAttrInDoc(rPos, rEntry);
        aI = m_Entries.erase(aI);
    }

    return pRet;
}

static bool MakePoint(const SwFltStackEntry& rEntry, SwDoc& rDoc,
    SwPaM& rRegion)
{
    // the anchor is the Pam's Point. It's modified when inserting
    // text, etc.; therefore it is kept on the stack. Only the
    // attribute's format needs to be set.
    rRegion.DeleteMark();

    SwNodeOffset nMk = rEntry.m_aMkPos.m_nNode.GetIndex() + 1;
    const SwNodes& rMkNodes = rEntry.m_aMkPos.m_nNode.GetNodes();
    if (nMk >= rMkNodes.Count())
        return false;

    rRegion.GetPoint()->Assign(nMk);
    GetContentNode(rDoc, *rRegion.GetPoint(), true);
    rRegion.GetPoint()->SetContent(rEntry.m_aMkPos.m_nContent);
    return true;
}

// MakeBookRegionOrPoint() behaves like MakeRegionOrPoint, except that
// it adheres to certain restrictions on bookmarks in tables (cannot
// span more than one cell)
static bool MakeBookRegionOrPoint(const SwFltStackEntry& rEntry, SwDoc& rDoc,
                    SwPaM& rRegion )
{
    if (rEntry.MakeRegion(rDoc, rRegion, SwFltStackEntry::RegionMode::CheckNodes))
    {
        if (rRegion.GetPoint()->GetNode().FindTableBoxStartNode()
              != rRegion.GetMark()->GetNode().FindTableBoxStartNode())
        {
            rRegion.Exchange();         // invalid range
            rRegion.DeleteMark();       // -> both to mark
        }
        return true;
    }
    return MakePoint(rEntry, rDoc, rRegion);
}

// IterateNumrulePiece() looks for the first range valid for Numrules
// between rTmpStart and rEnd.

// rNds denotes the doc nodes
// rEnd denotes the range end,
// rTmpStart is an in/out parameter: in: start of range to be searched,
//                                   out: start of valid range
// rTmpEnd is an out parameter
// Returns true for valid range
static bool IterateNumrulePiece( const SwPosition& rEnd,
                                SwNodeIndex& rTmpStart, SwNodeIndex& rTmpEnd )
{
    while( ( rTmpStart <= rEnd.GetNode() )
           && !( rTmpStart.GetNode().IsTextNode() ) )    // look for valid start
        ++rTmpStart;

    rTmpEnd = rTmpStart;
    while( ( rTmpEnd <= rEnd.GetNode() )
           && ( rTmpEnd.GetNode().IsTextNode() ) )       // look for valid end + 1
        ++rTmpEnd;

    --rTmpEnd;                                      // valid end

    return rTmpStart <= rTmpEnd;                    // valid ?
}

void SwFltControlStack::SetAttrInDoc(const SwPosition& rTmpPos,
    SwFltStackEntry& rEntry)
{
    SwPaM aRegion( rTmpPos );

    switch(rEntry.m_pAttr->Which())
    {
    case RES_FLTR_ANCHOR:
        {
            SwFrameFormat* pFormat = static_cast<SwFltAnchor*>(rEntry.m_pAttr.get())->GetFrameFormat();
            if (pFormat != nullptr)
            {
                MakePoint(rEntry, m_rDoc, aRegion);
                SwFormatAnchor aAnchor(pFormat->GetAnchor());
                aAnchor.SetAnchor(aRegion.GetPoint());
                pFormat->SetFormatAttr(aAnchor);
                // So the frames will be created when inserting into
                // existing doc (after setting the anchor!):
                if (m_rDoc.getIDocumentLayoutAccess().GetCurrentViewShell()
                   && (RndStdIds::FLY_AT_PARA == pFormat->GetAnchor().GetAnchorId()))
                {
                    pFormat->MakeFrames();
                }
            }
        }
        break;

    case RES_TXTATR_FIELD:
    case RES_TXTATR_ANNOTATION:
    case RES_TXTATR_INPUTFIELD:
        break;

    case RES_TXTATR_TOXMARK:
        break;

    case RES_FLTR_NUMRULE:          // insert Numrule
        {
            const OUString& rNumNm = static_cast<SfxStringItem*>(rEntry.m_pAttr.get())->GetValue();
            SwNumRule* pNumRule = m_rDoc.FindNumRulePtr( rNumNm );
            if( pNumRule )
            {
                if (rEntry.MakeRegion(m_rDoc, aRegion, SwFltStackEntry::RegionMode::CheckNodes))
                {
                    SwNodeIndex aTmpStart( aRegion.Start()->GetNode() );
                    SwNodeIndex aTmpEnd( aTmpStart );
                    SwPosition& rRegEndNd = *aRegion.End();
                    while( IterateNumrulePiece( rRegEndNd,
                                                aTmpStart, aTmpEnd ) )
                    {
                        SwPaM aTmpPam( aTmpStart, aTmpEnd );
                        // no start of a new list
                        m_rDoc.SetNumRule( aTmpPam, *pNumRule, false );

                        aTmpStart = aTmpEnd;    // here starts the next range
                        ++aTmpStart;
                    }
                }
                else
                    m_rDoc.DelNumRule( rNumNm );
            }
        }
        break;

    case RES_FLTR_BOOKMARK:
        {
            SwFltBookmark* pB = static_cast<SwFltBookmark*>(rEntry.m_pAttr.get());
            const OUString& rName = static_cast<SwFltBookmark*>(rEntry.m_pAttr.get())->GetName();

            if (IsFlagSet(BOOK_TO_VAR_REF))
            {
                SwFieldType* pFT = m_rDoc.getIDocumentFieldsAccess().GetFieldType(SwFieldIds::SetExp, rName, false);
                if (!pFT)
                {
                    SwSetExpFieldType aS(&m_rDoc, rName, nsSwGetSetExpType::GSE_STRING);
                    pFT = m_rDoc.getIDocumentFieldsAccess().InsertFieldType(aS);
                }
                SwSetExpField aField(static_cast<SwSetExpFieldType*>(pFT), pB->GetValSys());
                aField.SetSubType( nsSwExtendedSubType::SUB_INVISIBLE );
                MakePoint(rEntry, m_rDoc, aRegion);
                m_rDoc.getIDocumentContentOperations().InsertPoolItem(aRegion, SwFormatField(aField));
                MoveAttrs( *(aRegion.GetPoint()) );
            }
            if ( ( !IsFlagSet(HYPO) || IsFlagSet(BOOK_AND_REF) ) &&
                 !rEntry.m_bConsumedByField )
            {
                MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion);
                // #i120879# - create a cross reference heading bookmark if appropriate.
                const IDocumentMarkAccess::MarkType eBookmarkType =
                    ( pB->IsTOCBookmark() &&
                      IDocumentMarkAccess::IsLegalPaMForCrossRefHeadingBookmark( aRegion ) )
                    ? IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK
                    : IDocumentMarkAccess::MarkType::BOOKMARK;
                m_rDoc.getIDocumentMarkAccess()->makeMark(aRegion, rName, eBookmarkType, sw::mark::InsertMode::New);
            }
        }
        break;
    case RES_FLTR_ANNOTATIONMARK:
        {
            if (MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion))
            {
                SwTextNode const*const pTextNode(
                        aRegion.End()->GetNode().GetTextNode());
                SwTextField const*const pField = pTextNode ? pTextNode->GetFieldTextAttrAt(
                        aRegion.End()->GetContentIndex() - 1, ::sw::GetTextAttrMode::Default) : nullptr;
                if (pField)
                {
                    SwPostItField const*const pPostIt(
                        dynamic_cast<SwPostItField const*>(pField->GetFormatField().GetField()));
                    if (pPostIt)
                    {
                        assert(pPostIt->GetName().isEmpty());

                        if (!aRegion.HasMark())
                        {
                            // Annotation range was found in the file, but start/end is the same,
                            // pointing after the postit placeholder (see assert above).
                            // Adjust the start of the range to actually cover the comment, similar
                            // to what the UI and the UNO API does.
                            aRegion.SetMark();
                            aRegion.Start()->AdjustContent(-1);
                        }

                        m_rDoc.getIDocumentMarkAccess()->makeAnnotationMark(aRegion, OUString());
                    }
                    else
                    {
                        SAL_WARN("sw", "RES_FLTR_ANNOTATIONMARK: unexpected field");
                    }
                }
                else
                {
                    SAL_WARN("sw", "RES_FLTR_ANNOTATIONMARK: missing field");
                }
            }
            else
                SAL_WARN("sw", "failed to make book region or point");
        }
        break;
    case RES_FLTR_RDFMARK:
        {
            if (MakeBookRegionOrPoint(rEntry, m_rDoc, aRegion))
            {
                SwFltRDFMark* pMark = static_cast<SwFltRDFMark*>(rEntry.m_pAttr.get());
                if (aRegion.GetPointNode().IsTextNode())
                {
                    SwTextNode& rTextNode = *aRegion.GetPointNode().GetTextNode();

                    for (const std::pair<OUString, OUString>& rAttribute : pMark->GetAttributes())
                    {
                        OUString aTypeNS = rAttribute.first;
                        OUString aMetadataFilePath = lcl_getTypePath(aTypeNS);
                        if (aMetadataFilePath.isEmpty())
                            continue;

                        SwRDFHelper::addTextNodeStatement(aTypeNS, aMetadataFilePath, rTextNode, rAttribute.first, rAttribute.second);
                    }
                }
            }
            else
                SAL_WARN("sw", "failed to make book region or point");
        }
        break;
    case RES_FLTR_TOX:
        {
            MakePoint(rEntry, m_rDoc, aRegion);

            SwPosition* pPoint = aRegion.GetPoint();

            SwFltTOX* pTOXAttr = static_cast<SwFltTOX*>(rEntry.m_pAttr.get());

            // test if on this node there had been a pagebreak BEFORE the
            //     tox attribute was put on the stack
            SfxItemSetFixed<RES_PAGEDESC, RES_BREAK> aBkSet( m_rDoc.GetAttrPool() );
            SwContentNode* pNd = nullptr;
            if( !pTOXAttr->HadBreakItem() || !pTOXAttr->HadPageDescItem() )
            {
                pNd = pPoint->GetNode().GetContentNode();
                if( pNd )
                {
                    const SfxItemSet* pSet = pNd->GetpSwAttrSet();
                    const SfxPoolItem* pItem;
                    if( pSet )
                    {
                        if(    !pTOXAttr->HadBreakItem()
                            && SfxItemState::SET == pSet->GetItemState( RES_BREAK, false, &pItem ) )
                        {
                            aBkSet.Put( *pItem );
                            pNd->ResetAttr( RES_BREAK );
                        }
                        if(    !pTOXAttr->HadPageDescItem()
                            && SfxItemState::SET == pSet->GetItemState( RES_PAGEDESC, false, &pItem ) )
                        {
                            aBkSet.Put( *pItem );
                            pNd->ResetAttr( RES_PAGEDESC );
                        }
                    }
                }
            }

            // set (above saved and removed) the break item at the node following the TOX
            if (pNd && aBkSet.Count())
                pNd->SetAttr(aBkSet);
        }
        break;
    case RES_FLTR_REDLINE:
        {
            if (rEntry.MakeRegion(m_rDoc, aRegion,
                    SwFltStackEntry::RegionMode::CheckNodes|SwFltStackEntry::RegionMode::CheckFieldmark))
            {
                m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::On
                                              | RedlineFlags::ShowInsert
                                              | RedlineFlags::ShowDelete );
                SwFltRedline& rFltRedline = *static_cast<SwFltRedline*>(rEntry.m_pAttr.get());

                SwRedlineData aData(rFltRedline.m_eType,
                                    rFltRedline.m_nAutorNo,
                                    rFltRedline.m_aStamp,
                                    OUString(),
                                    nullptr
                                    );
                m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline(aData, aRegion), true );
                m_rDoc.getIDocumentRedlineAccess().SetRedlineFlags( RedlineFlags::NONE
                                                | RedlineFlags::ShowInsert
                                                | RedlineFlags::ShowDelete );
            }
        }
        break;
    default:
        {
            if (rEntry.MakeRegion(m_rDoc, aRegion, SwFltStackEntry::RegionMode::NoCheck))
            {
                m_rDoc.getIDocumentContentOperations().InsertPoolItem(aRegion, *rEntry.m_pAttr);
            }
        }
        break;
    }
}

SfxPoolItem* SwFltControlStack::GetFormatStackAttr(sal_uInt16 nWhich, sal_uInt16 * pPos)
{
    size_t nSize = m_Entries.size();

    while (nSize)
    {
        // is it the looked-for attribute ? (only applies to locked, meaning
        // currently set attributes!!)
        SwFltStackEntry &rEntry = *m_Entries[--nSize];
        if (rEntry.m_bOpen && rEntry.m_pAttr->Which() == nWhich)
        {
            if (pPos)
                *pPos = nSize;
            return rEntry.m_pAttr.get();      // Ok, so stop
        }
    }
    return nullptr;
}

const SfxPoolItem* SwFltControlStack::GetOpenStackAttr(const SwPosition& rPos, sal_uInt16 nWhich)
{
    SwFltPosition aFltPos(rPos);

    size_t nSize = m_Entries.size();

    while (nSize)
    {
        SwFltStackEntry &rEntry = *m_Entries[--nSize];
        if (rEntry.m_bOpen && rEntry.m_pAttr->Which() == nWhich && rEntry.m_aMkPos == aFltPos)
        {
            return rEntry.m_pAttr.get();
        }
    }
    return nullptr;
}

void SwFltControlStack::Delete(const SwPaM &rPam)
{
    auto [pStt, pEnd] = rPam.StartEnd(); // SwPosition*

    if( !rPam.HasMark() || *pStt >= *pEnd )
        return;

    SwNodeIndex aStartNode(pStt->GetNode(), -1);
    const sal_Int32 nStartIdx = pStt->GetContentIndex();
    SwNodeIndex aEndNode(pEnd->GetNode(), -1);
    const sal_Int32 nEndIdx = pEnd->GetContentIndex();

    // We don't support deleting content that is over one node, or removing a node.
    OSL_ENSURE(aEndNode == aStartNode, "nodes must be the same, or this method extended");
    if (aEndNode != aStartNode)
        return;

    for (size_t nSize = m_Entries.size(); nSize > 0;)
    {
        SwFltStackEntry& rEntry = *m_Entries[--nSize];

        bool bEntryStartAfterSelStart =
            (rEntry.m_aMkPos.m_nNode == aStartNode &&
             rEntry.m_aMkPos.m_nContent >= nStartIdx);

        bool bEntryStartBeforeSelEnd =
            (rEntry.m_aMkPos.m_nNode == aEndNode &&
             rEntry.m_aMkPos.m_nContent <= nEndIdx);

        bool bEntryEndAfterSelStart = false;
        bool bEntryEndBeforeSelEnd = false;
        if (!rEntry.m_bOpen)
        {
            bEntryEndAfterSelStart =
                (rEntry.m_aPtPos.m_nNode == aStartNode &&
                 rEntry.m_aPtPos.m_nContent >= nStartIdx);

            bEntryEndBeforeSelEnd =
                (rEntry.m_aPtPos.m_nNode == aEndNode &&
                 rEntry.m_aPtPos.m_nContent <= nEndIdx);
        }

        bool bTotallyContained = false;
        if (
             bEntryStartAfterSelStart && bEntryStartBeforeSelEnd &&
             bEntryEndAfterSelStart && bEntryEndBeforeSelEnd
           )
        {
           bTotallyContained = true;
        }

        if (bTotallyContained)
        {
            // after start, before end, delete
            DeleteAndDestroy(nSize);
            continue;
        }

        const sal_Int32 nContentDiff = nEndIdx - nStartIdx;

        // to be adjusted
        if (bEntryStartAfterSelStart)
        {
            if (bEntryStartBeforeSelEnd)
            {
                // move start to new start
                rEntry.m_aMkPos.SetPos(aStartNode, nStartIdx);
            }
            else
                rEntry.m_aMkPos.m_nContent -= nContentDiff;
        }

        if (bEntryEndAfterSelStart)
        {
            if (bEntryEndBeforeSelEnd)
                rEntry.m_aPtPos.SetPos(aStartNode, nStartIdx);
            else
                rEntry.m_aPtPos.m_nContent -= nContentDiff;
        }

        //That's what Open is, end equal to start, and nPtContent is invalid
        if (rEntry.m_bOpen)
            rEntry.m_aPtPos = rEntry.m_aMkPos;
    }
}

// methods of SwFltAnchor follow
SwFltAnchor::SwFltAnchor(SwFrameFormat* pFormat) :
    SfxPoolItem(RES_FLTR_ANCHOR), m_pFrameFormat(pFormat)
{
    m_pListener.reset(new SwFltAnchorListener(this));
    m_pListener->StartListening(m_pFrameFormat->GetNotifier());
}

SwFltAnchor::SwFltAnchor(const SwFltAnchor& rCpy) :
    SfxPoolItem(RES_FLTR_ANCHOR), m_pFrameFormat(rCpy.m_pFrameFormat)
{
    m_pListener.reset(new SwFltAnchorListener(this));
    m_pListener->StartListening(m_pFrameFormat->GetNotifier());
}

SwFltAnchor::~SwFltAnchor()
{
}

void SwFltAnchor::SetFrameFormat(SwFrameFormat * _pFrameFormat)
{
    m_pFrameFormat = _pFrameFormat;
}


bool SwFltAnchor::operator==(const SfxPoolItem& rItem) const
{
    return SfxPoolItem::operator==(rItem) &&
        m_pFrameFormat == static_cast<const SwFltAnchor&>(rItem).m_pFrameFormat;
}

SwFltAnchor* SwFltAnchor::Clone(SfxItemPool*) const
{
    return new SwFltAnchor(*this);
}

SwFltAnchorListener::SwFltAnchorListener(SwFltAnchor* pFltAnchor)
    : m_pFltAnchor(pFltAnchor)
{ }

void SwFltAnchorListener::Notify(const SfxHint& rHint)
{
    if (rHint.GetId() == SfxHintId::Dying)
        m_pFltAnchor->SetFrameFormat(nullptr);
    else if (rHint.GetId() == SfxHintId::SwDrawFrameFormat)
    {
        auto pDrawFrameFormatHint = static_cast<const sw::DrawFrameFormatHint*>(&rHint);
        if (pDrawFrameFormatHint->m_eId != sw::DrawFrameFormatHintId::DYING)
            return;
        m_pFltAnchor->SetFrameFormat(nullptr);
    }
    else if (rHint.GetId() == SfxHintId::SwLegacyModify)
    {
        auto pLegacyHint = static_cast<const sw::LegacyModifyHint*>(&rHint);
        if(pLegacyHint->m_pNew->Which() != RES_FMT_CHG)
            return;
        auto pFormatChg = dynamic_cast<const SwFormatChg*>(pLegacyHint->m_pNew);
        auto pFrameFormat = pFormatChg ? dynamic_cast<SwFrameFormat*>(pFormatChg->pChangedFormat) : nullptr;
        if(pFrameFormat)
            m_pFltAnchor->SetFrameFormat(pFrameFormat);
    }
}

// methods of SwFltRedline follow
bool SwFltRedline::operator==(const SfxPoolItem& rItem) const
{
    return SfxPoolItem::operator==(rItem) &&
        this == &rItem;
}

SwFltRedline* SwFltRedline::Clone( SfxItemPool* ) const
{
    return new SwFltRedline(*this);
}

// methods of SwFltBookmark follow
SwFltBookmark::SwFltBookmark( const OUString& rNa, OUString aVa,
                              tools::Long nHand, const bool bIsTOCBookmark )
    : SfxPoolItem( RES_FLTR_BOOKMARK )
    , mnHandle( nHand )
    , maName( rNa )
    , maVal(std::move( aVa ))
    , mbIsTOCBookmark( bIsTOCBookmark )
{
    // eSrc: CHARSET_DONTKNOW for no transform at operator <<
    // Upcase is always done.
    // Transform is never done at XXXStack.NewAttr(...).
    // otherwise: Src Charset from argument for aName
    // Src Charset from filter for aVal ( Text )

    if ( IsTOCBookmark() && ! rNa.startsWith(IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix()) )
    {
        maName = IDocumentMarkAccess::GetCrossRefHeadingBookmarkNamePrefix();
        maName += rNa;
    }
}

bool SwFltBookmark::operator==(const SfxPoolItem& rItem) const
{
    return SfxPoolItem::operator==(rItem)
        && maName == static_cast<const SwFltBookmark&>(rItem).maName
        && mnHandle == static_cast<const SwFltBookmark&>(rItem).mnHandle;
}

SwFltBookmark* SwFltBookmark::Clone(SfxItemPool*) const
{
    return new SwFltBookmark(*this);
}

SwFltRDFMark::SwFltRDFMark()
    : SfxPoolItem(RES_FLTR_RDFMARK),
      m_nHandle(0)
{
}

bool SwFltRDFMark::operator==(const SfxPoolItem& rItem) const
{
    if (!SfxPoolItem::operator==(rItem))
        return false;

    const SwFltRDFMark& rMark = static_cast<const SwFltRDFMark&>(rItem);

    return m_nHandle == rMark.m_nHandle && m_aAttributes == rMark.m_aAttributes;
}

SwFltRDFMark* SwFltRDFMark::Clone(SfxItemPool*) const
{
    return new SwFltRDFMark(*this);
}

void SwFltRDFMark::SetHandle(tools::Long nHandle)
{
    m_nHandle = nHandle;
}

tools::Long SwFltRDFMark::GetHandle() const
{
    return m_nHandle;
}

void SwFltRDFMark::SetAttributes( std::vector< std::pair<OUString, OUString> >&& rAttributes)
{
    m_aAttributes = std::move(rAttributes);
}

const std::vector< std::pair<OUString, OUString> >& SwFltRDFMark::GetAttributes() const
{
    return m_aAttributes;
}

// methods of SwFltTOX follow
SwFltTOX::SwFltTOX(std::shared_ptr<SwTOXBase> xBase)
    : SfxPoolItem(RES_FLTR_TOX), m_xTOXBase(std::move(xBase)),
      m_bHadBreakItem( false ), m_bHadPageDescItem( false )
{
}

bool SwFltTOX::operator==(const SfxPoolItem& rItem) const
{
    return SfxPoolItem::operator==(rItem) &&
        m_xTOXBase.get() == static_cast<const SwFltTOX&>(rItem).m_xTOXBase.get();
}

SwFltTOX* SwFltTOX::Clone(SfxItemPool*) const
{
    return new SwFltTOX(*this);
}

// UpdatePageDescs needs to be called at end of parsing to make Writer actually
// accept Pagedescs contents
void UpdatePageDescs(SwDoc &rDoc, size_t nInPageDescOffset)
{
    // Update document page descriptors (only this way also left pages
    // get adjusted)

    // PageDesc "Standard"
    rDoc.ChgPageDesc(0, rDoc.GetPageDesc(0));

    // PageDescs "Convert..."
    for (size_t i = nInPageDescOffset; i < rDoc.GetPageDescCnt(); ++i)
        rDoc.ChgPageDesc(i, rDoc.GetPageDesc(i));
}

FrameDeleteWatch::FrameDeleteWatch(SwFrameFormat* pFormat)
    : m_pFormat(pFormat)
{
    if(m_pFormat)
        StartListening(pFormat->GetNotifier());
}

void FrameDeleteWatch::Notify(const SfxHint& rHint)
{
    bool bDying = false;
    if (rHint.GetId() == SfxHintId::Dying)
        bDying = true;
    else if (rHint.GetId() == SfxHintId::SwDrawFrameFormat)
    {
        auto pDrawFrameFormatHint = static_cast<const sw::DrawFrameFormatHint*>(&rHint);
        bDying = pDrawFrameFormatHint->m_eId == sw::DrawFrameFormatHintId::DYING;
    }
    if (bDying)
    {
        m_pFormat = nullptr;
        EndListeningAll();
    }
}

FrameDeleteWatch::~FrameDeleteWatch()
{
    m_pFormat = nullptr;
    EndListeningAll();
}

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