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

#include "SidebarTxtControl.hxx"

#include <docsh.hxx>
#include <doc.hxx>

#include <PostItMgr.hxx>

#include <cmdid.h>
#include <strings.hrc>

#include <unotools/securityoptions.hxx>
#include <officecfg/Office/Common.hxx>

#include <sfx2/viewfrm.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/sfxhelp.hxx>

#include <vcl/commandevent.hxx>
#include <vcl/event.hxx>
#include <vcl/ptrstyle.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/gradient.hxx>
#include <vcl/settings.hxx>

#include <editeng/outliner.hxx>
#include <editeng/editeng.hxx>
#include <editeng/editview.hxx>
#include <editeng/flditem.hxx>

#include <uitool.hxx>
#include <view.hxx>
#include <wrtsh.hxx>
#include <AnnotationWin.hxx>
#include <IDocumentDeviceAccess.hxx>
#if ENABLE_YRS
#include <IDocumentState.hxx>
#include <swmodule.hxx>
#endif
#include <redline.hxx>
#include <memory>

namespace sw::sidebarwindows {

SidebarTextControl::SidebarTextControl(sw::annotation::SwAnnotationWin& rSidebarWin,
                                       SwView& rDocView,
                                       SwPostItMgr& rPostItMgr)
    : mrSidebarWin(rSidebarWin)
    , mrDocView(rDocView)
    , mrPostItMgr(rPostItMgr)
    , mbMouseDownGainingFocus(false)
{
}

EditView* SidebarTextControl::GetEditView() const
{
    OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView();
    if (!pOutlinerView)
        return nullptr;
    return &pOutlinerView->GetEditView();
}

EditEngine* SidebarTextControl::GetEditEngine() const
{
    OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView();
    if (!pOutlinerView)
        return nullptr;
    return &pOutlinerView->GetEditView().getEditEngine();
}

void SidebarTextControl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
{
    Size aSize(0, 0);
    pDrawingArea->set_size_request(aSize.Width(), aSize.Height());

    SetOutputSizePixel(aSize);

    weld::CustomWidgetController::SetDrawingArea(pDrawingArea);

    EnableRTL(false);

    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    Color aBgColor = rStyleSettings.GetWindowColor();

    OutputDevice& rDevice = pDrawingArea->get_ref_device();

    rDevice.SetMapMode(MapMode(MapUnit::MapTwip));
    rDevice.SetBackground(aBgColor);

    Size aOutputSize(rDevice.PixelToLogic(aSize));

    EditView* pEditView = GetEditView();
    pEditView->setEditViewCallbacks(this);

    EditEngine& rEditEngine = pEditView->getEditEngine();
    // For tdf#143443 note we want an 'infinite' height initially (which is the
    // editengines default). For tdf#144686 it is helpful if the initial width
    // is the "SidebarWidth" so the calculated text height is always meaningful
    // for layout in the sidebar.
    Size aPaperSize(mrPostItMgr.GetSidebarWidth(), rEditEngine.GetPaperSize().Height());
    rEditEngine.SetPaperSize(aPaperSize);
    rEditEngine.SetRefDevice(mrDocView.GetWrtShell().getIDocumentDeviceAccess().getReferenceDevice(false));

    pEditView->SetOutputArea(tools::Rectangle(Point(0, 0), aOutputSize));
    pEditView->SetBackgroundColor(aBgColor);

    pDrawingArea->set_cursor(PointerStyle::Text);

#if !ENABLE_WASM_STRIP_ACCESSIBILITY
    InitAccessible();
#endif
}

void SidebarTextControl::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark)
{
    Point aMousePos = EditViewOutputDevice().PixelToLogic(rPosition);
    m_xEditView->SetCursorLogicPosition(aMousePos, bPoint, bClearMark);
}

void SidebarTextControl::GetFocus()
{
    WeldEditView::GetFocus();
    if ( !mrSidebarWin.IsMouseOver() )
        Invalidate();
    mrSidebarWin.SetActiveSidebarWin();
}

void SidebarTextControl::LoseFocus()
{
    // write the visible text back into the SwField
    mrSidebarWin.UpdateData();

    WeldEditView::LoseFocus();
    if ( !mrSidebarWin.IsMouseOver() )
    {
        Invalidate();
    }
    // set false for autoscroll to typing location
    mrSidebarWin.LockView(false);
}

