/* This file is part of the KDE project
 * SPDX-FileCopyrightText: 2010 Thomas Zander <zander@kde.org>
 * SPDX-FileCopyrightText: 2010 Sebastian Sauer <sebsauer@kdab.com>
 *
 * SPDX-License-Identifier: LGPL-2.0-or-later
 */

#include "ShrinkToFitShapeContainer.h"

#include <KoShapeSavingContext.h>
#include <KoTextLayoutRootArea.h>

ShrinkToFitShapeContainer::ShrinkToFitShapeContainer(KoShape *childShape, KoDocumentResourceManager *documentResources)
    : KoShapeContainer(*(new ShrinkToFitShapeContainerPrivate(this, childShape)))
{
    Q_UNUSED(documentResources);
    Q_D(ShrinkToFitShapeContainer);

    setPosition(childShape->position());
    setSize(childShape->size());
    setZIndex(childShape->zIndex());
    setRunThrough(childShape->runThrough());
    rotate(childShape->rotation());
    // setTransformation(childShape->transformation());

    if (childShape->parent()) {
        childShape->parent()->addShape(this);
        childShape->setParent(nullptr);
    }

    childShape->setPosition(QPointF(0.0, 0.0)); // since its relative to my position, this won't move it
    childShape->setSelectable(false); // our ShrinkToFitShapeContainer will handle that from now on

    d->model = new ShrinkToFitShapeContainerModel(this, d);
    addShape(childShape);

    QSet<KoShape *> delegates;
    delegates << childShape;
    setToolDelegates(delegates);

    KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(childShape->userData());
    Q_ASSERT(data);
    KoTextDocumentLayout *lay = qobject_cast<KoTextDocumentLayout *>(data->document()->documentLayout());
    Q_ASSERT(lay);
    QObject::connect(lay,
                     &KoTextDocumentLayout::finishedLayout,
                     static_cast<ShrinkToFitShapeContainerModel *>(d->model),
                     &ShrinkToFitShapeContainerModel::finishedLayout);
}

ShrinkToFitShapeContainer::~ShrinkToFitShapeContainer() = default;

void ShrinkToFitShapeContainer::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &)
{
    Q_UNUSED(painter);
    Q_UNUSED(converter);
    // painter.fillRect(converter.documentToView(QRectF(QPointF(0,0),size())), QBrush(QColor("#ffcccc"))); // for testing
}

bool ShrinkToFitShapeContainer::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context)
{
    Q_UNUSED(element);
    Q_UNUSED(context);
    return true;
}

void ShrinkToFitShapeContainer::saveOdf(KoShapeSavingContext &context) const
{
    Q_D(const ShrinkToFitShapeContainer);
    d->childShape->saveOdf(context);
}

ShrinkToFitShapeContainer *ShrinkToFitShapeContainer::wrapShape(KoShape *shape, KoDocumentResourceManager *documentResourceManager)
{
    Q_ASSERT(dynamic_cast<KoTextShapeData *>(shape->userData()));
    Q_ASSERT(qobject_cast<KoTextDocumentLayout *>(dynamic_cast<KoTextShapeData *>(shape->userData())->document()->documentLayout()));

    return new ShrinkToFitShapeContainer(shape, documentResourceManager);
}

void ShrinkToFitShapeContainer::tryWrapShape(KoShape *shape, const KoXmlElement &element, KoShapeLoadingContext &context)
{
    KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(shape->userData());
    if (!data || data->resizeMethod() != KoTextShapeData::ShrinkToFitResize)
        return;

    KoShapeContainer *oldParent = shape->parent();
    ShrinkToFitShapeContainer *tos = wrapShape(shape, context.documentResourceManager());
    if (!tos->loadOdf(element, context)) {
        shape->setParent(oldParent);
        delete tos;
    }
}

void ShrinkToFitShapeContainer::unwrapShape(KoShape *shape)
{
    Q_ASSERT(shape->parent() == this);

    removeShape(shape);
    shape->setParent(parent());

    QSet<KoShape *> delegates = toolDelegates();
    delegates.remove(shape);
    setToolDelegates(delegates);

    shape->setPosition(position());
    shape->setSize(size());
    shape->rotate(rotation());
    shape->setSelectable(true);
}

ShrinkToFitShapeContainerModel::ShrinkToFitShapeContainerModel(ShrinkToFitShapeContainer *q, ShrinkToFitShapeContainerPrivate *d)
    : q(q)
    , d(d)
    , m_scale(1.0)
    , m_dirty(10)
    , m_maybeUpdate(false)
{
}

void ShrinkToFitShapeContainerModel::finishedLayout()
{
    m_maybeUpdate = true;
    containerChanged(q, KoShape::SizeChanged);
    m_maybeUpdate = false;
}

void ShrinkToFitShapeContainerModel::containerChanged(KoShapeContainer *container, KoShape::ChangeType type)
{
    Q_ASSERT(container == q);
    Q_UNUSED(container);
    if (type == KoShape::SizeChanged) {
        KoTextShapeData *data = dynamic_cast<KoTextShapeData *>(d->childShape->userData());
        Q_ASSERT(data);
        KoTextLayoutRootArea *rootArea = data->rootArea();
        Q_ASSERT(rootArea);

        QSizeF shapeSize = q->size();
        QSizeF documentSize = rootArea->boundingRect().size();
        if (m_maybeUpdate && shapeSize == m_shapeSize && documentSize == m_documentSize) {
            m_dirty = 0;
            return; // nothing to update
        }

        m_shapeSize = shapeSize;
        m_documentSize = documentSize;

        if (documentSize.width() > 0.0 && documentSize.height() > 0.0) {
            if (m_dirty || !m_maybeUpdate) {
                qreal scaleX = qMin<qreal>(1.0, shapeSize.width() / documentSize.width());
                qreal scaleY = qMin<qreal>(1.0, shapeSize.height() / documentSize.height());
                m_scale = (scaleX + scaleY) / 2.0 * 0.95;
                if (m_maybeUpdate && m_dirty)
                    --m_dirty;
            }
        } else {
            m_scale = 1.0;
            m_dirty = 1;
        }

        QSizeF newSize(shapeSize.width() / m_scale, shapeSize.height() / m_scale);
        d->childShape->setSize(newSize);

        QTransform m;
        m.scale(m_scale, m_scale);
        d->childShape->setTransformation(m);
    }
}

bool ShrinkToFitShapeContainerModel::inheritsTransform(const KoShape *child) const
{
    Q_ASSERT(child == d->childShape);
    Q_UNUSED(child);
    return true;
}

bool ShrinkToFitShapeContainerModel::isChildLocked(const KoShape *child) const
{
    Q_ASSERT(child == d->childShape);
    Q_UNUSED(child);
    return true;
}

bool ShrinkToFitShapeContainerModel::isClipped(const KoShape *child) const
{
    Q_ASSERT(child == d->childShape);
    Q_UNUSED(child);
    return false;
}
