/*
    SuperCollider Qt IDE
    Copyright (c) 2010-2012 Jakob Leben
    Copyright (c) 2012 Tim Blechmann
    http://www.audiosynth.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

#include "style.hpp"
#include "../../core/util/color.hpp"

#include <cmath>

#include <QPainter>
#include <QStyleOption>
#include <QDockWidget>
#include <QStyleOptionTab>
#include <QTabBar>
#include <QToolButton>
#include <QLayout>
#include <QDebug>

#ifdef SC_USE_QTWEBENGINE
#    include <QWebEngineView>
#endif // SC_USE_QTWEBENGINE

namespace ScIDE {

inline bool Style::shouldNotHandle(const QWidget* widget) const {
    return widget ? widget->window()->windowType() == Qt::Dialog : false;
}

void Style::polish(QWidget* widget) {
    if (shouldNotHandle(widget)) {
        QProxyStyle::polish(widget);
        widget->update();
        widget->updateGeometry();
        if (widget->layout())
            widget->layout()->invalidate();
        return;
    }

    if (qobject_cast<QDockWidget*>(widget)) {
        return;
    }
    if ((qobject_cast<QTabBar*>(widget)) || (qobject_cast<QToolButton*>(widget))) {
        widget->setAttribute(Qt::WA_Hover, true);
    } else
        QProxyStyle::polish(widget);
}

void Style::unpolish(QWidget* widget) { QProxyStyle::unpolish(widget); }

void Style::drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
                               const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        QProxyStyle::drawComplexControl(control, option, painter, widget);
        return;
    }

    switch (control) {
#ifdef SC_USE_QTWEBENGINE
    // FIXME: this is a workaround for the WebKit bug #104116 (or a variation on it).
    case QStyle::CC_ScrollBar: {
        if (qobject_cast<const QWebEngineView*>(widget) != 0 && option->type == QStyleOption::SO_Slider) {
            // WebKit tries to hide scrollbars, but mistakenly hides QWebView - NULL-ify styleObject to prevent.
            const QStyleOptionSlider* optSlider = static_cast<const QStyleOptionSlider*>(option);
            QStyleOptionSlider opt2(*optSlider);
            opt2.styleObject = NULL;

            QProxyStyle::drawComplexControl(control, &opt2, painter, widget);
            return;
        }
    }
#endif // SC_USE_QTWEBENGINE
    case QStyle::CC_ToolButton: {
        // TODO: We only draw either text, or icon, or arrow

        const QToolButton* toolBtn = qobject_cast<const QToolButton*>(widget);
        if (!toolBtn)
            break;

        Q_ASSERT(toolBtn);

        const QStyleOptionToolButton* toolOption = static_cast<const QStyleOptionToolButton*>(option);

        painter->save();

        QRect r = option->rect;

        if (option->state & QStyle::State_On) {
            painter->setBrush(option->palette.color(QPalette::Dark));
            painter->setPen(option->palette.color(QPalette::Shadow));
            painter->drawRect(r.adjusted(0, 0, -1, -1));
        } else {
            bool highlight = option->state & QStyle::State_MouseOver;

            if (highlight) {
                QColor fill = option->palette.color(QPalette::Button);
                painter->setBrush(fill);
                painter->setPen(Qt::NoPen);
                painter->drawRect(r.adjusted(0, 0, 0, -1));
            }

            if (qobject_cast<QTabBar*>(toolBtn->parent())) {
                if (!highlight) {
                    QColor fill = option->palette.color(QPalette::Mid);
                    painter->setBrush(fill);
                    painter->setPen(Qt::NoPen);
                    painter->drawRect(r.adjusted(0, 0, 0, -1));
                }
                if (toolBtn->arrowType() == Qt::LeftArrow) {
                    painter->setPen(option->palette.color(QPalette::Shadow));
                    painter->drawLine(option->rect.topLeft(), option->rect.bottomLeft());
                }
            }
        }

        painter->restore();

        QIcon icon = toolOption->icon;
        if (!icon.isNull()) {
            QIcon::Mode iconMode = option->state & QStyle::State_Enabled
                ? (option->state & QStyle::State_MouseOver ? QIcon::Active : QIcon::Normal)
                : QIcon::Disabled;

            QIcon::State iconState = option->state & QStyle::State_Selected ? QIcon::On : QIcon::Off;

            QPixmap pixmap = icon.pixmap(toolOption->iconSize, iconMode, iconState);
            QRect pixRect;
            pixRect.setSize(QSize(16, 16));
            pixRect.moveCenter(option->rect.center());
            painter->drawPixmap(pixRect, pixmap);
        } else {
            QStyle::PrimitiveElement elem = Style::PE_CustomBase;
            switch (toolBtn->arrowType()) {
            case Qt::LeftArrow:
                elem = PE_IndicatorArrowLeft;
                break;
            case Qt::RightArrow:
                elem = PE_IndicatorArrowRight;
                break;
            case Qt::DownArrow:
                elem = PE_IndicatorArrowDown;
                break;
            case Qt::UpArrow:
                elem = PE_IndicatorArrowUp;
                break;
            default:
                break;
            }
            if (elem != Style::PE_CustomBase) {
                drawPrimitive(elem, option, painter, widget);
            } else if (!toolOption->text.isEmpty()) {
                painter->drawText(toolOption->rect, Qt::AlignCenter | Qt::TextShowMnemonic, toolOption->text);
            }
        }
        return;
    }
    default:
        break;
    }
    QProxyStyle::drawComplexControl(control, option, painter, widget);
}

void Style::drawControl(ControlElement element, const QStyleOption* option, QPainter* painter,
                        const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        QProxyStyle::drawControl(element, option, painter, widget);
        return;
    }

    switch (element) {
    case QStyle::CE_TabBarTab: {
        const QStyleOptionTab* tabOption = static_cast<const QStyleOptionTab*>(option);

        painter->save();

        bool selected = tabOption->state & QStyle::State_Selected;
        bool mouseOver = tabOption->state & QStyle::State_MouseOver;

        QColor background_color;
        if (selected) {
            background_color = option->palette.color(QPalette::Window);
        } else {
            background_color = option->palette.color(QPalette::Mid);
            if (mouseOver) {
                background_color = color::lighten(background_color, 10);
            }
        }

        QColor text_color = option->palette.color(QPalette::WindowText);
        if (!selected) {
            text_color = color::interpolate(text_color, background_color, kDeselectedTabBlend);
        }

        // Draw tab rectangle.
        QRect rect = tabOption->rect;
        painter->setBrush(background_color);
        painter->setPen(Qt::NoPen);
        painter->drawRect(rect);

        // For inactive tabs, add a thin dark line to the right side for visual
        // separation.
        if (!selected && !(tabOption->selectedPosition == QStyleOptionTab::NextIsSelected)
            && !(tabOption->position == QStyleOptionTab::End)
            && !(tabOption->position == QStyleOptionTab::OnlyOneTab)) {
            QPen pen(option->palette.color(QPalette::Shadow), 1);
            painter->setPen(pen);
            painter->drawLine(rect.topRight(), rect.bottomRight());
        }

        // If the contrast between active and inactive tabs is stupidly low,
        // then use an overline to distinguish them. This happens when the user
        // sets a really dark background color.
        int window_mid_value_difference =
            abs(option->palette.color(QPalette::Window).value() - option->palette.color(QPalette::Mid).value());

        if (selected && window_mid_value_difference < 20) {
            painter->setBrush(text_color);
            painter->drawRect(rect.left(), rect.top(), rect.width(), 2);
        }

        painter->restore();

        // Draw icon.

        if (!tabOption->icon.isNull()) {
            QPixmap pixmap = tabOption->icon.pixmap(tabOption->iconSize);
            QRect iconRect;
            iconRect.setSize(QSize(14, 14));
            iconRect.moveCenter(tabOption->rect.center());
            int lmargin = 5;
            if (tabOption->leftButtonSize.width() > 0)
                lmargin += tabOption->leftButtonSize.width() + 4;
            iconRect.moveLeft(tabOption->rect.left() + lmargin);
            painter->drawPixmap(iconRect, pixmap);
        }

        // Draw text.

        QRect textRect = subElementRect(QStyle::SE_TabBarTabText, option, widget);

        painter->save();
        if (selected) {
            QFont font;
            font.setBold(true);
            painter->setFont(font);
        }
        painter->setPen(text_color);
        painter->drawText(textRect, Qt::AlignCenter | Qt::TextShowMnemonic, tabOption->text);
        painter->restore();

        return;
    }
    case CE_Splitter:
        painter->save();
        painter->setPen(Qt::NoPen);
        painter->setBrush(option->palette.color(QPalette::Shadow));
        painter->drawRect(option->rect);
        painter->restore();
        return;
    default:
        break;
    }
    QProxyStyle::drawControl(element, option, painter, widget);
}

void Style::drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter,
                          const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        QProxyStyle::drawPrimitive(element, option, painter, widget);
        return;
    }

    switch (element) {
    case QStyle::PE_IndicatorTabTear:
        return;
    case QStyle::PE_FrameTabBarBase: {
        const QTabBar* tabBar = qobject_cast<const QTabBar*>(widget);

        painter->save();

        painter->setPen(Qt::NoPen);

        if (tabBar) {
            painter->setBrush(option->palette.color(QPalette::Mid));
            painter->drawRect(tabBar->rect());
        }

        painter->setBrush(option->palette.color(QPalette::Shadow));
        painter->drawRect(option->rect);

        painter->restore();
        return;
    }
    case PE_IndicatorDockWidgetResizeHandle:
        painter->save();
        painter->setPen(Qt::NoPen);
        painter->setBrush(option->palette.color(QPalette::Shadow));
        painter->drawRect(option->rect);
        painter->restore();
        return;
    case PE_IndicatorTabClose: {
        QPoint center = option->rect.center();
        int rect_width = option->rect.width();
        // a = half of the width of the X.
        int a = rect_width * 0.5f * 0.4f;
        float cx = center.x() + 0.5f;
        float cy = center.y() + 0.5f;

        QColor x_color = option->palette.color(QPalette::WindowText);
        if (!(option->state & QStyle::State_Selected)) {
            QColor background = option->palette.color(QPalette::Mid);
            x_color = color::interpolate(x_color, background, kDeselectedTabBlend);
        }
        if ((option->state & State_Enabled) && (option->state & State_MouseOver)) {
            x_color = color::lighten(x_color, 40);
        }

        float thickness = 1.5f;
        // Sometimes this needs adjusting for symmetrical results on different
        // line thicknesses.
        float extend = 0.0f;

        QPen pen(x_color, thickness);
        painter->save();
        painter->setRenderHint(QPainter::Antialiasing, true);
        painter->setPen(pen);
        painter->drawLine(QLineF(cx - a, cy - a, cx + a + extend, cy + a + extend));
        painter->drawLine(QLineF(cx - a, cy + a, cx + a + extend, cy - a - extend));
        painter->restore();
        return;
    }
    default:
        QProxyStyle::drawPrimitive(element, option, painter, widget);
    }
}

QRect Style::subElementRect(SubElement element, const QStyleOption* option, const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        return QProxyStyle::subElementRect(element, option, widget);
    }

    switch (element) {
    // NOTE: Assuming horizontal tab bar direction
    case QStyle::SE_TabBarTabRightButton: {
        QRect r;
        r.setSize(QSize(16, 16));
        r.moveCenter(option->rect.center());
        r.moveRight(option->rect.right() + 1 - 5);
        return r;
    }
    case QStyle::SE_TabBarTabLeftButton: {
        QRect r;
        r.setSize(QSize(16, 16));
        r.moveCenter(option->rect.center());
        r.moveLeft(option->rect.left() + 5);
        return r;
    }
    case QStyle::SE_TabBarTabText: {
        const auto* tabOption = static_cast<const QStyleOptionTab*>(option);

        int lMargin = 5;
        if (tabOption->leftButtonSize.width() > 0)
            lMargin += tabOption->leftButtonSize.width() + 4;
        if (!tabOption->icon.isNull())
            lMargin += tabOption->iconSize.width() + 4;

        int rMargin = 5;
        if (tabOption->rightButtonSize.width() > 0)
            rMargin += tabOption->rightButtonSize.width() + 4;

        QRect r = option->rect;
        r.adjust(lMargin, 0, -rMargin, 0);
        return r;
    }
    default:
        return QProxyStyle::subElementRect(element, option, widget);
    }
}

QSize Style::sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& contentsSize,
                              const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        return QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
    }

    switch (type) {
    case QStyle::CT_TabBarTab:
        return contentsSize; //+ QSize(10, 10);
    case QStyle::CT_ToolButton:
        return contentsSize + QSize(10, 10);
    default:
        return QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
    }
}

int Style::pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const {
    if (shouldNotHandle(widget)) {
        return QProxyStyle::pixelMetric(metric, option, widget);
    }

    switch (metric) {
    case QStyle::PM_DockWidgetFrameWidth:
        return 2;
    case QStyle::PM_DockWidgetSeparatorExtent:
    case QStyle::PM_SplitterWidth:
        return 1;
    case QStyle::PM_TabBarBaseHeight:
        return 0;
    case QStyle::PM_TabBarBaseOverlap:
        return 1;
    case QStyle::PM_TabBarTabShiftHorizontal:
    case QStyle::PM_TabBarTabShiftVertical:
        return 0;
    case QStyle::PM_TabBarTabHSpace:
    case QStyle::PM_TabBarTabVSpace:
        return 10;
    case QStyle::PM_TabBarTabOverlap:
        return 0;
    case QStyle::PM_TabBarIconSize:
        return 16;
    case PM_TabCloseIndicatorHeight:
    case PM_TabCloseIndicatorWidth:
        return 16;
    case PM_TabBarScrollButtonWidth:
        return 24;
    case PM_TabBar_ScrollButtonOverlap:
        return 1;
    case PM_MenuButtonIndicator:
        return 0;
    default:
        break;
    }
    return QProxyStyle::pixelMetric(metric, option, widget);
}

int Style::styleHint(StyleHint hint, const QStyleOption* option, const QWidget* widget,
                     QStyleHintReturn* returnData) const {
    if (shouldNotHandle(widget)) {
        return QProxyStyle::styleHint(hint, option, widget, returnData);
    }

    switch (hint) {
    case SH_TabBar_PreferNoArrows:
        return 0;
    case SH_TabBar_ElideMode:
        return Qt::ElideNone;
    default:
        break;
    }
    return QProxyStyle::styleHint(hint, option, widget, returnData);
}

} // namespace ScIDE