OUString SidebarTextControl::RequestHelp(tools::Rectangle& rHelpRect)
{
    if (EditView* pEditView = GetEditView())
    {
        Point aPos = rHelpRect.TopLeft();

        const OutputDevice& rOutDev = pEditView->GetOutputDevice();
        Point aLogicClick = rOutDev.PixelToLogic(aPos);
        const SvxFieldItem* pItem = pEditView->GetField(aLogicClick);
        if (pItem)
        {
            const SvxFieldData* pField = pItem->GetField();
            const SvxURLField* pURL = dynamic_cast<const SvxURLField*>( pField  );
            if (pURL)
            {
                rHelpRect = tools::Rectangle(aPos, Size(50, 10));
                return SfxHelp::GetURLHelpText(pURL->GetURL());
            }
        }
    }

    TranslateId pResId;
    switch( mrSidebarWin.GetLayoutStatus() )
    {
        case SwPostItHelper::INSERTED:  pResId = STR_REDLINE_INSERT; break;
        case SwPostItHelper::DELETED:   pResId = STR_REDLINE_DELETE; break;
        default: break;
    }

    SwContentAtPos aContentAtPos( IsAttrAtPos::Redline );
    if ( pResId &&
         mrDocView.GetWrtShell().GetContentAtPos( mrSidebarWin.GetAnchorPos(), aContentAtPos ) )
    {
        OUString sText = SwResId(pResId) + ": " +
                        aContentAtPos.aFnd.pRedl->GetAuthorString() + " - " +
                        GetAppLangDateTimeString( aContentAtPos.aFnd.pRedl->GetTimeStamp() );
        return sText;
    }

    return OUString();
}

#if ENABLE_YRS
void SidebarTextControl::EditViewInvalidate(const tools::Rectangle& rRect)
{
    SAL_INFO("sw.yrs", "YRS EditViewInvalidate");
    mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifyCursorUpdate();
    mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsCommitModified(true);
    return WeldEditView::EditViewInvalidate(rRect);
}

void SidebarTextControl::EditViewSelectionChange()
{
    SAL_INFO("sw.yrs", "YRS EditViewSelectionChange");
    mrDocView.GetDocShell()->GetDoc()->getIDocumentState().YrsNotifyCursorUpdate();
    return WeldEditView::EditViewSelectionChange();
}
#endif

void SidebarTextControl::EditViewScrollStateChange()
{
    mrSidebarWin.SetScrollbar();
}

void SidebarTextControl::DrawForPage(OutputDevice* pDev, const Point& rPt)
{
    //Take the control's height, but overwrite the scrollbar area if there was one
    OutputDevice& rDevice = GetDrawingArea()->get_ref_device();
    Size aSize(rDevice.PixelToLogic(GetOutputSizePixel()));

    if (OutlinerView* pOutlinerView = mrSidebarWin.GetOutlinerView())
    {
        pOutlinerView->GetOutliner().SetPaperSize(aSize);
        pOutlinerView->GetOutliner().DrawText_ToRectangle(*pDev, tools::Rectangle(rPt, aSize));
    }

    if ( mrSidebarWin.GetLayoutStatus()!=SwPostItHelper::DELETED )
        return;

    auto popIt = pDev->ScopedPush(vcl::PushFlags::LINECOLOR);

    pDev->SetLineColor(mrSidebarWin.GetChangeColor());
    Point aBottomRight(rPt);
    aBottomRight.Move(aSize);
    pDev->DrawLine(rPt,  aBottomRight);

    Point aTopRight(rPt);
    aTopRight.Move(Size(aSize.Width(), 0));

    Point aBottomLeft(rPt);
    aBottomLeft.Move(Size(0, aSize.Height()));

    pDev->DrawLine(aTopRight, aBottomLeft);
}

void SidebarTextControl::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    Size aSize = GetOutputSizePixel();
    Point aPos;

    if (!rRenderContext.GetSettings().GetStyleSettings().GetHighContrastMode())
    {
        if (mrSidebarWin.IsMouseOverSidebarWin() || HasFocus())
        {
            rRenderContext.DrawGradient(tools::Rectangle(aPos, rRenderContext.PixelToLogic(aSize)),
                                        Gradient(css::awt::GradientStyle_LINEAR, mrSidebarWin.ColorDark(), mrSidebarWin.ColorDark()));
        }
        else
        {
            rRenderContext.DrawGradient(tools::Rectangle(aPos, rRenderContext.PixelToLogic(aSize)),
                           Gradient(css::awt::GradientStyle_LINEAR, mrSidebarWin.ColorLight(), mrSidebarWin.ColorDark()));
        }
    }

    DoPaint(rRenderContext, rRect);

