/* -*- 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 <calbck.hxx>
#include <node.hxx>
#include <ndindex.hxx>
#include <pam.hxx>
#include <swtable.hxx>
#include <ftnfrm.hxx>
#include <sectfrm.hxx>
#include <cntfrm.hxx>
#include <tabfrm.hxx>
#include <frmtool.hxx>
#include <section.hxx>
#include <node2lay.hxx>
#include <osl/diagnose.h>

/**
 * The SwNode2LayImpl class does the actual work, the SwNode2Layout class is
 * just the public interface.
 */
class SwNode2LayImpl
{
    std::unique_ptr<SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti>> mpIter;
    sw::BroadcastingModify* mpMod;
    std::vector<SwFrame*> mvUpperFrames; // To collect the Upper
    SwNodeOffset mnIndex;        // The Index of the to-be-inserted Nodes
    bool mbMaster    : 1; // true => only Master, false => only Frames without Follow
    bool mbInit      : 1; // Did we already call First() at SwClient?

    SwNode2LayImpl(const SwNode2LayImpl&) = delete;
    SwNode2LayImpl& operator=(const SwNode2LayImpl&) = delete;

public:
    SwNode2LayImpl( const SwNode& rNode, SwNodeOffset nIdx, bool bSearch );
    SwFrame* NextFrame(); // Returns the next "useful" Frame
    SwLayoutFrame* UpperFrame( SwFrame* &rpFrame, const SwNode &rNode );
    void SaveUpperFrames(); // Saves (and locks if needed) the pUpper
    // Inserts a Frame under every pUpper of the array
    void RestoreUpperFrames( SwNodes& rNds, SwNodeOffset nStt, SwNodeOffset nEnd );

    SwFrame* GetFrame( const Point* pDocPos ) const;
};

static SwNode* GoNextWithFrame(const SwNodes& rNodes, SwNodeIndex *pIdx, SwFlowFrame const**const ppFrame)
{
    if( pIdx->GetIndex() >= rNodes.Count() - 1 )
        return nullptr;

    SwNodeIndex aTmp(*pIdx, +1);
    SwNode* pNd = nullptr;
    while( aTmp < rNodes.Count()-1 )
    {
        pNd = &aTmp.GetNode();
        SwFrame const* pFound(nullptr);
        if ( pNd->IsContentNode() )
            // sw_redlinehide: assume that it's OK to find a node with the same
            // frame as the caller's one
            pFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First();
        else if ( pNd->IsTableNode() )
            pFound = SwIterator<SwFrame,SwFormat>(*static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()).First() ;
        else if( pNd->IsEndNode() && !pNd->StartOfSectionNode()->IsSectionNode() )
        {
            pNd = nullptr;
            break;
        }
        if (pFound != nullptr)
        {
            if (ppFrame)
            {
                *ppFrame = SwFlowFrame::CastFlowFrame(pFound);
                assert(*ppFrame);
            }
            break;
        }
        ++aTmp;
    }

    if( aTmp == rNodes.Count()-SwNodeOffset(1) )
        pNd = nullptr;
    else if( pNd )
        (*pIdx) = aTmp;
    return pNd;
}

static SwNode* GoPreviousWithFrame(SwNodeIndex *pIdx, SwFlowFrame const**const ppFrame)
{
    if( !pIdx->GetIndex() )
        return nullptr;

    SwNodeIndex aTmp( *pIdx, -1 );
    SwNode* pNd(nullptr);
    while( aTmp.GetIndex() )
    {
        pNd = &aTmp.GetNode();
        SwFrame const* pFound(nullptr);
        if ( pNd->IsContentNode() )
            // sw_redlinehide: assume that it's OK to find a node with the same
            // frame as the caller's one
            pFound = SwIterator<SwFrame, SwContentNode, sw::IteratorMode::UnwrapMulti>(*static_cast<SwContentNode*>(pNd)).First();
        else if ( pNd->IsTableNode() )
        {
            if (SwFrameFormat *const pFormat{static_cast<SwTableNode*>(pNd)->GetTable().GetFrameFormat()})
            {
                pFound = SwIterator<SwFrame,SwFormat>(*pFormat).First();
            }
        }
        else if( pNd->IsStartNode() && !pNd->IsSectionNode() )
        {
            pNd = nullptr;
            break;
        }
        if (pFound != nullptr)
        {
            if (ppFrame)
            {
                *ppFrame = SwFlowFrame::CastFlowFrame(pFound);
                assert(*ppFrame);
            }
            break;
        }
        --aTmp;
    }

    if( !aTmp.GetIndex() )
        pNd = nullptr;
    else if( pNd )
        (*pIdx) = aTmp;
    return pNd;
}

