/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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/.
 */

#include <svx/annotation/Annotation.hxx>
#include <svx/annotation/ObjectAnnotationData.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdview.hxx>
#include <tools/json_writer.hxx>
#include <sfx2/viewsh.hxx>
#include <unotools/datetime.hxx>
#include <comphelper/kit.hxx>
#include <COKit/COKit.hxx>

using namespace css;

namespace sdr::annotation
{
namespace
{
/** Undo/redo a modification of an annotation - change of annotation data */
class UndoAnnotation : public SdrUndoAction
{
public:
    explicit UndoAnnotation(Annotation& rAnnotation)
        : SdrUndoAction(*rAnnotation.GetModel())
        , mrAnnotation(rAnnotation)
    {
        mrAnnotation.toData(maUndoData);
    }

    void Undo() override
    {
        mrAnnotation.toData(maRedoData);
        mrAnnotation.fromData(maUndoData);
        KitCommentNotifyAll(CommentNotificationType::Modify, mrAnnotation);
    }

    void Redo() override
    {
        mrAnnotation.toData(maUndoData);
        mrAnnotation.fromData(maRedoData);
        KitCommentNotifyAll(CommentNotificationType::Modify, mrAnnotation);
    }

protected:
    Annotation& mrAnnotation;
    AnnotationData maUndoData;
    AnnotationData maRedoData;
};

} // anonymous ns

void KitCommentNotify(CommentNotificationType nType, const SfxViewShell* pViewShell,
                      Annotation& rAnnotation)
{
    if (!comphelper::COKit::isActive())
        return;

    OString aPayload = rAnnotation.ToJSON(nType);
    pViewShell->viewCallback(COKitCallbackType::COMMENT, aPayload);
}

void KitCommentNotifyAll(CommentNotificationType nType, Annotation& rAnnotation)
{
    if (!comphelper::COKit::isActive())
        return;

    // The comment belongs to a single document. When several documents share
    // one process (like in case of CODA), other documents may have views on the
    // global view list, so only notify views whose drawing model is the one that
    // owns the comment.
    const SdrModel* pAnnotationModel = rAnnotation.GetModel();

    OString aPayload = rAnnotation.ToJSON(nType);

    const SfxViewShell* pViewShell = SfxViewShell::GetFirst();
    while (pViewShell)
    {
        const SdrView* pDrawView = pViewShell->GetDrawView();
        if (pDrawView && &pDrawView->GetModel() == pAnnotationModel)
            pViewShell->viewCallback(COKitCallbackType::COMMENT, aPayload);
        pViewShell = SfxViewShell::GetNext(*pViewShell);
    }
}

void Annotation::toData(AnnotationData& rData)
{
    std::unique_lock g(m_aMutex);
    rData.m_Position = m_Position;
    rData.m_Size = m_Size;
    rData.m_Author = m_Author;
    rData.m_Initials = m_Initials;
    rData.m_DateTime = m_DateTime;
    rData.m_Text = GetTextImpl(g);
    rData.m_Threaded = m_Threaded;
    rData.m_Resolved = m_Resolved;
    rData.m_ParentId = m_ParentId;
    rData.m_SizeExplicit = m_SizeExplicit;
}

void Annotation::fromData(const AnnotationData& rData)
{
    std::unique_lock g(m_aMutex);
    m_Position = rData.m_Position;
    m_Size = rData.m_Size;
    m_Author = rData.m_Author;
    m_Initials = rData.m_Initials;
    m_DateTime = rData.m_DateTime;
    SetTextImpl(rData.m_Text, g);
    m_Threaded = rData.m_Threaded;
    m_Resolved = rData.m_Resolved;
    m_ParentId = rData.m_ParentId;
    m_SizeExplicit = rData.m_SizeExplicit;
}

Annotation::Annotation(const css::uno::Reference<css::uno::XComponentContext>& rxContext,
                       SdrPage* pPage)
    : cppu::PropertySetMixin<office::XAnnotation>(rxContext, IMPLEMENTS_PROPERTY_SET,
                                                  cpo::uno::Sequence<OUString>())
    , mpPage(pPage)
{
}

SdrModel* Annotation::GetModel() const
{
    return mpPage != nullptr ? &mpPage->getSdrModelFromSdrPage() : nullptr;
}

cpo::uno::Any Annotation::queryInterface(cpo::uno::Type const& type)
{
    return comphelper::WeakComponentImplHelper<office::XAnnotation>::queryInterface(type);
}

std::unique_ptr<SdrUndoAction> Annotation::createUndoAnnotation()
{
    return std::make_unique<UndoAnnotation>(*this);
}

OUString Annotation::GetText()
{
    std::unique_lock g(m_aMutex);
    return GetTextImpl(g);
}

OUString Annotation::GetTextImpl(const std::unique_lock<std::mutex>& g)
{
    uno::Reference<text::XText> xText(getTextRangeImpl(g));
    return xText->getString();
}

void Annotation::SetTextImpl(OUString const& rText, const std::unique_lock<std::mutex>& g)
{
    uno::Reference<text::XText> xText(getTextRangeImpl(g));
    return xText->setString(rText);
}

uno::Reference<text::XText> SAL_CALL Annotation::getTextRange()
{
    std::unique_lock g(m_aMutex);
    return getTextRangeImpl(g);
}

uno::Reference<text::XText> Annotation::getTextRangeImpl(const std::unique_lock<std::mutex>& /*g*/)
{
    if (!m_TextRange.is() && mpPage != nullptr)
    {
        m_TextRange = sdr::annotation::TextApiObject::create(&mpPage->getSdrModelFromSdrPage());
    }
    return m_TextRange;
}

void Annotation::SetPosition(const css::geometry::RealPoint2D& rValue)
{
    std::unique_lock g(m_aMutex);
    m_Position = rValue;
}

void Annotation::SetSize(const css::geometry::RealSize2D& rValue)
{
    std::unique_lock g(m_aMutex);
    m_Size = rValue;
}

void Annotation::SetThreaded(bool bThreaded)
{
    std::unique_lock g(m_aMutex);
    m_Threaded = bThreaded;
}

void Annotation::SetResolved(bool bResolved)
{
    std::unique_lock g(m_aMutex);
    m_Resolved = bResolved;
}

void Annotation::SetParentId(sal_uInt64 nParentId)
{
    std::unique_lock g(m_aMutex);
    m_ParentId = nParentId;
}

void Annotation::disposing(std::unique_lock<std::mutex>& /*rGuard*/)
{
    mpPage = nullptr;
    if (m_TextRange.is())
    {
        m_TextRange->dispose();
        m_TextRange.clear();
    }
}

SdrObject* Annotation::findAnnotationObject()
{
    SdrPage const* pPage = getPage();

    if (!pPage)
        return nullptr;

    for (size_t i = 0; i < pPage->GetObjCount(); ++i)
    {
        SdrObject* pObject = pPage->GetObj(i);
        if (pObject->isAnnotationObject()
            && pObject->getAnnotationData()->mxAnnotation.get() == this)
            return pObject;
    }
    return nullptr;
}

OString Annotation::ToJSON(CommentNotificationType nType)
{
    tools::JsonWriter aJsonWriter;

    {
        auto aCommentNode = aJsonWriter.startNode("comment");

        aJsonWriter.put(
            "action",
            (nType == CommentNotificationType::Add
                 ? "Add"
                 : (nType == CommentNotificationType::Remove
                        ? "Remove"
                        : (nType == CommentNotificationType::Modify ? "Modify" : "???"))));

        std::unique_lock g(m_aMutex);
        sal_uInt64 id = maUniqueID.getID();
        aJsonWriter.put("id", id);
        if (nType != CommentNotificationType::Remove)
        {
            aJsonWriter.put("id", id);
            aJsonWriter.put("author", m_Author);
            aJsonWriter.put("dateTime", utl::toISO8601(m_DateTime));
            aJsonWriter.put("text", GetTextImpl(g));
            // The page's stable unique id as a number; 0 stands for no page, a value the
            // ids never take.
            aJsonWriter.put("part", mpPage ? mpPage->GetUniqueID() : sal_uInt64(0));
            tools::Rectangle aRectangle(
                Point(std::round(o3tl::toTwips(m_Position.X, o3tl::Length::mm)),
                      std::round(o3tl::toTwips(m_Position.Y, o3tl::Length::mm))),
                Size(std::round(o3tl::toTwips(m_Size.Width, o3tl::Length::mm)),
                     std::round(o3tl::toTwips(m_Size.Height, o3tl::Length::mm))));
            aJsonWriter.put("rectangle", aRectangle.toString());
            // Online uses hasArea to decide whether to render an outlined
            // anchor box around the comment in addition to the small marker
            // icon: PDF /Rect from import or the Width/Height args of
            // .uno:InsertAnnotation produce a rectangle that's worth showing,
            // whereas the auto-grown text-frame size of a fresh ODP/PPTX
            // comment isn't.
            if (m_SizeExplicit)
                aJsonWriter.put("hasArea", "true");
            if (m_Threaded)
            {
                aJsonWriter.put("threaded", "true");
                aJsonWriter.put("resolved", m_Resolved ? "true" : "false");
                aJsonWriter.put("parentId", m_ParentId);
            }
        }
    }

    return aJsonWriter.finishAndGetAsOString();
}

} // namespace sdr::annotation

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