/* -*- 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 <crsrsh.hxx>
#include <doc.hxx>
#include <pagefrm.hxx>
#include <cntfrm.hxx>
#include <ftnfrm.hxx>
#include <swcrsr.hxx>
#include <ndtxt.hxx>
#include <txtfrm.hxx>
#include <txtftn.hxx>
#include <ftnidx.hxx>
#include <viscrs.hxx>
#include "callnk.hxx"
#include <svx/srchdlg.hxx>

bool SwCursorShell::CallCursorShellFN( FNCursorShell fnCursor )
{
    SwCallLink aLk( *this ); // watch Cursor-Moves
    bool bRet = (this->*fnCursor)();
    if( bRet )
        UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
                    SwCursorShell::READONLY );
    return bRet;
}

bool SwCursorShell::CallCursorFN( FNCursor fnCursor )
{
    SwCallLink aLk( *this ); // watch Cursor-Moves
    SwCursor* pCursor = getShellCursor( true );
    bool bRet = (pCursor->*fnCursor)();
    if( bRet )
        UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
                    SwCursorShell::READONLY );
    return bRet;
}

bool SwCursor::GotoFootnoteText()
{
    // jump from content to footnote
    bool bRet = false;
    SwTextNode* pTextNd = GetPoint()->nNode.GetNode().GetTextNode();

    SwTextAttr *const pFootnote( pTextNd
        ? pTextNd->GetTextAttrForCharAt(
            GetPoint()->nContent.GetIndex(), RES_TXTATR_FTN)
        : nullptr);
    if (pFootnote)
    {
        SwCursorSaveState aSaveState( *this );
        GetPoint()->nNode = *static_cast<SwTextFootnote*>(pFootnote)->GetStartNode();

        SwContentNode* pCNd = GetDoc()->GetNodes().GoNextSection(
                                            &GetPoint()->nNode,
                                            true, !IsReadOnlyAvailable() );
        if( pCNd )
        {
            GetPoint()->nContent.Assign( pCNd, 0 );
            bRet = !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
                              SwCursorSelOverFlags::Toggle );
        }
    }
    return bRet;
}

bool SwCursorShell::GotoFootnoteText()
{
    bool bRet = CallCursorFN( &SwCursor::GotoFootnoteText );
    if( !bRet )
    {
        SwTextNode* pTextNd = GetCursor_() ?
                   GetCursor_()->GetPoint()->nNode.GetNode().GetTextNode() : nullptr;
        if( pTextNd )
        {
            std::pair<Point, bool> const tmp(GetCursor_()->GetSttPos(), true);
            const SwFrame *pFrame = pTextNd->getLayoutFrame( GetLayout(),
                                                 GetCursor_()->Start(), &tmp);
            const SwFootnoteBossFrame* pFootnoteBoss;
            bool bSkip = pFrame && pFrame->IsInFootnote();
            while( pFrame )
            {
                pFootnoteBoss = pFrame->FindFootnoteBossFrame();
                if (!pFootnoteBoss)
                    break;
                pFrame = pFootnoteBoss->FindFootnoteCont();
                if( pFrame )
                {
                    if( bSkip )
                        bSkip = false;
                    else
                    {
                        const SwContentFrame* pCnt = static_cast<const SwLayoutFrame*>
                                                        (pFrame)->ContainsContent();
                        if( pCnt )
                        {
                            SwTextFrame const*const pTF(
                                    static_cast<const SwTextFrame*>(pCnt));
                            *GetCursor_()->GetPoint() =
                                    pTF->MapViewToModelPos(pTF->GetOffset());
                            UpdateCursor( SwCursorShell::SCROLLWIN |
                                SwCursorShell::CHKRANGE | SwCursorShell::READONLY );
                            bRet = true;
                            break;
                        }
                    }
                }
                if( pFootnoteBoss->GetNext() && !pFootnoteBoss->IsPageFrame() )
                    pFrame = pFootnoteBoss->GetNext();
                else
                    pFrame = pFootnoteBoss->GetUpper();
            }
        }
    }
    return bRet;
}

bool SwCursor::GotoFootnoteAnchor()
{
    // jump from footnote to anchor
    const SwNode* pSttNd = GetNode().FindFootnoteStartNode();
    if( pSttNd )
    {
        // search in all footnotes in document for this StartIndex
        const SwFootnoteIdxs& rFootnoteArr = pSttNd->GetDoc()->GetFootnoteIdxs();
        for( size_t n = 0; n < rFootnoteArr.size(); ++n )
        {
            const SwTextFootnote* pTextFootnote = rFootnoteArr[ n ];
            if( nullptr != pTextFootnote->GetStartNode() &&
                pSttNd == &pTextFootnote->GetStartNode()->GetNode() )
            {
                SwCursorSaveState aSaveState( *this );

                SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
                GetPoint()->nNode = rTNd;
                GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() );

                return !IsSelOvr( SwCursorSelOverFlags::CheckNodeSection |
                                  SwCursorSelOverFlags::Toggle );
            }
        }
    }
    return false;
}

bool SwCursorShell::GotoFootnoteAnchor()
{
    // jump from footnote to anchor
    SwCallLink aLk( *this ); // watch Cursor-Moves
    bool bRet = m_pCurrentCursor->GotoFootnoteAnchor();
    if( bRet )
    {
        // special treatment for table header row
        m_pCurrentCursor->GetPtPos() = Point();
        UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
                    SwCursorShell::READONLY );
    }
    return bRet;
}

static bool CmpLE( const SwTextFootnote& rFootnote, sal_uLong nNd, sal_Int32 nCnt )
{
    const sal_uLong nTNd = rFootnote.GetTextNode().GetIndex();
    return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() <= nCnt );
}

static bool CmpL( const SwTextFootnote& rFootnote, sal_uLong nNd, sal_Int32 nCnt )
{
    const sal_uLong nTNd = rFootnote.GetTextNode().GetIndex();
    return nTNd < nNd || ( nTNd == nNd && rFootnote.GetStart() < nCnt );
}

bool SwCursor::GotoNextFootnoteAnchor()
{
    const SwFootnoteIdxs& rFootnoteArr = GetDoc()->GetFootnoteIdxs();
    const SwTextFootnote* pTextFootnote = nullptr;
    size_t nPos = 0;

    if( rFootnoteArr.empty() )
    {
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
        return false;
    }

    if( rFootnoteArr.SeekEntry( GetPoint()->nNode, &nPos ))
    {
        // there is a footnote with this index, so search also for the next one
        if( nPos < rFootnoteArr.size() )
        {
            sal_uLong nNdPos = GetPoint()->nNode.GetIndex();
            const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex();

            pTextFootnote = rFootnoteArr[ nPos ];
            // search forwards
            if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
            {
                pTextFootnote = nullptr;
                for( ++nPos; nPos < rFootnoteArr.size(); ++nPos )
                {
                    pTextFootnote = rFootnoteArr[ nPos ];
                    if( !CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
                        break; // found
                    pTextFootnote = nullptr;
                }
            }
            else if( nPos )
            {
                // search backwards
                pTextFootnote = nullptr;
                while( nPos )
                {
                    pTextFootnote = rFootnoteArr[ --nPos ];
                    if( CmpLE( *pTextFootnote, nNdPos, nCntPos ) )
                    {
                        pTextFootnote = rFootnoteArr[ ++nPos ];
                        break; // found
                    }
                }
            }
        }
    }
    else if( nPos < rFootnoteArr.size() )
        pTextFootnote = rFootnoteArr[ nPos ];

    if (pTextFootnote == nullptr)
    {
        pTextFootnote = rFootnoteArr[ 0 ];
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::EndWrapped );
    }
    else
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );

    bool bRet = nullptr != pTextFootnote;
    if( bRet )
    {
        SwCursorSaveState aSaveState( *this );

        SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
        GetPoint()->nNode = rTNd;
        GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() );
        bRet = !IsSelOvr();
    }
    return bRet;
}

bool SwCursor::GotoPrevFootnoteAnchor()
{
    const SwFootnoteIdxs& rFootnoteArr = GetDoc()->GetFootnoteIdxs();
    const SwTextFootnote* pTextFootnote = nullptr;
    size_t nPos = 0;

    if( rFootnoteArr.empty() )
    {
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::NavElementNotFound );
        return false;
    }

    if( rFootnoteArr.SeekEntry( GetPoint()->nNode, &nPos ) )
    {
        // there is a footnote with this index, so search also for the next one
        sal_uLong nNdPos = GetPoint()->nNode.GetIndex();
        const sal_Int32 nCntPos = GetPoint()->nContent.GetIndex();

        pTextFootnote = rFootnoteArr[ nPos ];
        // search forwards
        if( CmpL( *pTextFootnote, nNdPos, nCntPos ))
        {
            for( ++nPos; nPos < rFootnoteArr.size(); ++nPos )
            {
                pTextFootnote = rFootnoteArr[ nPos ];
                if( !CmpL( *pTextFootnote, nNdPos, nCntPos ) )
                {
                    pTextFootnote = rFootnoteArr[ nPos-1 ];
                    break;
                }
            }
        }
        else if( nPos )
        {
            // search backwards
            pTextFootnote = nullptr;
            while( nPos )
            {
                pTextFootnote = rFootnoteArr[ --nPos ];
                if( CmpL( *pTextFootnote, nNdPos, nCntPos ))
                    break; // found
                pTextFootnote = nullptr;
            }
        }
        else
            pTextFootnote = nullptr;
    }
    else if( nPos )
        pTextFootnote = rFootnoteArr[ nPos-1 ];

    if( pTextFootnote == nullptr )
    {
        pTextFootnote = rFootnoteArr[ rFootnoteArr.size() - 1 ];
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::StartWrapped );
    }
    else
        SvxSearchDialogWrapper::SetSearchLabel( SearchLabel::Empty );

    bool bRet = nullptr != pTextFootnote;
    if( bRet )
    {
        SwCursorSaveState aSaveState( *this );

        SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
        GetPoint()->nNode = rTNd;
        GetPoint()->nContent.Assign( &rTNd, pTextFootnote->GetStart() );
        bRet = !IsSelOvr();
    }
    return bRet;
}

bool SwCursorShell::GotoNextFootnoteAnchor()
{
    return CallCursorFN( &SwCursor::GotoNextFootnoteAnchor );
}

bool SwCursorShell::GotoPrevFootnoteAnchor()
{
    return CallCursorFN( &SwCursor::GotoPrevFootnoteAnchor );
}

/// jump from border to anchor
void SwCursorShell::GotoFlyAnchor()
{
    SET_CURR_SHELL( this );
    const SwFrame* pFrame = GetCurrFrame();
    do {
        pFrame = pFrame->GetUpper();
    } while( pFrame && !pFrame->IsFlyFrame() );

    if( !pFrame ) // no FlyFrame
        return;

    SwCallLink aLk( *this ); // watch Cursor-Moves
    SwCursorSaveState aSaveState( *m_pCurrentCursor );

    // jump in BodyFrame closest to FlyFrame
    SwRect aTmpRect( m_aCharRect );
    if( !pFrame->getFrameArea().IsInside( aTmpRect ))
        aTmpRect = pFrame->getFrameArea();
    Point aPt( aTmpRect.Left(), aTmpRect.Top() +
                ( aTmpRect.Bottom() - aTmpRect.Top() ) / 2 );
    aPt.setX(aPt.getX() > (pFrame->getFrameArea().Left() + (pFrame->getFrameArea().SSize().Width() / 2 ))
                ? pFrame->getFrameArea().Right()
                : pFrame->getFrameArea().Left());

    const SwPageFrame* pPageFrame = pFrame->FindPageFrame();
    const SwContentFrame* pFndFrame = pPageFrame->GetContentPos( aPt, false, true );
    pFndFrame->GetModelPositionForViewPoint( m_pCurrentCursor->GetPoint(), aPt );

    bool bRet = !m_pCurrentCursor->IsInProtectTable() && !m_pCurrentCursor->IsSelOvr();
    if( bRet )
        UpdateCursor( SwCursorShell::SCROLLWIN | SwCursorShell::CHKRANGE |
                    SwCursorShell::READONLY );
}

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