#if ENABLE_YRS
    if (EditView *const pEditView{GetEditView()})
    {
        rRenderContext.Push(vcl::PushFlags::ALL);
        rRenderContext.SetClipRegion();

        ::std::vector<::std::pair<OUString, ::std::vector<tools::Rectangle>>> rects;
        pEditView->YrsGetSelectionRectangles(rects);
        for (auto const& it : rects)
        {
            ::std::size_t const authorId{SwModule::get()->InsertRedlineAuthor(it.first)};
            Color const color{SwPostItMgr::GetColorAnchor(authorId)};
            PaintSelection(rRenderContext, rRect, it.second, color);
        }

        rRenderContext.Pop();
    }
#endif

    if (mrSidebarWin.GetLayoutStatus() != SwPostItHelper::DELETED)
        return;

    const AntialiasingFlags nFormerAntialiasing( rRenderContext.GetAntialiasing() );
    const bool bIsAntiAliasing = officecfg::Office::Common::Drawinglayer::AntiAliasing::get();
    if ( bIsAntiAliasing )
        rRenderContext.SetAntialiasing(AntialiasingFlags::Enable);
    rRenderContext.SetLineColor(mrSidebarWin.GetChangeColor());
    rRenderContext.DrawLine(rRenderContext.PixelToLogic(aPos),
                            rRenderContext.PixelToLogic(aPos + Point(aSize.Width(),
                                                                     aSize.Height() * 0.95)));
    rRenderContext.DrawLine(rRenderContext.PixelToLogic(aPos + Point(aSize.Width(),
                                                                     0)),
                            rRenderContext.PixelToLogic(aPos + Point(0,
                                                                     aSize.Height() * 0.95)));
    if ( bIsAntiAliasing )
        rRenderContext.SetAntialiasing(nFormerAntialiasing);
}

void SidebarTextControl::MakeVisible()
{
    //let's make sure we see our note
    mrPostItMgr.MakeVisible(&mrSidebarWin);
}

bool SidebarTextControl::KeyInput( const KeyEvent& rKeyEvt )
{
    const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode();
    const sal_uInt16 nKey = rKeyCode.GetCode();
    if (nKey == KEY_F12 && getenv("SW_DEBUG"))
    {
        if (rKeyEvt.GetKeyCode().IsShift())
        {
            mrDocView.GetDocShell()->GetDoc()->dumpAsXml();
            return true;
        }
    }

    bool bDone = false;

    if ( ( rKeyCode.IsMod1() && rKeyCode.IsMod2() ) &&
         ( (nKey == KEY_PAGEUP) || (nKey == KEY_PAGEDOWN) ) )
    {
        mrSidebarWin.SwitchToPostIt(nKey);
        bDone = true;
    }
    else if ( nKey == KEY_ESCAPE ||
              ( rKeyCode.IsMod1() &&
                ( nKey == KEY_PAGEUP ||
                  nKey == KEY_PAGEDOWN ) ) )
    {
        mrSidebarWin.SwitchToFieldPos();
        bDone = true;
    }
    else if ( rKeyCode.GetFullCode() == KEY_INSERT )
    {
        mrSidebarWin.ToggleInsMode();
        bDone = true;
    }
    else
    {
        tools::Long aOldHeight = mrSidebarWin.GetPostItTextHeight();

        /// HACK: need to switch off processing of Undo/Redo in Outliner
        if ( !( (nKey == KEY_Z || nKey == KEY_Y) && rKeyCode.IsMod1()) )
        {
            bool bIsProtected = mrSidebarWin.IsReadOnlyOrProtected();
            if ( !bIsProtected || !EditEngine::DoesKeyChangeText(rKeyEvt) )
            {
                EditView* pEditView = GetEditView();
                bDone = pEditView && pEditView->PostKeyEvent(rKeyEvt);
            }
            else
                mrDocView.GetWrtShell().InfoReadOnlyDialog(false);
        }
        if (bDone)
        {
            MakeVisible();
            mrSidebarWin.ResizeIfNecessary( aOldHeight, mrSidebarWin.GetPostItTextHeight() );
        }
        else
        {
            // write back data first when showing navigator
            if ( nKey==KEY_F5 )
                mrSidebarWin.UpdateData();
            bDone = mrDocView.KeyInput(rKeyEvt);
        }
    }

    mrDocView.GetViewFrame().GetBindings().InvalidateAll(false);

    return bDone;
}