namespace sw {

SwFrame const* FindNeighbourFrameForNode(SwNode const& rNode)
{
    SwNodeIndex idx(rNode);
    SwFlowFrame const* pFlow(nullptr);
    if (GoPreviousWithFrame(&idx, &pFlow))
    {
        if (::CheckNodesRange(rNode, idx.GetNode(), true))
        {
            while (pFlow->HasFollow())
            {   // try to get the one on the current page
                pFlow = pFlow->GetFollow();
            }
            return &pFlow->GetFrame();
        }
    }
    idx = rNode;
    if (GoNextWithFrame(idx.GetNodes(), &idx, &pFlow))
    {
        if (::CheckNodesRange(rNode, idx.GetNode(), true))
        {
            while (pFlow->IsFollow())
            {   // try to get the one on the current page
                pFlow = pFlow->GetPrecede();
            }
            return &pFlow->GetFrame();
        }
    }
    return nullptr;
}

}

/**
 * The main purpose of this ctor is to find the right sw::BroadcastingModify to iterate over.
 *
 * @param bSearch true:     find the next Content or TableNode which contains
 *                          Frames (to collect the pUpper).
 *                          Else we assume that rNode points already to such a
 *                          Content or TableNode.
 *                          We insert before or after it.
 */
SwNode2LayImpl::SwNode2LayImpl( const SwNode& rNode, SwNodeOffset nIdx, bool bSearch )
    : mnIndex( nIdx ), mbInit( false )
{
    const SwNode* pNd;
    if( bSearch || rNode.IsSectionNode() )
    {
        // Find the next Content/TableNode that contains a Frame, so that we can add
        // ourselves before/after it
        if( !bSearch && rNode.GetIndex() < mnIndex )
        {
            SwNodeIndex aTmp( *rNode.EndOfSectionNode(), +1 );
            pNd = GoPreviousWithFrame(&aTmp, nullptr);
            if( pNd && rNode.GetIndex() > pNd->GetIndex() )
                pNd = nullptr; // Do not go over the limits
            mbMaster = false;
        }
        else
        {
            SwNodeIndex aTmp( rNode, -1 );
            pNd = GoNextWithFrame(rNode.GetNodes(), &aTmp, nullptr);
            mbMaster = true;
            if( !bSearch && pNd && rNode.EndOfSectionIndex() < pNd->GetIndex() )
                pNd = nullptr; // Do not go over the limits
        }
    }
    else
    {
        pNd = &rNode;
        mbMaster = mnIndex < rNode.GetIndex();
    }
    if( pNd )
    {
        if( pNd->IsContentNode() )
            mpMod = const_cast<sw::BroadcastingModify*>(static_cast<sw::BroadcastingModify const *>(pNd->GetContentNode()));
        else
        {
            assert(pNd->IsTableNode());
            mpMod = pNd->GetTableNode()->GetTable().GetFrameFormat();
        }
        mpIter.reset(new SwIterator<SwFrame, sw::BroadcastingModify, sw::IteratorMode::UnwrapMulti>(*mpMod));
    }
    else
    {
        mpIter = nullptr;
        mpMod = nullptr;
    }
}

/**
 * Returns the next "useful" Frame.
 *
 * When calling this method for the first time, a First is triggered at the
 * actual Iterator. The result is check for suitability: Follows are not
 * accepted, a Master is accepted when collecting the pUpper and when
 * inserting before it.
 * When inserting after it, we find and return the last Follow starting
 * from the Master.
 *
 * If the Frame is located in a SectionFrame, we check to see whether the
 * SectionFrame is the suitable return value (instead of the Frame itself).
 * This is the case if the to-be-inserted Node is outside of the Section.
 */
SwFrame* SwNode2LayImpl::NextFrame()
{
    SwFrame* pRet;
    if( !mpIter )
        return nullptr;
    if( !mbInit )
    {
         pRet = mpIter->First();
         mbInit = true;
    }
    else
        pRet = mpIter->Next();
    while( pRet )
    {
        SwFlowFrame* pFlow = SwFlowFrame::CastFlowFrame( pRet );
        assert(pFlow);
        // Follows are pretty volatile, thus we ignore them.
        // Even if we insert after the Frame, we start from the Master
        // and iterate through it until the last Follow
        if( !pFlow->IsFollow() )
        {
            if( !mbMaster )
            {
                while( pFlow->HasFollow() )
                    pFlow = pFlow->GetFollow();
                pRet = &(pFlow->GetFrame());
            }
            if( pRet->IsInSct() )
            {
                SwSectionFrame* pSct = pRet->FindSctFrame();
                // ATTENTION: If we are in a Footnote, from a Layout point of view
                // it could be located in a Section with columns, although it
                // should be outside of it when looking at the Nodes.
                // Thus, when dealing with Footnotes, we need to check whether the
                // SectionFrame is also located within the Footnote and not outside of it.
                if( !pRet->IsInFootnote() || pSct->IsInFootnote() )
                {
                    assert(pSct && pSct->GetSection());
                    SwSectionNode* pNd = pSct->GetSection()->GetFormat()->GetSectionNode();
                    assert(pNd);
                    // If the result Frame is located within a Section Frame
                    // whose Section does not contain the Node, we return with
                    // the SectionFrame, else we return with the Content/TabFrame
                    if( mbMaster )
                    {
                        if( pNd->GetIndex() >= mnIndex )
                            pRet = pSct;
                    }
                    else if( pNd->EndOfSectionIndex() < mnIndex )
                        pRet = pSct;
                }
            }
            return pRet;
        }
        pRet = mpIter->Next();
    }
    return nullptr;
}

