#include "MegaProxyStyle.h"

#include "EventHelper.h"
#include "MegaApplication.h"
#include "StatsEventHandler.h" // used in cast in function polish

#include <QComboBox>
#include <QHeaderView>
#include <QMouseEvent>
#include <QOperatingSystemVersion>
#include <QSpinBox>
#include <QStyleOption>
#include <QTreeView>
#include <QWindow>

#include <memory>

const int TOOLTIP_DELAY = 250;

void MegaProxyStyle::drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget) const
{
    painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
    QProxyStyle::drawComplexControl(control, option, painter, widget);
}

void MegaProxyStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
    painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
    if (element != QStyle::CE_ItemViewItem)
    {
        painter->setRenderHints(QPainter::Antialiasing, true);
    }

    switch (element)
    {
        case CE_HeaderLabel:
        {
            auto headerView = qobject_cast<const QHeaderView*>(widget);
            if (!headerView)
            {
                break;
            }

        if(!headerView->property("HeaderIconCenter").isValid()
                || !headerView->property("HeaderIconCenter").toBool())
            {
                break;
            }

        if (const QStyleOptionHeader* header = qstyleoption_cast<const QStyleOptionHeader*>(option))
            {
                if (!header->icon.isNull() && header->text.isEmpty())
                {
                    QRect rect = header->rect;
                if (!header->icon.isNull()) {
                        int size = qRound(headerView->height() * 0.8);
                    QPixmap pixmap
                            = header->icon.pixmap(QSize(size, size), (header->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled);

                    QRect aligned = alignedRect(header->direction, QFlag(Qt::AlignCenter), pixmap.size() / pixmap.devicePixelRatio(), rect);
                        QRect inter = aligned.intersected(rect);
                    painter->drawPixmap(inter.x(), inter.y(), pixmap,
                                        inter.x() - aligned.x(), inter.y() - aligned.y(),
                            qRound(aligned.width() * pixmap.devicePixelRatio() + 0.5),
                            qRound(pixmap.height() * pixmap.devicePixelRatio() + 0.5));
                        return;
                    }
                }
            }
            break;
        }
        default:
            break;
    }

    QProxyStyle::drawControl(element, option, painter, widget);
}

void MegaProxyStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const
{
    painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
    QProxyStyle::drawItemPixmap(painter, rect, alignment, pixmap);
}

void MegaProxyStyle::drawItemText(QPainter *painter, const QRect &rect, int flags, const QPalette &pal, bool enabled, const QString &text, QPalette::ColorRole textRole) const
{
    painter->setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
    QProxyStyle::drawItemText(painter, rect, flags, pal, enabled, text, textRole);
}

void MegaProxyStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const
{
    painter->setRenderHints(QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform, true);
    switch (element)
    {
        case QStyle::PE_PanelItemViewItem:
            // Fallthrough
        case QStyle::PE_PanelItemViewRow:
            // Fallthrough
        case QStyle::PE_IndicatorBranch:
            break;
        default:
        {
            painter->setRenderHint(QPainter::Antialiasing, true);
        }
    }

    if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull())
    {
        auto view = dynamic_cast<const QAbstractItemView*>(widget);
        if (view)
        {
            auto index = view->indexAt(option->rect.topLeft());

            if (index.isValid())
            {
                if(!(index.flags() & Qt::ItemIsDropEnabled) && !(index.flags() & Qt::ItemIsDragEnabled))
                {
                    return;
                }

                QStyleOption modOption(*option);

                int adjustTop(0);

                //The top and bottom rows indicator was cut by the middle
                //Move the indicator lines avoid this ugly cut
                {
                    auto indexRect = view->visualRect(index);
                    auto isTop(modOption.rect.y() == indexRect.y());

                    if(isTop && index.row() == 0)
                    {
                        adjustTop = 5;
                    }
                    else if(!isTop && (index.row() == (view->model()->rowCount() -1)))
                    {
                        adjustTop = -5;
                    }

                    modOption.rect.adjust(0,adjustTop,0,adjustTop);
                }

                QPoint leftPoint(modOption.rect.topLeft() + QPoint(25, 0));
                QPoint rightPoint(modOption.rect.topRight() - QPoint(25, 0));

                QPen linepen(0xFF654F);
                linepen.setCapStyle(Qt::RoundCap);
                linepen.setWidth(8);
                painter->setPen(linepen);

                painter->drawPoint(leftPoint);
                painter->drawPoint(rightPoint);

                QPen whitepen(Qt::white);
                whitepen.setWidth(4);
                whitepen.setCapStyle(Qt::RoundCap);
                painter->setPen(whitepen);
                painter->drawPoint(leftPoint);
                painter->drawPoint(rightPoint);

                modOption.rect.setLeft(30);
                modOption.rect.setRight(modOption.rect.width());
                linepen.setWidth(2);
                painter->setPen(linepen);

                QProxyStyle::drawPrimitive(element, &modOption, painter, widget);
            }
        }

        return;
    }
    else if (element == QStyle::PE_FrameFocusRect && dynamic_cast<const QTreeView*>(widget))
    {
        return;
    }
    QProxyStyle::drawPrimitive(element, option, painter, widget);
}

