/* -*- 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 <com/sun/star/drawing/XDrawView.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/geometry/RealPoint2D.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/document/XEventBroadcaster.hpp>
#include <com/sun/star/office/XAnnotationAccess.hpp>
#include <comphelper/lok.hxx>
#include <svx/svxids.hrc>
#include <svx/svditer.hxx>

#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <tools/gen.hxx>

#include <sal/macros.h>
#include <svl/itempool.hxx>
#include <svl/intitem.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/useroptions.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/saveopt.hxx>

#include <tools/datetime.hxx>
#include <tools/UnitConversion.hxx>
#include <comphelper/diagnose_ex.hxx>

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

#include <editeng/editeng.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/postitem.hxx>

#include <svx/postattr.hxx>

#include <annotationmanager.hxx>
#include "annotationmanagerimpl.hxx"
#include "annotationwindow.hxx"
#include <strings.hrc>

#include <Annotation.hxx>
#include "AnnotationPopup.hxx"
#include <DrawDocShell.hxx>
#include <DrawViewShell.hxx>
#include <DrawController.hxx>
#include <sdresid.hxx>
#include <EventMultiplexer.hxx>
#include <ViewShellBase.hxx>
#include <sdpage.hxx>
#include <drawdoc.hxx>
#include <svx/annotation/TextAPI.hxx>
#include <svx/annotation/AnnotationObject.hxx>
#include <svx/annotation/Annotation.hxx>
#include <svx/annotation/ObjectAnnotationData.hxx>
#include <optsitem.hxx>
#include <sdmod.hxx>

#include <svx/svdobj.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdorect.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdotext.hxx>
#include <svx/svdograf.hxx>

#include <svx/xfillit0.hxx>
#include <svx/xflclit.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/xfltrit.hxx>
#include <svx/xlntrit.hxx>

#include <cmath>
#include <memory>

using namespace css;

namespace sd
{

SfxItemPool* GetAnnotationPool()
{
    static rtl::Reference<SfxItemPool> s_pAnnotationPool;
    if( !s_pAnnotationPool )
    {
        s_pAnnotationPool = EditEngine::CreatePool();
        s_pAnnotationPool->SetUserDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT));

        vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() );
        s_pAnnotationPool->SetUserDefaultItem(SvxFontItem(aAppFont.GetFamilyTypeMaybeAskConfig(),aAppFont.GetFamilyName(),u""_ustr,PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO));
    }

    return s_pAnnotationPool.get();
}

static SfxBindings* getBindings( ViewShellBase const & rBase )
{
    auto pMainViewShell = rBase.GetMainViewShell().get();
    if( pMainViewShell && pMainViewShell->GetViewFrame() )
        return &pMainViewShell->GetViewFrame()->GetBindings();

    return nullptr;
}

static SfxDispatcher* getDispatcher( ViewShellBase const & rBase )
{
    auto pMainViewShell = rBase.GetMainViewShell().get();
    if( pMainViewShell && pMainViewShell->GetViewFrame() )
        return pMainViewShell->GetViewFrame()->GetDispatcher();

    return nullptr;
}

css::util::DateTime getCurrentDateTime()
{
    DateTime aCurrentDate( DateTime::SYSTEM );
    return css::util::DateTime( 0, aCurrentDate.GetSec(),
            aCurrentDate.GetMin(), aCurrentDate.GetHour(),
            aCurrentDate.GetDay(), aCurrentDate.GetMonth(),
            aCurrentDate.GetYear(), false );
}

OUString getAnnotationDateTimeString(const uno::Reference<office::XAnnotation>& xAnnotation)
{
    OUString sRet;
    if( xAnnotation.is() )
    {
        const SvtSysLocale aSysLocale;
        const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();

        css::util::DateTime aDateTime( xAnnotation->getDateTime() );

        Date aSysDate( Date::SYSTEM );
        Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year );
        if (aDate==aSysDate)
            sRet = SdResId(STR_ANNOTATION_TODAY);
        else if (aDate == (aSysDate-1))
            sRet = SdResId(STR_ANNOTATION_YESTERDAY);
        else if (aDate.IsValidAndGregorian() )
            sRet = rLocalData.getDate(aDate);

        ::tools::Time aTime( aDateTime );
        if(aTime.GetTime() != 0)
            sRet += " "  + rLocalData.getTime( aTime,false );
    }
    return sRet;
}

AnnotationManagerImpl::AnnotationManagerImpl( ViewShellBase& rViewShellBase )
: mrBase( rViewShellBase )
, mpDoc( rViewShellBase.GetDocument() )
, mbShowAnnotations( true )
, mnUpdateTagsEvent( nullptr )
{
    if (SdOptions* pOptions = SdModule::get()->GetSdOptions(mpDoc->GetDocumentType()))
        mbShowAnnotations = pOptions->IsShowComments();
}

void AnnotationManagerImpl::init()
{
    // get current controller and initialize listeners
    try
    {
        addListener();
        mxView = mrBase.GetDrawController();
    }
    catch (uno::Exception&)
    {
        TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::AnnotationManagerImpl()" );
    }

    try
    {
        uno::Reference<document::XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW);
        uno::Reference<document::XEventListener> xListener( this );
        xModel->addEventListener( xListener );
    }
    catch (uno::Exception&)
    {
    }
}

// WeakComponentImplHelper
void AnnotationManagerImpl::disposing (std::unique_lock<std::mutex>&)
{
    for (sal_uInt16 i = 0; i < mpDoc->GetPageCount(); i++)
    {
        SdrPage* pPage = mpDoc->GetPage(i);
        SdrObjListIter aIterator(pPage, SdrIterMode::DeepWithGroups);
        while (aIterator.IsMore())
        {
            SdrObject* pObject = aIterator.Next();
            if (pObject)
            {
                auto& xAnnotationData = pObject->getAnnotationData();
                if (xAnnotationData)
                    xAnnotationData->closePopup();
            }
        }
    }

    try
    {
        uno::Reference<document::XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW);
        uno::Reference<document::XEventListener> xListener( this );
        xModel->removeEventListener( xListener );
    }
    catch (uno::Exception&)
    {
    }

    removeListener();

    if( mnUpdateTagsEvent )
    {
        Application::RemoveUserEvent( mnUpdateTagsEvent );
        mnUpdateTagsEvent = nullptr;
    }

    mxView.clear();
    mxCurrentPage.clear();
}

// XEventListener
void SAL_CALL AnnotationManagerImpl::notifyEvent( const css::document::EventObject& aEvent )
{
    if( !(aEvent.EventName == "OnAnnotationInserted" || aEvent.EventName == "OnAnnotationRemoved" || aEvent.EventName == "OnAnnotationChanged") )
        return;

    // AnnotationInsertion and modification is not handled here because when
    // a new annotation is inserted, it consists of OnAnnotationInserted
    // followed by a chain of OnAnnotationChanged (called for setting each
    // of the annotation attributes - author, text etc.). This is not what a
    // LOK client wants. So only handle removal here as annotation removal
    // consists of only one event - 'OnAnnotationRemoved'
    if ( aEvent.EventName == "OnAnnotationRemoved" )
    {
        uno::Reference<office::XAnnotation> xAnnotation( aEvent.Source, uno::UNO_QUERY );
        if ( auto pAnnotation = dynamic_cast<sd::Annotation*>(xAnnotation.get()) )
        {
            LOKCommentNotify(sdr::annotation::CommentNotificationType::Remove, &mrBase, *pAnnotation);
        }
    }

    UpdateTags();
}

void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ )
{
}

rtl::Reference<sdr::annotation::Annotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId)
{
    SdPage* pPage = nullptr;
    do
    {
        pPage = GetNextPage(pPage, true);
        if( pPage && !pPage->getAnnotations().empty() )
        {
            sdr::annotation::AnnotationVector aAnnotations(pPage->getAnnotations());
            auto iterator = std::find_if(aAnnotations.begin(), aAnnotations.end(),
                [nAnnotationId](rtl::Reference<sdr::annotation::Annotation> const& xAnnotation)
                {
                        return xAnnotation->GetId() == nAnnotationId;
                });
            if (iterator != aAnnotations.end())
                return *iterator;
        }
    } while(pPage);

    rtl::Reference<sdr::annotation::Annotation> xAnnotationEmpty;
    return xAnnotationEmpty;
}

void AnnotationManagerImpl::ShowAnnotations( bool bShow )
{
    // enforce show annotations if a new annotation is inserted
    if( mbShowAnnotations != bShow )
    {
        mbShowAnnotations = bShow;

        if (SdOptions* pOptions = SdModule::get()->GetSdOptions(mpDoc->GetDocumentType()))
            pOptions->SetShowComments( mbShowAnnotations );

        UpdateTags();
    }
}

bool AnnotationManagerImpl::isVisibleAnnotations()
{
    return (!comphelper::LibreOfficeKit::isActive()
            || comphelper::LibreOfficeKit::isTiledAnnotations());
}

void AnnotationManagerImpl::ExecuteAnnotation(SfxRequest const & rReq )
{
    switch( rReq.GetSlot() )
    {
    case SID_INSERT_POSTIT:
        ExecuteInsertAnnotation( rReq );
        break;
    case SID_DELETE_POSTIT:
    case SID_DELETEALL_POSTIT:
    case SID_DELETEALLBYAUTHOR_POSTIT:
        ExecuteDeleteAnnotation( rReq );
        break;
    case SID_EDIT_POSTIT:
        ExecuteEditAnnotation( rReq );
        break;
    case SID_PREVIOUS_POSTIT:
    case SID_NEXT_POSTIT:
        SelectNextAnnotation( rReq.GetSlot() == SID_NEXT_POSTIT );
        break;
    case SID_REPLYTO_POSTIT:
        ExecuteReplyToAnnotation( rReq );
        break;
    case SID_TOGGLE_NOTES:
        ShowAnnotations( !mbShowAnnotations );
        break;
    }
}

void AnnotationManagerImpl::ExecuteInsertAnnotation(SfxRequest const & rReq)
{
    if (isVisibleAnnotations())
        ShowAnnotations(true);

    const SfxItemSet* pArgs = rReq.GetArgs();
    OUString sText;
    if (pArgs)
    {
        if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
        {
            sText = pPoolItem->GetValue();
        }
    }

    InsertAnnotation(sText);
}

void AnnotationManagerImpl::ExecuteDeleteAnnotation(SfxRequest const & rReq)
{
    const SfxItemSet* pArgs = rReq.GetArgs();

    switch( rReq.GetSlot() )
    {
    case SID_DELETEALL_POSTIT:
        DeleteAllAnnotations();
        break;
    case SID_DELETEALLBYAUTHOR_POSTIT:
        if( pArgs )
        {
            const SfxPoolItem*  pPoolItem = nullptr;
            if( SfxItemState::SET == pArgs->GetItemState( SID_DELETEALLBYAUTHOR_POSTIT, true, &pPoolItem ) )
            {
                OUString sAuthor( static_cast<const SfxStringItem*>( pPoolItem )->GetValue() );
                DeleteAnnotationsByAuthor( sAuthor );
            }
        }
        break;
    case SID_DELETE_POSTIT:
        {
            rtl::Reference<sdr::annotation::Annotation> xAnnotation;
            sal_uInt32 nId = 0;
            if( pArgs )
            {
                const SfxPoolItem*  pPoolItem = nullptr;
                if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) )
                {
                    uno::Reference<office::XAnnotation> xTmpAnnotation;
                    if (static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xTmpAnnotation)
                    {
                        xAnnotation = dynamic_cast<sdr::annotation::Annotation*>(xTmpAnnotation.get());
                        assert(bool(xAnnotation) == bool(xTmpAnnotation) && "must be of concrete type sd::Annotation");
                    }
                }
                if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
                    nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
            }

            if (nId != 0)
                xAnnotation = GetAnnotationById(nId);
            else if( !xAnnotation.is() )
                GetSelectedAnnotation(xAnnotation);

            DeleteAnnotation(xAnnotation);
        }
        break;
    }

    UpdateTags();
}

void AnnotationManagerImpl::ExecuteEditAnnotation(SfxRequest const & rReq)
{
    const SfxItemSet* pArgs = rReq.GetArgs();
    rtl::Reference<sdr::annotation::Annotation> xAnnotation;
    OUString sText;
    sal_Int32 nPositionX = -1;
    sal_Int32 nPositionY = -1;

    if (!pArgs)
        return;

    if (mpDoc->IsUndoEnabled())
        mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT));

    if (const SvxPostItIdItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_ID))
    {
        sal_uInt32 nId = pPoolItem->GetValue().toUInt32();
        xAnnotation = GetAnnotationById(nId);
    }
    if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
        sText = pPoolItem->GetValue();

    if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_X))
        nPositionX = pPoolItem->GetValue();

    if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_Y))
        nPositionY = pPoolItem->GetValue();

    if (xAnnotation.is())
    {
        auto pSdAnnotation = static_cast<sd::Annotation*>(xAnnotation.get());
        pSdAnnotation->createChangeUndo();

        SdrObject* pObject = xAnnotation->findAnnotationObject();
        if (pObject && nPositionX >= 0 && nPositionY >= 0)
        {
            double fX = convertTwipToMm100<double>(nPositionX);
            double fY = convertTwipToMm100<double>(nPositionY);

            ::tools::Long deltaX = std::round(fX - (pSdAnnotation->getPosition().X * 100.0));
            ::tools::Long deltaY = std::round(fY - (pSdAnnotation->getPosition().Y * 100.0));

            pObject->Move({ deltaX, deltaY });
        }

        if (!sText.isEmpty())
        {
            // TODO: Not allow other authors to change others' comments ?
            uno::Reference<text::XText> xText(xAnnotation->getTextRange());
            xText->setString(sText);
        }

        LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Modify, *xAnnotation);
    }

    if (mpDoc->IsUndoEnabled())
        mpDoc->EndUndo();

    UpdateTags(true);
}

void AnnotationManagerImpl::InsertAnnotation(const OUString& rText)
{
    SdPage* pPage = GetCurrentPage();
    if (!pPage)
        return;

    if (mpDoc->IsUndoEnabled())
        mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_INSERT));

    // find free space for new annotation
    int y = 0;
    int x = 0;

    sdr::annotation::AnnotationVector aAnnotations(pPage->getAnnotations());
    if (!aAnnotations.empty())
    {
        const int fPageWidth = pPage->GetSize().Width();
        const int fWidth = 1000;
        const int fHeight = 800;

        while (true)
        {
            ::tools::Rectangle aNewRect(Point(x, y), Size(fWidth, fHeight));
            bool bFree = true;

            for (const auto& rxAnnotation : aAnnotations)
            {
                geometry::RealPoint2D aRealPoint2D(rxAnnotation->getPosition());
                Point aPoint(::tools::Long(aRealPoint2D.X * 100.0), ::tools::Long(aRealPoint2D.Y * 100.0));
                Size aSize(fWidth, fHeight);

                if (aNewRect.Overlaps(::tools::Rectangle(aPoint, aSize)))
                {
                    bFree = false;
                    break;
                }
            }

            if (!bFree)
            {
                x += fWidth;
                if (x > fPageWidth)
                {
                    x = 0;
                    y += fHeight;
                }
            }
            else
            {
                break;
            }
        }
    }

    rtl::Reference<sdr::annotation::Annotation> xAnnotation = pPage->createAnnotation();

    OUString sAuthor;
    if (comphelper::LibreOfficeKit::isActive())
        sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
    else
    {
        SvtUserOptions aUserOptions;
        sAuthor = aUserOptions.GetFullName();
        xAnnotation->setInitials( aUserOptions.GetID() );
    }

    if (!rText.isEmpty())
    {
        uno::Reference<text::XText> xText(xAnnotation->getTextRange());
        xText->setString(rText);
    }

    // set current author to new annotation
    xAnnotation->setAuthor( sAuthor );
    // set current time to new annotation
    xAnnotation->setDateTime( getCurrentDateTime() );

    // set position
    geometry::RealPoint2D aPosition(x / 100.0, y / 100.0);
    xAnnotation->setPosition(aPosition);
    xAnnotation->setSize({5.0, 5.0});

    pPage->addAnnotation(xAnnotation, -1);

    if (mpDoc->IsUndoEnabled())
        mpDoc->EndUndo();

    // Tell our LOK clients about new comment added
    LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Add, *xAnnotation);

    UpdateTags(true);
    SelectAnnotation(xAnnotation, isVisibleAnnotations());
}

void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq )
{
    rtl::Reference< sdr::annotation::Annotation> xAnnotation;
    const SfxItemSet* pArgs = rReq.GetArgs();
    OUString sReplyText;
    if( pArgs )
    {
        const SfxPoolItem*  pPoolItem = nullptr;
        if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
        {
            sal_uInt32 nReplyId = 0; // Id of the comment to reply to
            nReplyId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
            xAnnotation = GetAnnotationById(nReplyId);
        }
        else if( SfxItemState::SET == pArgs->GetItemState( rReq.GetSlot(), true, &pPoolItem ) )
        {
            uno::Reference<office::XAnnotation> xTmpAnnotation;
            if (static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xTmpAnnotation)
            {
                xAnnotation = dynamic_cast<Annotation*>(xTmpAnnotation.get());
                assert(bool(xAnnotation) == bool(xTmpAnnotation) && "must be of concrete type sd::Annotation");
            }
        }

        if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_TEXT, true, &pPoolItem ) )
            sReplyText = static_cast<const SvxPostItTextItem*>( pPoolItem )->GetValue();
    }

    auto* pTextApi = getTextApiObject( xAnnotation );
    if( !pTextApi )
        return;

    if (mpDoc->IsUndoEnabled())
        mpDoc->BegUndo(SdResId(STR_ANNOTATION_REPLY));

    if (xAnnotation)
    {
        auto pSdAnnotation = static_cast<sd::Annotation*>(xAnnotation.get());
        pSdAnnotation->createChangeUndo();
    }

    ::Outliner aOutliner( GetAnnotationPool(),OutlinerMode::TextObject );

    SdDrawDocument::SetCalcFieldValueHdl( &aOutliner );
    aOutliner.SetUpdateLayout( true );

    OUString aStr(SdResId(STR_ANNOTATION_REPLY));
    OUString sAuthor( xAnnotation->getAuthor() );
    if( sAuthor.isEmpty() )
        sAuthor = SdResId( STR_ANNOTATION_NOAUTHOR );

    aStr = aStr.replaceFirst("%1", sAuthor) +
        " (" + getAnnotationDateTimeString( xAnnotation ) + "): \"";

    OUString sQuote( pTextApi->GetText() );

    if( sQuote.isEmpty() )
        sQuote = "...";
    aStr += sQuote + "\"\n";

    for( sal_Int32 nIdx = 0; nIdx >= 0; )
        aOutliner.Insert(aStr.getToken(0, '\n', nIdx), EE_PARA_MAX, -1);

    if( aOutliner.GetParagraphCount() > 1 )
    {
        SfxItemSet aAnswerSet( aOutliner.GetEmptyItemSet() );
        aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));

        ESelection aSel;
        aSel.end.nPara = aOutliner.GetParagraphCount() - 2;
        aSel.end.nIndex = aOutliner.GetText( aOutliner.GetParagraph( aSel.end.nPara ) ).getLength();

        aOutliner.QuickSetAttribs( aAnswerSet, aSel );
    }

    if (!sReplyText.isEmpty())
        aOutliner.Insert(sReplyText);

    std::optional< OutlinerParaObject > pOPO( aOutliner.CreateParaObject() );
    pTextApi->SetText(*pOPO);

    OUString sReplyAuthor;
    if (comphelper::LibreOfficeKit::isActive())
        sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
    else
    {
        SvtUserOptions aUserOptions;
        sReplyAuthor = aUserOptions.GetFullName();
        xAnnotation->setInitials( aUserOptions.GetID() );
    }

    xAnnotation->setAuthor( sReplyAuthor );
    // set current time to reply
    xAnnotation->setDateTime( getCurrentDateTime() );

    // Tell our LOK clients about this (comment modification)
    LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Modify, *xAnnotation);

    if( mpDoc->IsUndoEnabled() )
        mpDoc->EndUndo();

    UpdateTags(true);
    SelectAnnotation( xAnnotation, isVisibleAnnotations() );
}

void AnnotationManagerImpl::DeleteAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation )
{
    SdPage* pPage = GetCurrentPage();

    if( xAnnotation.is() && pPage )
    {
        if( mpDoc->IsUndoEnabled() )
            mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );

        pPage->removeAnnotation( xAnnotation );

        if( mpDoc->IsUndoEnabled() )
            mpDoc->EndUndo();
    }
}

void AnnotationManagerImpl::DeleteAnnotationsByAuthor( std::u16string_view sAuthor )
{
    if( mpDoc->IsUndoEnabled() )
        mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );

    SdPage* pPage = nullptr;
    do
    {
        pPage = GetNextPage( pPage, true );

        if( pPage )
        {
            std::vector<rtl::Reference<sdr::annotation::Annotation>> aAnnotations(pPage->getAnnotations()); // intentionally copy
            for (auto const& xAnnotation : aAnnotations)
            {
                if( xAnnotation->getAuthor() == sAuthor )
                {
                    if( mxSelectedAnnotation == xAnnotation )
                        mxSelectedAnnotation.clear();
                    pPage->removeAnnotation( xAnnotation );
                }
            }
        }
    } while( pPage );

    if( mpDoc->IsUndoEnabled() )
        mpDoc->EndUndo();
}

void AnnotationManagerImpl::DeleteAllAnnotations()
{
    if( mpDoc->IsUndoEnabled() )
        mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );

    SdPage* pPage = nullptr;
    do
    {
        pPage = GetNextPage( pPage, true );

        if( pPage && !pPage->getAnnotations().empty() )
        {
            std::vector<rtl::Reference<sdr::annotation::Annotation>> aAnnotations(pPage->getAnnotations()); // intentionally copy
            for( const auto& rxAnnotation : aAnnotations)
            {
                pPage->removeAnnotation( rxAnnotation );
            }
        }
    }
    while( pPage );

    mxSelectedAnnotation.clear();

    if( mpDoc->IsUndoEnabled() )
        mpDoc->EndUndo();
}

void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet)
{
    SdPage* pCurrentPage = GetCurrentPage();

    const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
    const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard);

    const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion( GetODFSaneDefaultVersion() );

    if (bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFSVER_012))
        rSet.DisableItem( SID_INSERT_POSTIT );

    rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations));

    rtl::Reference<sdr::annotation::Annotation> xAnnotation;
    GetSelectedAnnotation(xAnnotation);

    // Don't disable these slot in case of LOK, as postit doesn't need to
    // selected before doing an operation on it in LOK
    if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly )
    {
        rSet.DisableItem( SID_DELETE_POSTIT );
        rSet.DisableItem( SID_EDIT_POSTIT );
    }

    SdPage* pPage = nullptr;

    bool bHasAnnotations = false;
    do
    {
        pPage = GetNextPage( pPage, true );

        if( pPage && !pPage->getAnnotations().empty() )
            bHasAnnotations = true;
    }
    while( pPage && !bHasAnnotations );

    if( !bHasAnnotations || bReadOnly )
    {
        rSet.DisableItem( SID_DELETEALL_POSTIT );
    }

    if( bWrongPageKind || !bHasAnnotations )
    {
        rSet.DisableItem( SID_PREVIOUS_POSTIT );
        rSet.DisableItem( SID_NEXT_POSTIT );
    }
}

void AnnotationManagerImpl::SelectNextAnnotation(bool bForward)
{
    ShowAnnotations( true );

    rtl::Reference<sdr::annotation::Annotation> xCurrent;
    GetSelectedAnnotation(xCurrent);
    SdPage* pPage = GetCurrentPage();
    if( !pPage )
        return;

    sdr::annotation::AnnotationVector const& aAnnotations = pPage->getAnnotations();

    if( bForward )
    {
        if( xCurrent.is() )
        {
            auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
            if (iter != aAnnotations.end())
            {
                ++iter;
                if( iter != aAnnotations.end() )
                {
                    SelectAnnotation( *iter );
                    return;
                }
            }
        }
        else if( !aAnnotations.empty() )
        {
            SelectAnnotation( *(aAnnotations.begin()) );
            return;
        }
    }
    else
    {
        if( xCurrent.is() )
        {
            auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
            if (iter != aAnnotations.end() && iter != aAnnotations.begin())
            {
                --iter;
                SelectAnnotation( *iter );
                return;
            }
        }
        else if( !aAnnotations.empty() )
        {
            auto iterator = aAnnotations.end();
            iterator--;
            SelectAnnotation(*iterator);
            return;
        }
    }

    mxSelectedAnnotation.clear();
    do
    {
        do
        {
            pPage = GetNextPage( pPage, bForward );

            if( pPage && !pPage->getAnnotations().empty() )
            {
                // switch to next/previous slide with annotations
                std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell()));
                if (pDrawViewShell != nullptr)
                {
                    pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false);
                    pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1);

                    SfxDispatcher* pDispatcher = getDispatcher( mrBase );
                    if( pDispatcher )
                        pDispatcher->Execute( bForward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );

                    return;
                }
            }
        }
        while( pPage );

        // The question text depends on the search direction.
        bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress;
        TranslateId pStringId;
        if(bForward)
            pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW;
        else
            pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW;

        // Pop up question box that asks the user whether to wrap around.
        // The dialog is made modal with respect to the whole application.
        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
                                                       VclMessageType::Question, VclButtonsType::YesNo,
                                                       SdResId(pStringId)));
        xQueryBox->set_default_response(RET_YES);
        if (xQueryBox->run() != RET_YES)
            break;
    }
    while( true );
}

void AnnotationManagerImpl::SelectAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation, bool bEdit)
{
    mxSelectedAnnotation = xAnnotation;
    if (bEdit)
    {
        SdrObject* pObject = xAnnotation->findAnnotationObject();
        if (pObject)
        {
            auto& pAnnotationData = pObject->getAnnotationData();
            if (pAnnotationData)
                pAnnotationData->openPopup();
        }
    }
}

void AnnotationManagerImpl::GetSelectedAnnotation( rtl::Reference<sdr::annotation::Annotation>& xAnnotation )
{
    xAnnotation = mxSelectedAnnotation;
}

void AnnotationManagerImpl::invalidateSlots()
{
    SfxBindings* pBindings = getBindings( mrBase );
    if( pBindings )
    {
        pBindings->Invalidate( SID_INSERT_POSTIT );
        pBindings->Invalidate( SID_DELETE_POSTIT );
        pBindings->Invalidate( SID_DELETEALL_POSTIT );
        pBindings->Invalidate( SID_PREVIOUS_POSTIT );
        pBindings->Invalidate( SID_NEXT_POSTIT );
        pBindings->Invalidate( SID_UNDO );
        pBindings->Invalidate( SID_REDO );
    }
}

void AnnotationManagerImpl::onSelectionChanged()
{
    if (!mxView.is() || !mrBase.GetDrawView())
        return;

    rtl::Reference<SdPage> xPage = mrBase.GetMainViewShell()->getCurrentPage();
    if (xPage != mxCurrentPage)
    {
        mxCurrentPage = std::move(xPage);
        UpdateTags(true);
    }
}

void AnnotationManagerImpl::UpdateTags(bool bSynchron)
{
    SyncAnnotationObjects();

    invalidateSlots();

    if (bSynchron)
    {
        if (mnUpdateTagsEvent)
            Application::RemoveUserEvent(mnUpdateTagsEvent);

        UpdateTagsHdl(nullptr);
    }
    else
    {
        if (!mnUpdateTagsEvent && mxView.is())
            mnUpdateTagsEvent = Application::PostUserEvent(LINK(this, AnnotationManagerImpl, UpdateTagsHdl));
    }
}

IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void)
{
    mnUpdateTagsEvent  = nullptr;
    SyncAnnotationObjects();

    if (mrBase.GetDrawView())
        static_cast<::sd::View*>(mrBase.GetDrawView())->updateHandles();

    invalidateSlots();
}

namespace
{

void applyAnnotationCommon(SdrObject& rObject, rtl::Reference<sdr::annotation::Annotation> const& xAnnotation)
{
    rObject.setAsAnnotationObject();
    auto& xAnnotationData = rObject.getAnnotationData();
    xAnnotationData->mpAnnotationPopup.reset(new AnnotationPopup(xAnnotation));
    xAnnotationData->mxAnnotation = xAnnotation;
    rObject.SetPrintable(false);
}

void applyAnnotationProperties(SdrObject& rObject, sdr::annotation::CreationInfo const& rInfo)
{
    if (rInfo.mbColor)
    {
        rObject.SetMergedItem(XLineStyleItem(drawing::LineStyle_SOLID));
        rObject.SetMergedItem(XLineColorItem(OUString(), rInfo.maColor));
        sal_uInt16 nTransparence = 100.0 - (rInfo.maColor.GetAlpha() / 255.0) * 100.0;
        rObject.SetMergedItem(XLineTransparenceItem(nTransparence));
    }
    rObject.SetMergedItem(XLineWidthItem(rInfo.mnWidth));

    if (rInfo.mbFillColor)
    {
        rObject.SetMergedItem(XFillStyleItem(drawing::FillStyle_SOLID));
        rObject.SetMergedItem(XFillColorItem(OUString(), rInfo.maFillColor));
        sal_uInt16 nTransparence = 100.0 - (rInfo.maFillColor.GetAlpha() / 255.0) * 100.0;
        rObject.SetMergedItem(XFillTransparenceItem(nTransparence));
    }
}

}

void AnnotationManagerImpl::SyncAnnotationObjects()
{
    if (!mxCurrentPage.is() || !mpDoc)
        return;

    std::shared_ptr<DrawViewShell> pDrawViewShell = std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell());

    if (!pDrawViewShell)
        return;

    auto* pView = pDrawViewShell->GetView();
    if (!pView)
        return;

    if (!pView->GetSdrPageView())
        return;

    auto& rModel = pView->getSdrModelFromSdrView();

    sal_Int32 nIndex = 1;
    bool bAnnotatonInserted = false;
    for (auto const& xAnnotation : mxCurrentPage->getAnnotations())
    {
        SdrObject* pObject = xAnnotation->findAnnotationObject();

        if (pObject)
        {
            nIndex++;
            pObject->SetVisible(mbShowAnnotations);
            continue;
        }

        if (!bAnnotatonInserted && mpDoc->IsUndoEnabled())
            mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_INSERT));

        bAnnotatonInserted = true;

        auto const& rInfo = xAnnotation->getCreationInfo();

        auto aRealPoint2D = xAnnotation->getPosition();
        Point aPosition(::tools::Long(aRealPoint2D.X * 100.0), ::tools::Long(aRealPoint2D.Y * 100.0));

        auto aRealSize2D = xAnnotation->getSize();
        Size aSize(::tools::Long(aRealSize2D.Width * 100.0), ::tools::Long(aRealSize2D.Height * 100.0));
        // If the size is not set, set it to a default value so it is non-zero
        if (aSize.getWidth() == 0 || aSize.getHeight() == 0)
             aSize = Size(500, 500);

        ::tools::Rectangle aRectangle(aPosition, aSize);

        rtl::Reference<SdrObject> pNewObject;

        if (rInfo.meType == sdr::annotation::AnnotationType::None)
        {
            sal_uInt16 nAuthorIndex = mpDoc->GetAnnotationAuthorIndex(xAnnotation->getAuthor());

            sdr::annotation::AnnotationViewData aAnnotationViewData
            {
                .nIndex = nIndex,
                .nAuthorIndex = nAuthorIndex
            };

            rtl::Reference<sdr::annotation::AnnotationObject> pAnnotationObject = new sdr::annotation::AnnotationObject(rModel, aRectangle, aAnnotationViewData);
            pNewObject = pAnnotationObject;

            applyAnnotationCommon(*pNewObject, xAnnotation);

            pAnnotationObject->ApplyAnnotationName();
        }
        else if (rInfo.meType == sdr::annotation::AnnotationType::FreeText)
        {
            rtl::Reference<SdrRectObj> pRectangleObject = new SdrRectObj(rModel, aRectangle, SdrObjKind::Text);
            pNewObject = pRectangleObject;

            applyAnnotationCommon(*pNewObject, xAnnotation);
            applyAnnotationProperties(*pNewObject, rInfo);

            OUString aString = xAnnotation->getTextRange()->getString();
            pRectangleObject->SetText(aString);
        }
        else if (rInfo.meType == sdr::annotation::AnnotationType::Square)
        {
            pNewObject = new SdrRectObj(rModel, aRectangle);

            applyAnnotationCommon(*pNewObject, xAnnotation);
            applyAnnotationProperties(*pNewObject, rInfo);
        }
        else if (rInfo.meType == sdr::annotation::AnnotationType::Circle)
        {
            pNewObject = new SdrCircObj(rModel, SdrCircKind::Full, aRectangle);

            applyAnnotationCommon(*pNewObject, xAnnotation);
            applyAnnotationProperties(*pNewObject, rInfo);
        }
        else if (rInfo.meType == sdr::annotation::AnnotationType::Stamp)
        {
            rtl::Reference<SdrGrafObj> pGrafObject = new SdrGrafObj(rModel, Graphic(rInfo.maBitmap), aRectangle);
            pNewObject = pGrafObject;

            applyAnnotationCommon(*pNewObject, xAnnotation);

            pGrafObject->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE));
            pGrafObject->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE));
        }
        else
        {
            SdrObjKind ekind = SdrObjKind::Polygon;

            switch (rInfo.meType)
            {
                case sdr::annotation::AnnotationType::Polygon:
                    ekind = SdrObjKind::Polygon;
                    break;
                case sdr::annotation::AnnotationType::Line:
                    ekind = SdrObjKind::PolyLine;
                    break;
                case sdr::annotation::AnnotationType::Ink:
                    ekind = SdrObjKind::FreehandLine;
                    break;
                default:
                    break;
            }

            basegfx::B2DPolyPolygon aPolyPolygon;
            for (auto const& rPolygon : rInfo.maPolygons)
                aPolyPolygon.append(rPolygon);

            pNewObject = new SdrPathObj(rModel, ekind, std::move(aPolyPolygon));

            applyAnnotationCommon(*pNewObject, xAnnotation);
            applyAnnotationProperties(*pNewObject, rInfo);
        }

        pNewObject->SetRelativePos(aRectangle.TopLeft());
        pNewObject->SetVisible(mbShowAnnotations);

        pView->InsertObjectAtView(pNewObject.get(), *pView->GetSdrPageView());

        nIndex++;
    }

    if (bAnnotatonInserted && mpDoc->IsUndoEnabled())
        mpDoc->EndUndo();
}

void AnnotationManagerImpl::addListener()
{
    Link<sdtools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
    mrBase.GetEventMultiplexer()->AddEventListener(aLink);
}

void AnnotationManagerImpl::removeListener()
{
    Link<sdtools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
    mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
}

IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener,
    sdtools::EventMultiplexerEvent&, rEvent, void)
{
    switch (rEvent.meEventId)
    {
        case EventMultiplexerEventId::CurrentPageChanged:
        case EventMultiplexerEventId::EditViewSelection:
            onSelectionChanged();
            break;

        case EventMultiplexerEventId::MainViewRemoved:
            mxView.clear();
            onSelectionChanged();
            break;

        case EventMultiplexerEventId::MainViewAdded:
            mxView = mrBase.GetDrawController();
            onSelectionChanged();
            break;

        default: break;
    }
}

// TODO: Update colors on notification via DrawViewShell::ConfigurationChanged()
Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
{
    if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
    {
        svtools::ColorConfig aColorConfig;
        switch (aAuthorIndex % 9)
        {
            case 0: return aColorConfig.GetColorValue(svtools::AUTHOR1).nColor;
            case 1: return aColorConfig.GetColorValue(svtools::AUTHOR2).nColor;
            case 2: return aColorConfig.GetColorValue(svtools::AUTHOR3).nColor;
            case 3: return aColorConfig.GetColorValue(svtools::AUTHOR4).nColor;
            case 4: return aColorConfig.GetColorValue(svtools::AUTHOR5).nColor;
            case 5: return aColorConfig.GetColorValue(svtools::AUTHOR6).nColor;
            case 6: return aColorConfig.GetColorValue(svtools::AUTHOR7).nColor;
            case 7: return aColorConfig.GetColorValue(svtools::AUTHOR8).nColor;
            case 8: return aColorConfig.GetColorValue(svtools::AUTHOR9).nColor;
        }
    }

    return COL_WHITE;
}

Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
{
    Color aColor = GetColor(aAuthorIndex);
    if (MiscSettings::GetUseDarkMode())
        aColor.IncreaseLuminance(30);
    else
        aColor.DecreaseLuminance(30);
    return aColor;
}

Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
{
    Color aColor = GetColor(aAuthorIndex);;
    if (MiscSettings::GetUseDarkMode())
        aColor.DecreaseLuminance(80);
    else
        aColor.IncreaseLuminance(80);
    return aColor;
}

SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward )
{
    if( pPage == nullptr )
    {
        if (bForward)
            return mpDoc->GetSdPage(0, PageKind::Standard ); // first page
        else
            return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page
    }

    sal_uInt16 nPageNum = static_cast<sal_uInt16>((pPage->GetPageNum() - 1) >> 1);

    // first all non master pages
    if( !pPage->IsMasterPage() )
    {
        if( bForward )
        {
            if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 )
            {
                // we reached end of draw pages, start with master pages (skip handout master for draw)
                return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard );
            }
            nPageNum++;
        }
        else
        {
            if( nPageNum == 0 )
                return nullptr; // we are already on the first draw page, finished

            nPageNum--;
        }
        return mpDoc->GetSdPage(nPageNum, PageKind::Standard);
    }
    else
    {
        if( bForward )
        {
            if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 )
            {
                return nullptr;   // we reached the end, there is nothing more to see here
            }
            nPageNum++;
        }
        else
        {
            if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) )
            {
                // we reached beginning of master pages, start with end if pages
                return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard );
            }

            nPageNum--;
        }
        return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard);
    }
}

SdPage* AnnotationManagerImpl::GetCurrentPage()
{
    if (auto pMainViewShell = mrBase.GetMainViewShell().get())
        return pMainViewShell->getCurrentPage();
    return nullptr;
}

AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase )
: mxImpl( new AnnotationManagerImpl( rViewShellBase ) )
{
    mxImpl->init();
}

AnnotationManager::~AnnotationManager()
{
    mxImpl->dispose();
}

void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest)
{
    mxImpl->ExecuteAnnotation( rRequest );
}

void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet)
{
    mxImpl->GetAnnotationState(rItemSet);
}

void AnnotationManager::SelectAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation, bool bEdit)
{
    mxImpl->SelectAnnotation(xAnnotation, bEdit);
}

} // end sd

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