void SwNode2LayImpl::SaveUpperFrames()
{
    SwFrame* pFrame;
    while( nullptr != (pFrame = NextFrame()) )
    {
        SwFrame* pPrv = pFrame->GetPrev();
        pFrame = pFrame->GetUpper();
        if( pFrame )
        {
            if( pFrame->IsFootnoteFrame() )
                static_cast<SwFootnoteFrame*>(pFrame)->ColLock();
            else if( pFrame->IsInSct() )
                pFrame->FindSctFrame()->ColLock();
            if( pPrv && pPrv->IsSctFrame() )
                static_cast<SwSectionFrame*>(pPrv)->LockJoin();
            mvUpperFrames.push_back( pPrv );
            mvUpperFrames.push_back( pFrame );
        }
    }
    mpIter.reset();
    mpMod = nullptr;
}

SwLayoutFrame* SwNode2LayImpl::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode )
{
    rpFrame = NextFrame();
    if( !rpFrame )
        return nullptr;
    SwLayoutFrame* pUpper = rpFrame->GetUpper();
    if( rpFrame->IsSctFrame() )
    {
        const SwNode* pNode = rNode.StartOfSectionNode();
        if( pNode->IsSectionNode() )
        {
            SwFrame* pFrame = mbMaster ? rpFrame->FindPrev() : rpFrame->FindNext();
            if( pFrame && pFrame->IsSctFrame() )
            {
                // pFrame could be a "dummy"-section
                if( static_cast<SwSectionFrame*>(pFrame)->GetSection() &&
                    (&static_cast<const SwSectionNode*>(pNode)->GetSection() ==
                     static_cast<SwSectionFrame*>(pFrame)->GetSection()) )
                {
                    // #i22922# - consider columned sections
                    // 'Go down' the section frame as long as the layout frame
                    // is found, which would contain content.
                    SwFrame* pLower = static_cast<SwLayoutFrame*>(pFrame)->Lower();
                    while ( pFrame->IsLayoutFrame() && pLower &&
                            !pLower->IsFlowFrame() && pLower->IsLayoutFrame() )
                    {
                        pFrame = pLower;
                        pLower = static_cast<SwLayoutFrame*>(pFrame)->Lower();
                    }
                    assert(pFrame->IsLayoutFrame());
                    rpFrame = mbMaster ? nullptr : pLower;
                    assert((!rpFrame || rpFrame->IsFlowFrame()) &&
                            "<SwNode2LayImpl::UpperFrame(..)> - expected sibling isn't a flow frame." );
                    return static_cast<SwLayoutFrame*>(pFrame);
                }

                pUpper = new SwSectionFrame(const_cast<SwSectionNode*>(static_cast<const SwSectionNode*>(pNode))->GetSection(), rpFrame);
                pUpper->Paste( rpFrame->GetUpper(),
                               mbMaster ? rpFrame : rpFrame->GetNext() );
                // coverity[freed_arg : FALSE] - pUpper->Lower() is not freed here
                static_cast<SwSectionFrame*>(pUpper)->Init();
                rpFrame = nullptr;
                // 'Go down' the section frame as long as the layout frame
                // is found, which would contain content.
                SwFrame* pLower = pUpper->Lower();
                while ( pLower && !pLower->IsFlowFrame() &&
                        pLower->IsLayoutFrame() )
                {
                    pUpper = static_cast<SwLayoutFrame*>(pLower);
                    pLower = pUpper->Lower();
                }
                return pUpper;
            }
        }
    }
    if( !mbMaster )
        rpFrame = rpFrame->GetNext();
    return pUpper;
}

