//////////////////////////////////////////////////////////////////////////////
// darklyblurhelper.cpp
// handle regions passed to kwin for blurring
// -------------------
//
// Copyright (C) 2018 Alex Nemeth <alex.nemeth329@gmail.com>
//
// Largely rewritten from Oxygen widget style
// Copyright (C) 2007 Thomas Luebking <thomas.luebking@web.de>
// Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////

#include "darklyblurhelper.h"
#include "darklypropertynames.h"
#include "darklystyleconfigdata.h"

#include <KWindowEffects>

#include <QAbstractScrollArea>
#include <QComboBox>
#include <QEvent>
#include <QMainWindow>
#include <QMenu>
#include <QPair>
#include <QRegularExpression>
#include <QToolBar>
#include <QVector>
// #include <QDebug>

namespace
{

QRegion roundedRegion(const QRect &rect, int radius, bool topLeft, bool topRight, bool bottomLeft, bool bottomRight)
{
    QRegion region(rect, QRegion::Rectangle);

    if (topLeft) {
        // Round top-left corner.
        const QRegion topLeftCorner(rect.x(), rect.y(), radius, radius, QRegion::Rectangle);
        const QRegion topLeftRounded(rect.x(), rect.y(), 2 * radius, 2 * radius, QRegion::Ellipse);
        const QRegion topLeftEar = topLeftCorner - topLeftRounded;
        region -= topLeftEar;
    }

    if (topRight) {
        // Round top-right corner.
        const QRegion topRightCorner(rect.x() + rect.width() - radius, rect.y(), radius, radius, QRegion::Rectangle);
        const QRegion topRightRounded(rect.x() + rect.width() - 2 * radius, rect.y(), 2 * radius, 2 * radius, QRegion::Ellipse);
        const QRegion topRightEar = topRightCorner - topRightRounded;
        region -= topRightEar;
    }

    if (bottomRight) {
        // Round bottom-right corner.
        const QRegion bottomRightCorner(rect.x() + rect.width() - radius, rect.y() + rect.height() - radius, radius, radius, QRegion::Rectangle);
        const QRegion bottomRightRounded(rect.x() + rect.width() - 2 * radius, rect.y() + rect.height() - 2 * radius, 2 * radius, 2 * radius, QRegion::Ellipse);
        const QRegion bottomRightEar = bottomRightCorner - bottomRightRounded;
        region -= bottomRightEar;
    }

    if (bottomLeft) {
        // Round bottom-left corner.
        const QRegion bottomLeftCorner(rect.x(), rect.y() + rect.height() - radius, radius, radius, QRegion::Rectangle);
        const QRegion bottomLeftRounded(rect.x(), rect.y() + rect.height() - 2 * radius, 2 * radius, 2 * radius, QRegion::Ellipse);
        const QRegion bottomLeftEar = bottomLeftCorner - bottomLeftRounded;
        region -= bottomLeftEar;
    }

    return region;
}

}