bool SidebarTextControl::MouseButtonDown(const MouseEvent& rMEvt)
{
    if (EditView* pEditView = GetEditView())
    {
        bool bExecuteMod = SvtSecurityOptions::IsOptionSet( SvtSecurityOptions::EOption::CtrlClickHyperlink);

        if ( !bExecuteMod || (rMEvt.GetModifier() == KEY_MOD1))
        {
            const OutputDevice& rOutDev = pEditView->GetOutputDevice();
            Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel());
            if (const SvxFieldItem* pItem = pEditView->GetField(aLogicClick))
            {
                const SvxFieldData* pField = pItem->GetField();
                const SvxURLField* pURL = dynamic_cast<const SvxURLField*>( pField  );
                if ( pURL )
                {
                    pEditView->MouseButtonDown( rMEvt );
                    SwWrtShell &rSh = mrDocView.GetWrtShell();
                    const OUString& sURL( pURL->GetURL() );
                    const OUString& sTarget( pURL->GetTargetFrame() );
                    ::LoadURL(rSh, sURL, LoadUrlFlags::NONE, sTarget);
                    return true;
                }
            }
        }
    }

    mbMouseDownGainingFocus = !HasFocus();
    GrabFocus();

    bool bRet = WeldEditView::MouseButtonDown(rMEvt);

    mrDocView.GetViewFrame().GetBindings().InvalidateAll(false);

    return bRet;
}

bool SidebarTextControl::MouseButtonUp(const MouseEvent& rMEvt)
{
    bool bRet = WeldEditView::MouseButtonUp(rMEvt);

    if (mbMouseDownGainingFocus)
    {
        MakeVisible();
        mbMouseDownGainingFocus = false;
    }

    return bRet;
}

bool SidebarTextControl::MouseMove(const MouseEvent& rMEvt)
{
    if (rMEvt.IsEnterWindow())
        GetDrawingArea()->set_cursor(PointerStyle::Text);
    return WeldEditView::MouseMove(rMEvt);
}

IMPL_LINK( SidebarTextControl, OnlineSpellCallback, SpellCallbackInfo&, rInfo, void )
{
    if ( rInfo.nCommand == SpellCallbackCommand::STARTSPELLDLG )
    {
        mrDocView.GetViewFrame().GetDispatcher()->Execute( FN_SPELL_GRAMMAR_DIALOG, SfxCallMode::ASYNCHRON);
    }
}

bool SidebarTextControl::Command( const CommandEvent& rCEvt )
{
    EditView* pEditView = GetEditView();

    if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
    {
        if (IsMouseCaptured())
            ReleaseMouse();

        bool bUsedSpellPopup = false;
        if ( !mrSidebarWin.IsReadOnlyOrProtected() &&
             pEditView &&
             pEditView->IsWrongSpelledWordAtPos( rCEvt.GetMousePosPixel(), true ))
        {
            Link<SpellCallbackInfo&,void> aLink = LINK(this, SidebarTextControl, OnlineSpellCallback);
            bUsedSpellPopup = pEditView->ExecuteSpellPopup(rCEvt.GetMousePosPixel(), aLink);
        }
        if (!bUsedSpellPopup)
        {
            Point aPos;
            if (rCEvt.IsMouseEvent())
                aPos = rCEvt.GetMousePosPixel();
            else
            {
                const Size aSize = GetOutputSizePixel();
                aPos = Point( aSize.getWidth()/2, aSize.getHeight()/2 );
            }

            // tdf#164072: don't use SwAnnotationWin itself as popup parent because it
            // may get disposed while the popup menu is still executed
            vcl::Window* pParentWin = mrSidebarWin.GetParent();
            assert(pParentWin);
            aPos += mrSidebarWin.GetWindowExtentsRelative(*pParentWin).TopLeft();
            SfxDispatcher::ExecutePopup(pParentWin, &aPos);
        }
        return true;
    }
    else if (rCEvt.GetCommand() == CommandEventId::Wheel)
    {
        // if no scrollbar, or extra keys held scroll the document and consume
        // this event, otherwise don't consume and let the event get to the
        // surrounding scrolled window
        if (!mrSidebarWin.IsScrollbarVisible())
        {
            mrDocView.HandleWheelCommands(rCEvt);
            return true;
        }
        else
        {
            const CommandWheelData* pData = rCEvt.GetWheelData();
            if (pData->IsShift() || pData->IsMod1() || pData->IsMod2())
            {
                mrDocView.HandleWheelCommands(rCEvt);
                return true;
            }
        }
    }

    return WeldEditView::Command(rCEvt);
}

} // end of namespace sw::sidebarwindows

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