int MegaProxyStyle::pixelMetric(PixelMetric metric, const QStyleOption * option, const QWidget * widget) const
{
    if(metric == QStyle::PM_SmallIconSize)
    {
        return 24;
    }
    return QProxyStyle::pixelMetric(metric, option, widget);
}

int MegaProxyStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const
{
    if (hint == QStyle::SH_ToolTip_WakeUpDelay)
    {
        return TOOLTIP_DELAY;
    }
    else if(hint == QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea)
    {
        return true;
    }
    return QProxyStyle::styleHint(hint, option, widget, returnData);
}

QIcon MegaProxyStyle::standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const
{
    switch (standardIcon)
    {
        case SP_MessageBoxInformation:
            return QIcon(QString::fromLatin1("://images/icon_info.png"));
        case SP_MessageBoxQuestion:
            return QIcon(QString::fromLatin1("://images/icon_question.png"));
        case SP_MessageBoxCritical:
            return QIcon(QString::fromLatin1("://images/icon_error.png"));
        case SP_MessageBoxWarning:
            return QIcon(QString::fromLatin1("://images/icon_warning.png"));
        default:
            break;
    }
    return QProxyStyle::standardIcon(standardIcon, option, widget);
}

void MegaProxyStyle::polish(QWidget *widget)
{
    if (!widget)
    {
        return;
    }

    if(auto spinBox = qobject_cast<QSpinBox*>(widget))
    {
        EventManager::addEvent(spinBox, QEvent::Wheel, EventHelper::BLOCK);
    }
    else if(auto comboBox = qobject_cast<QComboBox*>(widget))
    {
        EventManager::addEvent(comboBox, QEvent::Wheel, EventHelper::BLOCK);
    }
    else if (auto menu = qobject_cast<QMenu*>(widget))
    {
        // Only a genuine left-button press+release performed on the menu itself may
        // activate an entry. QMenu activates on release only while QMenuPrivate's
        // (static, process-wide) mouseDown still points at the menu; that flag is
        // cleared only inside mouseReleaseEvent. Because the tray menu pops up next to
        // the cursor, the popup's mouse grab can deliver an "orphan" release — one
        // whose press went to another widget (the button/icon that opened the menu) —
        // straight onto the last entry, triggering it by itself (observed: Exit /
        // Settings firing without the user choosing them). Block any release that was
        // not preceded by a press on this same menu since it was shown.
        auto pressSeen = std::make_shared<bool>(false);

        EventManager::addEvent(menu,
                               QEvent::Show,
                               [pressSeen](QEvent*)
                               {
                                   *pressSeen = false;
                                   return false;
                               });

        EventManager::addEvent(menu,
                               QEvent::MouseButtonPress,
                               [pressSeen, menu](QEvent* event)
                               {
                                   if (auto mouseEvent = dynamic_cast<QMouseEvent*>(event))
                                   {
                                       if (mouseEvent->button() != Qt::MouseButton::LeftButton)
                                       {
                                           // Block non-left presses only when they land on the
                                           // menu itself, where they could activate an entry.
                                           // Presses outside the popup must reach QMenu so it
                                           // closes itself (e.g. right-clicking another row
                                           // while a context menu is already open).
                                           return menu->rect().contains(mouseEvent->pos());
                                       }
                                       *pressSeen = true;
                                   }

                                   return false;
                               });

        EventManager::addEvent(menu,
                               QEvent::MouseButtonRelease,
                               [pressSeen](QEvent* event)
                               {
                                   if (auto mouseEvent = dynamic_cast<QMouseEvent*>(event))
                                   {
                                       if (mouseEvent->button() != Qt::MouseButton::LeftButton)
                                       {
                                           return true;
                                       }
                                       if (!*pressSeen)
                                       {
                                           // Orphan release (no matching press on this
                                           // menu since it was shown) — ignore it.
                                           return true;
                                       }
                                       *pressSeen = false;
                                   }

                                   return false;
                               });

        if (menu->property("class").toString() == QLatin1String("MegaMenu") &&
            !menu->testAttribute(Qt::WA_TranslucentBackground))
        {
            menu->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint);
            menu->setAttribute(Qt::WA_TranslucentBackground);
        }
    }
    else if(qobject_cast<QDialog*>(widget) || qobject_cast<QWindow*>(widget))
    {
        if(MegaSyncApp->getStatsEventHandler())
        {
            widget->installEventFilter(MegaSyncApp->getStatsEventHandler());
        }
    }

    QProxyStyle::polish(widget);
}

void MegaProxyStyle::polish(QPalette &pal)
{
    QProxyStyle::polish(pal);
}

void MegaProxyStyle::polish(QApplication *app)
{
    QProxyStyle::polish(app);
}

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

void MegaProxyStyle::unpolish(QApplication *app)
{
    QProxyStyle::unpolish(app);
}

bool MegaProxyStyle::event(QEvent *e)
{
    return QProxyStyle::event(e);
}