namespace Darkly
{
//___________________________________________________________
BlurHelper::BlurHelper(QObject *parent)
    : QObject(parent)
{
}

//___________________________________________________________
void BlurHelper::registerWidget(QWidget *widget, const bool isDolphin)
{
    // install event filter
    addEventFilter(widget);

    // schedule shadow area repaint
    update(widget);

    _isDolphin = isDolphin;
}

//___________________________________________________________
void BlurHelper::unregisterWidget(QWidget *widget)
{
    // remove event filter
    widget->removeEventFilter(this);
}

//___________________________________________________________
bool BlurHelper::eventFilter(QObject *object, QEvent *event)
{
    switch (event->type()) {
    case QEvent::Hide:
    case QEvent::Show:
    case QEvent::Resize: {
        // cast to widget and check
        QWidget *widget(qobject_cast<QWidget *>(object));

        if (!widget || !widget->isWindow())
            break;

        update(widget);
        break;
    }

    default:
        break;
    }

    // never eat events
    return false;
}

//___________________________________________________________
QRegion BlurHelper::blurRegion(QWidget *widget) const
{
    if (!widget->isVisible())
        return QRegion();

    QRect rect = widget->rect();
    QRegion wMask = widget->mask();

    /* blurring may not be suitable when the available
        painting area is restricted by a widget mask */
    if (!wMask.isEmpty() && wMask != QRegion(rect))
        return QRegion();

    else if ((qobject_cast<QMenu *>(widget) && !widget->testAttribute(Qt::WA_X11NetWmWindowTypeMenu)) // not a detached menu
             || widget->inherits("QComboBoxPrivateContainer")) {
        return roundedRegion(rect, StyleConfigData::cornerRadius() + 1, true, true, true, true);
    } else {
        // blur entire window
        if (widget->palette().color(QPalette::Window).alpha() < 255){
            QWidget *window = widget->window();
            if (window->isFullScreen() || window->isMaximized())
            return roundedRegion(rect, StyleConfigData::cornerRadius(), false, false, false, false);
            else if (StyleConfigData::floatingTitlebar())
            return roundedRegion(rect, StyleConfigData::cornerRadius(), true, true, true, true);
            else
            return roundedRegion(rect, StyleConfigData::cornerRadius(), false, false, true, true);
        }

        // blur specific widgets
        QRegion region;

        // toolbar and menubar
        if (_translucentTitlebar || StyleConfigData::menuBarOpacity() < 100 || StyleConfigData::toolBarOpacity() < 100
            || StyleConfigData::dolphinSidebarOpacity() < 100) {
            // menubar
            int menubarHeight = 0;
            if (QMainWindow *mw = qobject_cast<QMainWindow *>(widget)) {
                if (QWidget *mb = mw->menuWidget()) {
                    if (mb->isVisible()) {
                        region += mb->rect();
                        menubarHeight = mb->height();
                    }
                }
            }

            QList<QToolBar *> toolbars = widget->window()->findChildren<QToolBar *>(QString(), Qt::FindDirectChildrenOnly);
            QRect mainToolbar = QRect();

            // just assuming
            Qt::Orientation orientation = Qt::Vertical;

            // find which one is the main toolbar
            for (auto tb : toolbars) {
                // single toolbar
                if (tb && tb->isVisible() && toolbars.length() == 1) {
                    region += QRegion(QRect(tb->pos(), tb->rect().size()));
                    orientation = tb->orientation();
                }

                else if (tb && tb->isVisible()) {
                    if (mainToolbar.isNull()) {
                        mainToolbar = QRect(tb->pos(), tb->rect().size());
                        orientation = tb->orientation();
                    }

                    // test against the previous best candidate
                    else {
                        if ((tb->y() < mainToolbar.y()) || (tb->y() == mainToolbar.y() && tb->x() < mainToolbar.x())) {
                            mainToolbar = QRect(tb->pos(), tb->rect().size());
                            orientation = tb->orientation();
                        }
                    }
                }
            }

            if (mainToolbar.isValid()) {
                // make adjustments
                if (orientation == Qt::Horizontal) {
                    // toolbar may be at the top but not ocupy the whole avaliable width
                    // so we blur the whole area instead
                    if (mainToolbar.y() == 0 || mainToolbar.y() == menubarHeight) {
                        mainToolbar.setX(0);
                        mainToolbar.setWidth(widget->width());
                        region += mainToolbar;
                    }

                    // round corners if it is at the bottom
                    else if (mainToolbar.y() + mainToolbar.height() == widget->height())
                        region += roundedRegion(mainToolbar, StyleConfigData::cornerRadius(), false, false, false, true);

                    // else
                    //     region += mainToolbar;

                } else {
                    // round bottom left
                    if (mainToolbar.x() == 0)
                        region += roundedRegion(mainToolbar, StyleConfigData::cornerRadius(), false, false, true, false);

                    // round bottom right
                    else if (mainToolbar.x() + mainToolbar.width() == widget->width())
                        region += roundedRegion(mainToolbar, StyleConfigData::cornerRadius(), false, false, false, true);

                    // no round corners
                    // else region += mainToolbar; //FIXME: is this valid?
                }
            }
        }

        if (_isDolphin) {
            // dolphin's sidebar
            if (StyleConfigData::dolphinSidebarOpacity() < 100) {
                // sidetoolbar
                if (!_translucentTitlebar) {
                    QToolBar *toolbar = widget->window()->findChild<QToolBar *>(QString(), Qt::FindDirectChildrenOnly);
                    if (toolbar) {
                        if (toolbar->orientation() == Qt::Vertical) {
                            if (toolbar->x() == 0)
                                region +=
                                    roundedRegion(QRect(toolbar->pos(), toolbar->rect().size()), StyleConfigData::cornerRadius(), false, false, true, false);
                            else
                                region +=
                                    roundedRegion(QRect(toolbar->pos(), toolbar->rect().size()), StyleConfigData::cornerRadius(), false, false, false, true);
                        }
                    }
                }

                // sidepanels
                QList<QWidget *> sidebars =
                    widget->findChildren<QWidget *>(QRegularExpression("^(places|terminal|info|folders)Dock$"), Qt::FindDirectChildrenOnly);
                for (auto sb : sidebars) {
                    if (sb && sb->isVisible()) {
                        if (sb->x() == 0)
                            region += roundedRegion(QRect(sb->pos(), sb->rect().size()), StyleConfigData::cornerRadius(), false, false, true, false);
                        else if (sb->x() + sb->width() == widget->width())
                            region += roundedRegion(QRect(sb->pos(), sb->rect().size()), StyleConfigData::cornerRadius(), false, false, false, true);
                        else
                            region += QRect(sb->pos(), sb->rect().size());
                    }
                }

                // settings page
                // moved to blurSettingsDialogRegion
            }

            // Dolphin main view
            if (StyleConfigData::dolphinViewOpacity() < 100) {
                QList<QAbstractScrollArea *> itemContainers = widget->findChildren<QAbstractScrollArea *>();
                for (QAbstractScrollArea *container : itemContainers) {
                    if (container->inherits("KItemListContainer") && container->isVisible()) {
                        region += QRect(container->mapTo(widget, QPoint(0, 0)), container->rect().size());
                    }
                }
            }

            /*if( (widget->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog )
            {
                QList<QWidget *> dialogWidgets = widget->findChildren<QWidget *>( QString(), Qt::FindDirectChildrenOnly );
                for( auto w : dialogWidgets )
                {
                    if( w->inherits( "KPageWidget" ) )
                    {
                        QList<QWidget *> KPageWidgets = w->findChildren<QWidget *>( QString(), Qt::FindDirectChildrenOnly );
                        for ( auto wid : KPageWidgets ){
                            if( wid->property( PropertyNames::sidePanelView ).toBool() ) {
                                region += roundedRegion( QRect( wid->pos(), wid->rect().size() ), StyleConfigData::cornerRadius(),  false, false, true, false );
                                break;
                            }
                        }
                    }
                }
            }*/
        }

        // tabs
        if (StyleConfigData::tabBarOpacity() < 100) {
            region += blurTabWidgetRegion(widget);
        }

        // settings

        region += blurSettingsDialogRegion(widget);

        return region;
    }
}

//___________________________________________________________
QRegion BlurHelper::blurTabWidgetRegion(QWidget *widget) const
{
    QRegion region;

    // tabs blur only works on dolphin / konsole main windows

    if (widget->inherits("Konsole::MainWindow") || widget->inherits("DolphinMainWindow")) {
        const QTabWidget *tw = widget->window()->findChild<QTabWidget *>(QString(), Qt::FindDirectChildrenOnly);

        if (tw && tw->isVisible()) {
            if (tw->inherits("DolphinTabWidget") || tw->inherits("Konsole::TabbedViewContainer")) {
                // use the tabbar size as the blur region should only focus on the tabbar not the entire tabwidget
                // the width of the tabbar is always -1 smaller than the tabwidget width so increment it
                QSize tbSize(tw->tabBar()->rect().size());
                tbSize.rwidth() += 1;
                region += roundedRegion(QRect(tw->pos(), tbSize), StyleConfigData::cornerRadius(), false, false, false, false);
            }
        }
    }

    return region;
}

//___________________________________________________________
QRegion BlurHelper::blurSettingsDialogRegion(QWidget *widget) const
{
    QRegion region;

    // settings only change it for konsole or dolphin about window
    if ((widget->windowFlags() & Qt::WindowType_Mask) == Qt::Dialog
        && (widget->inherits("KAboutApplicationDialog") || widget->inherits("KDEPrivate::KAboutKdeDialog"))) {
        QList<QWidget *> widgets = widget->findChildren<QWidget *>();
        if (widgets.length() > 0) {
            for (auto w : widgets) {
                if (qobject_cast<QTabWidget *>(w) && (widget->inherits("KAboutApplicationDialog") || widget->inherits("KDEPrivate::KAboutKdeDialog"))) {
                    // about dialog
                    const QTabWidget *tw = qobject_cast<QTabWidget *>(w);
                    QSize tbSize(tw->rect().size());
                    // the blur region is too small without adjusting the height of the tabbar height
                    tbSize.setHeight(tw->tabBar()->rect().height() + 4);
                    region += roundedRegion(QRect(tw->pos(), tbSize), StyleConfigData::cornerRadius(), false, false, true, false);
                } else {
                    // settings main dialog
                    region += roundedRegion(QRect(w->mapToGlobal(w->pos()), w->rect().size()), StyleConfigData::cornerRadius(), false, false, true, false);
                }
                if (w->inherits("KPageWidget")) {
                    // sidebar
                    QList<QWidget *> KPageWidgets = w->findChildren<QWidget *>(QString(), Qt::FindDirectChildrenOnly);
                    for (auto wid : KPageWidgets) {
                        if (wid->property(PropertyNames::sidePanelView).toBool()) {
                            region += roundedRegion(QRect(wid->pos(), wid->rect().size()), StyleConfigData::cornerRadius(), false, false, true, false);
                            break;
                        }
                    }
                }
            }
        }
    }

    return region;
}

//___________________________________________________________
void BlurHelper::update(QWidget *widget) const
{
    /*
    directly from bespin code. Supposedly prevent playing with some 'pseudo-widgets'
    that have winId matching some other -random- window
    */
    if (!(widget->testAttribute(Qt::WA_WState_Created) || widget->internalWinId()))
        return;

    QRegion region = blurRegion(widget);
    if (region.isNull())
        return;

    widget->winId(); // force creation of the window handle
    KWindowEffects::enableBlurBehind(widget->windowHandle(), true, region);

    // force update
    if (widget->isVisible()) {
        widget->update();
    }
}
}
