/*
 * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "breezemdiwindowshadow.h"

#include "breezeboxshadowrenderer.h"
#include "breezemetrics.h"
#include "breezeshadowhelper.h"
#include "breezestyleconfigdata.h"

#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPainter>
#include <QTextStream>

namespace Breeze
{
//____________________________________________________________________
MdiWindowShadow::MdiWindowShadow(QWidget *parent, const TileSet &shadowTiles)
    : QWidget(parent)
    , _shadowTiles(shadowTiles)
{
    setAttribute(Qt::WA_OpaquePaintEvent, false);
    setAttribute(Qt::WA_TransparentForMouseEvents, true);
    setFocusPolicy(Qt::NoFocus);
}

//____________________________________________________________________
void MdiWindowShadow::updateGeometry()
{
    if (!_widget) {
        return;
    }

    // metrics
    const CompositeShadowParams params = ShadowHelper::lookupShadowParams(StyleConfigData::shadowSize());
    if (params.isNone()) {
        return;
    }

    const QSizeF boxSize =
        BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius).expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));

    const QSizeF shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset)
                                  .expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset));

    const QRectF shadowRect(QPoint(0, 0), shadowSize);

    QRectF boxRect(QPoint(0, 0), boxSize);
    boxRect.moveCenter(shadowRect.center());

    const double topSize(boxRect.top() - shadowRect.top() - Metrics::Shadow_Overlap - params.offset.y());
    const double bottomSize(shadowRect.bottom() - boxRect.bottom() - Metrics::Shadow_Overlap + params.offset.y());
    const double leftSize(boxRect.left() - shadowRect.left() - Metrics::Shadow_Overlap - params.offset.x());
    const double rightSize(shadowRect.right() - boxRect.right() - Metrics::Shadow_Overlap + params.offset.x());

    // get tileSet rect
    auto hole = _widget->frameGeometry();
    _shadowTilesRect = hole.adjusted(-leftSize, -topSize, rightSize, bottomSize);

    // get parent MDI area's viewport
    auto parent(parentWidget());
    if (parent && !qobject_cast<QMdiArea *>(parent) && qobject_cast<QMdiArea *>(parent->parentWidget())) {
        parent = parent->parentWidget();
    }

    if (qobject_cast<QAbstractScrollArea *>(parent)) {
        parent = qobject_cast<QAbstractScrollArea *>(parent)->viewport();
    }

    // set geometry
    QRect geometry(_shadowTilesRect);
    if (parent) {
        geometry &= parent->rect();
        hole &= parent->rect();
    }

    // update geometry and mask
    const QRegion mask = QRegion(geometry) - hole.adjusted(2, 2, -2, -2);
    if (mask.isEmpty()) {
        hide();
    } else {
        setGeometry(geometry);
        setMask(mask.translated(-geometry.topLeft()));
        show();
    }

    // translate rendering rect
    _shadowTilesRect.translate(-geometry.topLeft());
}

//____________________________________________________________________
void MdiWindowShadow::updateZOrder()
{
    stackUnder(_widget);
}

//____________________________________________________________________
void MdiWindowShadow::paintEvent(QPaintEvent *event)
{
    if (!_shadowTiles.isValid()) {
        return;
    }

    QPainter painter(this);
    painter.setRenderHints(QPainter::Antialiasing);
    painter.setClipRegion(event->region());
    _shadowTiles.render(_shadowTilesRect, &painter);
}

//____________________________________________________________________
MdiWindowShadowFactory::MdiWindowShadowFactory()
    : QObject()
{
}

//____________________________________________________________________________________
bool MdiWindowShadowFactory::registerWidget(QWidget *widget)
{
    // check widget type
    auto subwindow(qobject_cast<QMdiSubWindow *>(widget));
    if (!subwindow) {
        return false;
    }
    if (subwindow->widget() && subwindow->widget()->inherits("KMainWindow")) {
        return false;
    }

    // make sure widget is not already registered
    if (isRegistered(widget)) {
        return false;
    }

    // store in set
    _registeredWidgets.insert(widget);

    // create shadow immediately if widget is already visible
    if (widget->isVisible()) {
        installShadow(widget);
        updateShadowGeometry(widget);
        updateShadowZOrder(widget);
    }

    widget->installEventFilter(this);

    // catch object destruction
    connect(widget, &QObject::destroyed, this, &MdiWindowShadowFactory::widgetDestroyed);

    return true;
}

//____________________________________________________________________________________
void MdiWindowShadowFactory::unregisterWidget(QWidget *widget)
{
    if (!isRegistered(widget)) {
        return;
    }
    widget->removeEventFilter(this);
    _registeredWidgets.remove(widget);
    removeShadow(widget);
}

//____________________________________________________________________________________
bool MdiWindowShadowFactory::eventFilter(QObject *object, QEvent *event)
{
    switch (event->type()) {
    // TODO: possibly implement ZOrderChange event, to make sure that
    // the shadow is always painted on top
    case QEvent::ZOrderChange:
        updateShadowZOrder(object);
        break;

    case QEvent::Hide:
        hideShadows(object);
        break;

    case QEvent::Show:
        installShadow(object);
        updateShadowGeometry(object);
        updateShadowZOrder(object);
        break;

    case QEvent::Move:
    case QEvent::Resize:
        updateShadowGeometry(object);
        break;

    default:
        break;
    }

    return QObject::eventFilter(object, event);
}

//____________________________________________________________________________________
MdiWindowShadow *MdiWindowShadowFactory::findShadow(QObject *object) const
{
    // check object,
    if (!object->parent()) {
        return nullptr;
    }

    // find existing window shadows
    const auto children = object->parent()->children();
    for (QObject *child : children) {
        if (MdiWindowShadow *shadow = qobject_cast<MdiWindowShadow *>(child)) {
            if (shadow->widget() == object) {
                return shadow;
            }
        }
    }

    return nullptr;
}

//____________________________________________________________________________________
void MdiWindowShadowFactory::installShadow(QObject *object)
{
    // cast
    auto widget(static_cast<QWidget *>(object));
    if (!widget->parentWidget()) {
        return;
    }

    // make sure shadow is not already installed
    if (findShadow(object)) {
        return;
    }

    if (!_shadowHelper) {
        return;
    }

    // create new shadow
    auto windowShadow(new MdiWindowShadow(widget->parentWidget(), _shadowHelper->shadowTiles(widget)));
    windowShadow->setWidget(widget);
}

//____________________________________________________________________________________
void MdiWindowShadowFactory::removeShadow(QObject *object)
{
    if (MdiWindowShadow *windowShadow = findShadow(object)) {
        windowShadow->hide();
        windowShadow->deleteLater();
    }
}

//____________________________________________________________________________________
void MdiWindowShadowFactory::widgetDestroyed(QObject *object)
{
    _registeredWidgets.remove(object);
    removeShadow(object);
}

}