void SwNode2LayImpl::RestoreUpperFrames( SwNodes& rNds, SwNodeOffset nStt, SwNodeOffset nEnd )
{
    SwNode* pNd;
    SwDoc& rDoc = rNds.GetDoc();
    bool bFirst = true;
    for( ; nStt < nEnd; ++nStt )
    {
        SwFrame* pNew = nullptr;
        SwFrame* pNxt;
        SwLayoutFrame* pUp;
        if( (pNd = rNds[nStt])->IsContentNode() )
            for( std::vector<SwFrame*>::size_type n = 0; n < mvUpperFrames.size(); )
            {
                pNxt = mvUpperFrames[n++];
                if( bFirst && pNxt && pNxt->IsSctFrame() )
                    static_cast<SwSectionFrame*>(pNxt)->UnlockJoin();
                pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[n++]);
                if( pNxt )
                    pNxt = pNxt->GetNext();
                else
                    pNxt = pUp->Lower();
                pNew = static_cast<SwContentNode*>(pNd)->MakeFrame( pUp );
                pNew->Paste( pUp, pNxt );
                mvUpperFrames[n-2] = pNew;
            }
        else if( pNd->IsTableNode() )
            for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); )
            {
                pNxt = mvUpperFrames[x++];
                if( bFirst && pNxt && pNxt->IsSctFrame() )
                    static_cast<SwSectionFrame*>(pNxt)->UnlockJoin();
                pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]);
                if( pNxt )
                    pNxt = pNxt->GetNext();
                else
                    pNxt = pUp->Lower();
                pNew = static_cast<SwTableNode*>(pNd)->MakeFrame( pUp );
                assert(pNew->IsTabFrame());
                pNew->Paste( pUp, pNxt );
                static_cast<SwTabFrame*>(pNew)->RegistFlys();
                mvUpperFrames[x-2] = pNew;
            }
        else if( pNd->IsSectionNode() )
        {
            nStt = pNd->EndOfSectionIndex();
            for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); )
            {
                pNxt = mvUpperFrames[x++];
                if( bFirst && pNxt && pNxt->IsSctFrame() )
                    static_cast<SwSectionFrame*>(pNxt)->UnlockJoin();
                pUp = static_cast<SwLayoutFrame*>(mvUpperFrames[x++]);
                OSL_ENSURE( pUp->GetUpper() || pUp->IsFlyFrame(), "Lost Upper" );
                ::InsertCnt_( pUp, rDoc, pNd->GetIndex(), false, nStt+1, pNxt );
                pNxt = pUp->GetLastLower();
                mvUpperFrames[x-2] = pNxt;
            }
        }
        bFirst = false;
    }
    for( std::vector<SwFrame*>::size_type x = 0; x < mvUpperFrames.size(); ++x )
    {
        SwFrame* pTmp = mvUpperFrames[++x];
        if( pTmp->IsFootnoteFrame() )
            static_cast<SwFootnoteFrame*>(pTmp)->ColUnlock();
        else if ( pTmp->IsInSct() )
        {
            SwSectionFrame* pSctFrame = pTmp->FindSctFrame();
            pSctFrame->ColUnlock();
            // #i18103# - invalidate size of section in order to
            // assure, that the section is formatted, unless it was 'Collocked'
            // from its 'collection' until its 'restoration'.
            pSctFrame->InvalidateSize_();
        }
    }
}

SwFrame* SwNode2LayImpl::GetFrame( const Point* pDocPos ) const
{
    // test if change of member pIter -> pMod broke anything
    std::pair<Point, bool> tmp;
    if (pDocPos)
    {
        tmp.first = *pDocPos;
        tmp.second = false;
    }
    return mpMod ? ::GetFrameOfModify(nullptr, *mpMod, FRM_ALL, nullptr, pDocPos ? &tmp : nullptr) : nullptr;
}

SwNode2Layout::SwNode2Layout( const SwNode& rNd, SwNodeOffset nIdx )
    : m_pImpl( new SwNode2LayImpl( rNd, nIdx, false ) )
{
}

SwNode2LayoutSaveUpperFrames::SwNode2LayoutSaveUpperFrames(const SwNode& rNd)
    : m_pImpl( new SwNode2LayImpl( rNd, rNd.GetIndex(), true ) )
{
    m_pImpl->SaveUpperFrames();
}

void SwNode2LayoutSaveUpperFrames::RestoreUpperFrames(
        SwNodes& rNds, SwNodeOffset const nStt, SwNodeOffset const nEnd)
{
    m_pImpl->RestoreUpperFrames( rNds, nStt, nEnd );
}

SwFrame* SwNode2Layout::NextFrame()
{
    return m_pImpl->NextFrame();
}

SwLayoutFrame* SwNode2Layout::UpperFrame( SwFrame* &rpFrame, const SwNode &rNode )
{
    return m_pImpl->UpperFrame( rpFrame, rNode );
}

SwNode2Layout::~SwNode2Layout()
{
}

SwNode2LayoutSaveUpperFrames::~SwNode2LayoutSaveUpperFrames()
{
}

SwFrame* SwNode2Layout::GetFrame( const Point* pDocPos ) const
{
    return m_pImpl->GetFrame( pDocPos );
}

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