/* -*- 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 <doc.hxx>
#include <osl/thread.h>
#include <vcl/help.hxx>
#include <tools/urlobj.hxx>
#include <fmtrfmrk.hxx>
#include <svl/urihelper.hxx>
#include <sfx2/sfxhelp.hxx>
#include <svx/svdview.hxx>
#include <svx/svdpagv.hxx>
#include <swmodule.hxx>
#include <modcfg.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <docsh.hxx>
#include <edtwin.hxx>
#include <dpage.hxx>
#include <docufld.hxx>
#include <reffld.hxx>
#include <cellatr.hxx>
#include <shdwcrsr.hxx>
#include <fmtinfmt.hxx>
#include <fmtftn.hxx>
#include <redline.hxx>
#include <tox.hxx>
#include <txatbase.hxx>
#include <uitool.hxx>
#include <viewopt.hxx>
#include <strings.hrc>

#include <IDocumentMarkAccess.hxx>
#include <txtfrm.hxx>
#include <ndtxt.hxx>

static OUString lcl_GetRedlineHelp( const SwRangeRedline& rRedl, bool bBalloon )
{
    const char* pResId = nullptr;
    switch( rRedl.GetType() )
    {
    case RedlineType::Insert:   pResId = STR_REDLINE_INSERT; break;
    case RedlineType::Delete:   pResId = STR_REDLINE_DELETE; break;
    case RedlineType::Format:   pResId = STR_REDLINE_FORMAT; break;
    case RedlineType::Table:    pResId = STR_REDLINE_TABLE; break;
    case RedlineType::FmtColl:  pResId = STR_REDLINE_FMTCOLL; break;
    case RedlineType::ParagraphFormat: pResId = STR_REDLINE_PARAGRAPH_FORMAT; break;
    case RedlineType::TableRowInsert: pResId = STR_REDLINE_TABLE_ROW_INSERT; break;
    case RedlineType::TableRowDelete: pResId = STR_REDLINE_TABLE_ROW_DELETE; break;
    case RedlineType::TableCellInsert: pResId = STR_REDLINE_TABLE_CELL_INSERT; break;
    case RedlineType::TableCellDelete: pResId = STR_REDLINE_TABLE_CELL_DELETE; break;
    default: break;
    }

    OUStringBuffer sBuf;
    if (pResId)
    {
        sBuf.append(SwResId(pResId));
        sBuf.append(": ");
        sBuf.append(rRedl.GetAuthorString());
        sBuf.append(" - ");
        sBuf.append(GetAppLangDateTimeString(rRedl.GetTimeStamp()));
        if( bBalloon && !rRedl.GetComment().isEmpty() )
            sBuf.append('\n').append(rRedl.GetComment());
    }
    return sBuf.makeStringAndClear();
}

OUString SwEditWin::ClipLongToolTip(const OUString& rText)
{
    OUString sDisplayText(rText);
    long nTextWidth = GetTextWidth(sDisplayText);
    long nMaxWidth = GetDesktopRectPixel().GetWidth() * 2 / 3;
    nMaxWidth = PixelToLogic(Size(nMaxWidth, 0)).Width();
    if (nTextWidth > nMaxWidth)
        sDisplayText = GetEllipsisString(sDisplayText, nMaxWidth, DrawTextFlags::CenterEllipsis);
    return sDisplayText;
}

void SwEditWin::RequestHelp(const HelpEvent &rEvt)
{
    SwWrtShell &rSh = m_rView.GetWrtShell();
    bool bQuickBalloon = bool(rEvt.GetMode() & ( HelpEventMode::QUICK | HelpEventMode::BALLOON ));
    if(bQuickBalloon && !rSh.GetViewOptions()->IsShowContentTips())
        return;
    bool bContinue = true;
    SET_CURR_SHELL(&rSh);
    OUString sText;
    Point aPos( PixelToLogic( ScreenToOutputPixel( rEvt.GetMousePosPixel() ) ));
    bool bBalloon = bool(rEvt.GetMode() & HelpEventMode::BALLOON);

    SdrView *pSdrView = rSh.GetDrawView();

    if( bQuickBalloon && pSdrView )
    {
        SdrPageView* pPV = pSdrView->GetSdrPageView();
        SwDPage* pPage = pPV ? static_cast<SwDPage*>(pPV->GetPage()) : nullptr;
        bContinue = pPage && pPage->RequestHelp(this, pSdrView, rEvt);
    }

    if( bContinue && bQuickBalloon)
    {
        SwRect aFieldRect;
        SwContentAtPos aContentAtPos( IsAttrAtPos::Field |
                                    IsAttrAtPos::InetAttr |
                                    IsAttrAtPos::Ftn |
                                    IsAttrAtPos::Redline |
                                    IsAttrAtPos::ToxMark |
                                    IsAttrAtPos::RefMark |
                                    IsAttrAtPos::SmartTag |
#ifdef DBG_UTIL
                                    IsAttrAtPos::TableBoxValue |
                                    ( bBalloon ? IsAttrAtPos::CurrAttrs : IsAttrAtPos::NONE) |
#endif
                                    IsAttrAtPos::TableBoxFml );

        if( rSh.GetContentAtPos( aPos, aContentAtPos, false, &aFieldRect ) )
        {
            QuickHelpFlags nStyle = QuickHelpFlags::NONE; // style of quick help
            switch( aContentAtPos.eContentAtPos )
            {
            case IsAttrAtPos::TableBoxFml:
                sText = "= " + static_cast<const SwTableBoxFormula*>(aContentAtPos.aFnd.pAttr)->GetFormula();
                break;
#ifdef DBG_UTIL
            case IsAttrAtPos::TableBoxValue:
            {
                sText = OStringToOUString(OString::number(
                            static_cast<const SwTableBoxValue*>(aContentAtPos.aFnd.pAttr)->GetValue()),
                            osl_getThreadTextEncoding());
                break;
            }
            case IsAttrAtPos::CurrAttrs:
                sText = aContentAtPos.sStr;
                break;
#endif

            case IsAttrAtPos::InetAttr:
            {
                sText = static_cast<const SwFormatINetFormat*>(aContentAtPos.aFnd.pAttr)->GetValue();
                sText = URIHelper::removePassword( sText,
                                        INetURLObject::EncodeMechanism::WasEncoded,
                                           INetURLObject::DecodeMechanism::Unambiguous);
                //#i63832# remove the link target type
                sal_Int32 nFound = sText.indexOf(cMarkSeparator);
                if( nFound != -1 && (++nFound) < sText.getLength() )
                {
                    OUString sSuffix( sText.copy(nFound) );
                    if( sSuffix == "table" ||
                        sSuffix == "frame" ||
                        sSuffix == "region" ||
                        sSuffix == "outline" ||
                        sSuffix == "text" ||
                        sSuffix == "graphic" ||
                        sSuffix == "ole" )
                        sText = sText.copy( 0, nFound - 1);
                }
                // #i104300#
                // special handling if target is a cross-reference bookmark
                {
                    OUString sTmpSearchStr = sText.copy( 1 );
                    IDocumentMarkAccess* pMarkAccess = rSh.getIDocumentMarkAccess();
                    IDocumentMarkAccess::const_iterator_t ppBkmk =
                                    pMarkAccess->findBookmark( sTmpSearchStr );
                    if ( ppBkmk != pMarkAccess->getBookmarksEnd() &&
                         IDocumentMarkAccess::GetType(**ppBkmk)
                            == IDocumentMarkAccess::MarkType::CROSSREF_HEADING_BOOKMARK )
                    {
                        SwTextNode* pTextNode = (*ppBkmk)->GetMarkStart().nNode.GetNode().GetTextNode();
                        if ( pTextNode )
                        {
                            sText = sw::GetExpandTextMerged(rSh.GetLayout(), *pTextNode, true, false, ExpandMode(0));

                            if( !sText.isEmpty() )
                            {
                                OUStringBuffer sTmp(sText.replaceAll(u"\u00ad", ""));
                                for (sal_Int32 i = 0; i < sTmp.getLength(); ++i)
                                {
                                    if (sTmp[i] < 0x20)
                                        sTmp[i] = 0x20;
                                    else if (sTmp[i] == 0x2011)
                                        sTmp[i] = '-';
                                }
                                sText = sTmp.makeStringAndClear();
                            }
                        }
                    }
                }
                // #i80029#
                bool bExecHyperlinks = m_rView.GetDocShell()->IsReadOnly();
                if ( !bExecHyperlinks )
                {
                    sText = SfxHelp::GetURLHelpText(sText);
                }
                break;
            }
            case IsAttrAtPos::SmartTag:
            {
                vcl::KeyCode aCode( KEY_SPACE );
                vcl::KeyCode aModifiedCode( KEY_SPACE, KEY_MOD1 );
                OUString aModStr( aModifiedCode.GetName() );
                aModStr = aModStr.replaceFirst(aCode.GetName(), "");
                aModStr = aModStr.replaceAll("+", "");
                sText = SwResId(STR_SMARTTAG_CLICK).replaceAll("%s", aModStr);
                break;
            }

            case IsAttrAtPos::Ftn:
                if( aContentAtPos.pFndTextAttr && aContentAtPos.aFnd.pAttr )
                {
                    const SwFormatFootnote* pFootnote = static_cast<const SwFormatFootnote*>(aContentAtPos.aFnd.pAttr);
                    OUString sTmp(pFootnote->GetFootnoteText(*rSh.GetLayout()));
                    sText = SwResId( pFootnote->IsEndNote()
                                    ? STR_ENDNOTE : STR_FTNNOTE ) + sTmp;
                    bBalloon = true;
                    if( aContentAtPos.IsInRTLText() )
                        nStyle |= QuickHelpFlags::BiDiRtl;
                }
                break;

            case IsAttrAtPos::Redline:
            {
                const bool bShowTrackChanges = IDocumentRedlineAccess::IsShowChanges( m_rView.GetDocShell()->GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() );
                const bool bShowInlineTooltips = rSh.GetViewOptions()->IsShowInlineTooltips();
                if ( bShowTrackChanges && bShowInlineTooltips )
                     sText = lcl_GetRedlineHelp(*aContentAtPos.aFnd.pRedl, bBalloon);
                break;
            }

            case IsAttrAtPos::ToxMark:
                sText = aContentAtPos.sStr;
                if( !sText.isEmpty() && aContentAtPos.pFndTextAttr )
                {
                    const SwTOXType* pTType = aContentAtPos.pFndTextAttr->
                                        GetTOXMark().GetTOXType();
                    if( pTType && !pTType->GetTypeName().isEmpty() )
                    {
                        sText = ": " + sText;
                        sText = pTType->GetTypeName() + sText;
                    }
                }
                break;

            case IsAttrAtPos::RefMark:
                if(aContentAtPos.aFnd.pAttr)
                {
                    sText = SwResId(STR_CONTENT_TYPE_SINGLE_REFERENCE) + ": ";
                    sText += static_cast<const SwFormatRefMark*>(aContentAtPos.aFnd.pAttr)->GetRefName();
                }
            break;

            default:
                {
                    SwModuleOptions* pModOpt = SW_MOD()->GetModuleConfig();
                    if(!pModOpt->IsHideFieldTips())
                    {
                        const SwField* pField = aContentAtPos.aFnd.pField;
                        switch( pField->Which() )
                        {
                        case SwFieldIds::SetExp:
                        case SwFieldIds::Table:
                        case SwFieldIds::GetExp:
                        {
                            sal_uInt16 nOldSubType = pField->GetSubType();
                            const_cast<SwField*>(pField)->SetSubType(nsSwExtendedSubType::SUB_CMD);
                            sText = pField->ExpandField(true, rSh.GetLayout());
                            const_cast<SwField*>(pField)->SetSubType(nOldSubType);
                        }
                        break;

                        case SwFieldIds::Postit:
                            {
                                break;
                            }
                        case SwFieldIds::Input:  // BubbleHelp, because the suggestion could be quite long
                            bBalloon = true;
                            [[fallthrough]];
                        case SwFieldIds::Dropdown:
                        case SwFieldIds::JumpEdit:
                            sText = pField->GetPar2();
                            break;

                        case SwFieldIds::Database:
                            sText = pField->GetFieldName();
                            break;

                        case SwFieldIds::User:
                        case SwFieldIds::HiddenText:
                            sText = pField->GetPar1();
                            break;

                        case SwFieldIds::DocStat:
                            break;

                        case SwFieldIds::Macro:
                            sText = static_cast<const SwMacroField*>(pField)->GetMacro();
                            break;

                        case SwFieldIds::GetRef:
                        {
                            // #i85090#
                            const SwGetRefField* pRefField( dynamic_cast<const SwGetRefField*>(pField) );
                            OSL_ENSURE( pRefField,
                                    "<SwEditWin::RequestHelp(..)> - unexpected type of <pField>" );
                            if ( pRefField )
                            {
                                if ( pRefField->IsRefToHeadingCrossRefBookmark() ||
                                     pRefField->IsRefToNumItemCrossRefBookmark() )
                                {
                                    sText = pRefField->GetExpandedTextOfReferencedTextNode(*rSh.GetLayout());
                                    if ( sText.getLength() > 80  )
                                    {
                                        sText = sText.copy(0, 80) + "...";
                                    }
                                }
                                else
                                {
                                    sText = static_cast<const SwGetRefField*>(pField)->GetSetRefName();
                                }
                            }
                            break;
                        }

                        default: break;
                        }
                    }

                    if( sText.isEmpty() )
                    {
                        const bool bShowTrackChanges = IDocumentRedlineAccess::IsShowChanges( m_rView.GetDocShell()->GetDoc()->getIDocumentRedlineAccess().GetRedlineFlags() );
                        const bool bShowInlineTooltips = rSh.GetViewOptions()->IsShowInlineTooltips();
                        if ( bShowTrackChanges && bShowInlineTooltips )
                        {
                            aContentAtPos.eContentAtPos = IsAttrAtPos::Redline;
                            if( rSh.GetContentAtPos( aPos, aContentAtPos, false, &aFieldRect ) )
                                sText = lcl_GetRedlineHelp(*aContentAtPos.aFnd.pRedl, bBalloon);
                        }
                    }
                }
            }
            if (!sText.isEmpty())
            {
                tools::Rectangle aRect( aFieldRect.SVRect() );
                Point aPt( OutputToScreenPixel( LogicToPixel( aRect.TopLeft() )));
                aRect.SetLeft( aPt.X() );
                aRect.SetTop( aPt.Y() );
                aPt = OutputToScreenPixel( LogicToPixel( aRect.BottomRight() ));
                aRect.SetRight( aPt.X() );
                aRect.SetBottom( aPt.Y() );

                // tdf#136336 ensure tooltip area surrounds the current mouse position with at least a pixel margin
                aRect.Union(tools::Rectangle(rEvt.GetMousePosPixel(), Size(1, 1)));
                aRect.AdjustLeft(-1);
                aRect.AdjustRight(1);
                aRect.AdjustTop(-1);
                aRect.AdjustBottom(1);

                if( bBalloon )
                    Help::ShowBalloon( this, rEvt.GetMousePosPixel(), aRect, sText );
                else
                {
                    // the show the help
                    OUString sDisplayText(ClipLongToolTip(sText));
                    Help::ShowQuickHelp(this, aRect, sDisplayText, nStyle);
                }
            }

            bContinue = false;
        }

    }

    if( bContinue )
        Window::RequestHelp( rEvt );
}

void SwEditWin::PrePaint(vcl::RenderContext& /*rRenderContext*/)
{
    SwWrtShell* pWrtShell = GetView().GetWrtShellPtr();

    if(pWrtShell)
    {
        pWrtShell->PrePaint();
    }
}

void SwEditWin::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    SwWrtShell* pWrtShell = GetView().GetWrtShellPtr();
    if(!pWrtShell)
        return;
    bool bPaintShadowCursor = false;
    if( m_pShadCursor )
    {
        tools::Rectangle aRect( m_pShadCursor->GetRect());
        // fully resides inside?
        if( rRect.IsInside( aRect ) )
        {
            // then cancel
            m_pShadCursor.reset();
        }
        else if( rRect.IsOver( aRect ))
        {
            // resides somewhat above, then everything is clipped outside
            // and we have to make the "inner part" at the end of the
            // Paint visible again. Otherwise Paint errors occur!
            bPaintShadowCursor = true;
        }
    }

    if ( GetView().GetVisArea().GetWidth()  <= 0 ||
              GetView().GetVisArea().GetHeight() <= 0 )
        Invalidate( rRect );
    else
    {
        pWrtShell->setOutputToWindow(true);
        pWrtShell->Paint(rRenderContext, rRect);
        pWrtShell->setOutputToWindow(false);
    }

    if( bPaintShadowCursor )
        m_pShadCursor->Paint();
}

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