/*
 * Plasma applet to display indicators from libindicate
 *
 * Copyright 2009 Canonical Ltd.
 *
 * Authors:
 * - Aurélien Gâteau <aurelien.gateau@canonical.com>
 *
 * License: GPL v3
 */
// Self
#include "message-indicator.h"

// Qt
#include <QGraphicsLinearLayout>
#include <QGraphicsSceneMouseEvent>
#include <QLabel>
#include <QLayout>
#include <QRegExp>

// KDE
#include <KGlobalSettings>
#include <KIcon>
#include <Plasma/IconWidget>
#include <Plasma/Theme>
#include <Plasma/ToolTipManager>

// Local
#include "delegate.h"
#include "view.h"

//#define DUMP_MODELS

static const char* NO_NEW_STUFF_ICON = "normal";
static const char* NEW_STUFF_ICON = "new";

K_EXPORT_PLASMA_APPLET(message-indicator, MessageIndicator)


MessageIndicator::MessageIndicator(QObject* parent, const QVariantList& args)
: Plasma::PopupApplet(parent, args)
, mListener(QIndicate::Listener::defaultInstance())
, mSourceModel(0)
, mStack(new QWidget)
, mView(new ExpandedTreeView(mStack))
, mSvg(new Plasma::Svg(this))
, mNoIndicatorLabel(new QLabel(mStack))
{
    setBackgroundHints(StandardBackground);
    setAspectRatioMode(Plasma::ConstrainedSquare);
    resize(48, 48);

    mNoIndicatorLabel->setText(i18n(
        "<p>The Message Indicator widget helps you keep track of incoming messages in a non-intrusive way.</p>"
        "<p>To take advantage of it, you need to enable support for Message Indicator in your application."
        " Applications with support for Message Indicator include:</p>"
        "<ul>"
        "<li>Kopete</li>"
        "<li>Konversation</li>"
        "<li>Quassel</li>"
        "<li>KMail</li>"
        "</ul>"
        ));
    mNoIndicatorLabel->setWordWrap(true);
    mNoIndicatorLabel->setOpenExternalLinks(true);
    mCurrentWidget = mNoIndicatorLabel;

    mSvg->setImagePath("icons/message-indicator");
    mSvg->setContainsMultipleImages(true);
}

MessageIndicator::~MessageIndicator()
{
    removeInterestOnServers();
}

void MessageIndicator::init()
{
    Plasma::Applet::init();
    setWidget(mStack);

    Plasma::ToolTipManager::self()->registerWidget(this);
    connect(mListener,
            SIGNAL(serverAdded(QIndicate::Listener::Server*, const QString&)),
            SLOT(slotServerAdded(QIndicate::Listener::Server*))
            );

    initSourceModel();
    initView();
    initIcon();

    updateStatus();
}

void MessageIndicator::initIcon()
{
    mSvg->resize(contentsRect().size());
    // activate() is emitted on all clicks. We disable it because we
    // want the popup to show on left-click only, middle-click is used
    // to activate the latest indicator.
    disconnect(this, SIGNAL(activate()), 0, 0);
    installSceneEventFilter(this);
    updateStatus();
}

bool MessageIndicator::sceneEventFilter(QGraphicsItem* item, QEvent* event)
{
    if (event->type() == QEvent::GraphicsSceneMousePress) {
        return true;
    }
    if (event->type() == QEvent::GraphicsSceneMouseRelease) {
        QGraphicsSceneMouseEvent* mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
        if (mouseEvent->button() == Qt::MidButton
            || (mouseEvent->button() == Qt::LeftButton
                && (mouseEvent->modifiers() & Qt::ShiftModifier)))
        {
            activateLatestIndicator();
        } else {
            togglePopup();
        }
        return true;
    }
    return Plasma::PopupApplet::sceneEventFilter(item, event);
}

void MessageIndicator::initSourceModel()
{
    // Reg exp is a hack to avoid regressions while changing app server types
    // from "messaging" to "message.<something>"
    mSourceModel = new ListenerModel(mListener, QRegExp("^messag(e|ing)"));
    connect(mSourceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
            SLOT(slotRowsChanged(const QModelIndex&))
            );
    connect(mSourceModel, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
            SLOT(slotRowsChanged(const QModelIndex&))
            );
    connect(mSourceModel, SIGNAL(drawAttentionChanged(const QModelIndex&)),
            SLOT(slotDrawAttentionChanged())
            );
    #ifdef DUMP_MODELS
    connect(mSourceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
            SLOT(dumpModels())
            );
    #endif
}

void MessageIndicator::initView()
{
    mView->setModel(mSourceModel);

    mView->setItemDelegate(new Delegate(this));
    mView->setSelectionMode(QAbstractItemView::NoSelection);

    initPalette();
    connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(initPalette()));
    connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), SLOT(initPalette()));
    mView->setFrameStyle(QFrame::NoFrame);
    mView->setRootIsDecorated(false);
    mView->setHeaderHidden(true);
    mView->setIndentation(16);
    mView->setIconSize(QSize(16, 16));

    // Disable scrollbars: we compute the size of the view so that its content
    // fits without scrollbars, but if we do not disable them a vertical
    // scrollbar sometimes appears when the view grows while visible
    mView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    mView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    mView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    connect(mView, SIGNAL(clicked(const QModelIndex&)),
            SLOT(slotClicked(const QModelIndex&))
            );
    connect(mView, SIGNAL(sizeChanged()),
            SLOT(adjustViewSize())
            );
}

void MessageIndicator::initPalette()
{
    QPalette pal = widget()->palette();
    pal.setColor(QPalette::Base, Qt::transparent);
    QColor textColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
    pal.setColor(QPalette::Text, textColor);
    widget()->setPalette(pal);
}

void MessageIndicator::toolTipAboutToShow()
{
    Plasma::ToolTipContent toolTip;

    QSizeF oldSize = mSvg->size();
    // FIXME: Hardcoded size
    mSvg->resize(32, 32);
    QString element = status() == Plasma::NeedsAttentionStatus ? NEW_STUFF_ICON : NO_NEW_STUFF_ICON;
    QPixmap pix = mSvg->pixmap(element);
    mSvg->resize(oldSize);
    toolTip.setImage(pix);

    toolTip.setMainText(i18n("Message Indicator"));
    int appCount = mSourceModel->rowCount();
    if (appCount == 0) {
        toolTip.setSubText(i18n("No applications running"));
    } else {
        toolTip.setSubText(i18np("One application running", "%1 applications running", appCount));
    }
    Plasma::ToolTipManager::self()->setContent(this, toolTip);
}

void MessageIndicator::adjustViewSize()
{
    QSize sh = mCurrentWidget->sizeHint();
    mCurrentWidget->resize(sh);

    QWidget* dialog = widget()->parentWidget();
    if (!dialog) {
        kWarning() << "No parentWidget for applet widget()!";
        return;
    }
    int left, top, right, bottom;
    dialog->getContentsMargins(&left, &top, &right, &bottom);

    dialog->resize(sh.width() + left + right, sh.height() + top + bottom);

    // Hack: Plasma::Dialog only emits dialogResized() if its content is a
    // QGraphicsWidget. Emit it ourself to ensure the dialog is correctly
    // positioned.
    QMetaObject::invokeMethod(dialog, "dialogResized");
}

void MessageIndicator::constraintsEvent(Plasma::Constraints)
{
    mSvg->resize(contentsRect().size());
}

void MessageIndicator::paintInterface(QPainter* painter, const QStyleOptionGraphicsItem*, const QRect& contentsRect)
{
    int minSize = qMin(contentsRect.height(), contentsRect.width());
    QRect contentsSquare = QRect(contentsRect.x() + (contentsRect.width() - minSize) / 2, contentsRect.y() + (contentsRect.height() - minSize) / 2, minSize, minSize);

    QString element = status() == Plasma::NeedsAttentionStatus ? NEW_STUFF_ICON : NO_NEW_STUFF_ICON;
    mSvg->paint(painter, contentsSquare, element);
}

void MessageIndicator::popupEvent(bool show)
{
    if (show) {
        adjustViewSize();
    }
}

void MessageIndicator::slotClicked(const QModelIndex& index)
{
    mSourceModel->activate(index);
    hidePopup();
}

void MessageIndicator::slotRowsChanged(const QModelIndex& /*parent*/)
{
    updateStatus();

    #ifdef DUMP_MODELS
    dumpModels();
    #endif
}

void MessageIndicator::slotDrawAttentionChanged()
{
    updateStatus();

    #ifdef DUMP_MODELS
    dumpModels();
    #endif
}

void MessageIndicator::updateStatus()
{
    Plasma::ItemStatus status;
    if (mSourceModel && mSourceModel->rowCount() > 0) {
        // Check if one of the indicators want to draw attention
        QModelIndexList lst = mSourceModel->match(mSourceModel->index(0, 0),
                                                  ListenerModel::IndicatorDrawAttentionRole,
                                                  QVariant(true),
                                                  1 /* hits */,
                                                  Qt::MatchExactly | Qt::MatchRecursive);
        status = lst.isEmpty() ? Plasma::ActiveStatus : Plasma::NeedsAttentionStatus;
    } else {
        status = Plasma::PassiveStatus;
    }

    setStatus(status);

    // Update icon
    update();

    // Update views
    if (status == Plasma::PassiveStatus) {
        mView->hide();
        mNoIndicatorLabel->show();
        mCurrentWidget = mNoIndicatorLabel;
        hidePopup();
    } else {
        mView->show();
        mNoIndicatorLabel->hide();
        mCurrentWidget = mView;
    }
    adjustViewSize();
}

void MessageIndicator::slotServerAdded(QIndicate::Listener::Server* server)
{
    mListener->setInterest(server, QIndicate::InterestServerDisplay, true);
    mListener->setInterest(server, QIndicate::InterestServerSignal, true);
}

void MessageIndicator::removeInterestOnServers()
{
    for (int row = mSourceModel->rowCount() - 1; row >= 0; --row) {
        QIndicate::Listener::Server* server = 0;
        QIndicate::Listener::Indicator* indicator = 0;
        QModelIndex index = mSourceModel->index(row, 0);
        mSourceModel->getProxiesForIndex(index, &server, &indicator);
        if (server) {
            mListener->setInterest(server, QIndicate::InterestServerDisplay, false);
            mListener->setInterest(server, QIndicate::InterestServerSignal, false);
        } else {
            kWarning() << "No server for row" << row;
        }
    }
}

void MessageIndicator::activateLatestIndicator()
{
    QDateTime latestDateTime;
    QModelIndex latestIndicatorIndex;
    for (int row = mSourceModel->rowCount() - 1; row >= 0; --row) {
        QModelIndex serverIndex = mSourceModel->index(row, 0);
        for (int row2 = mSourceModel->rowCount(serverIndex) - 1; row2 >= 0; --row2) {
            QModelIndex indicatorIndex = mSourceModel->index(row2, 0, serverIndex);
            QDateTime dateTime = indicatorIndex.data(ListenerModel::IndicatorDateTimeRole).toDateTime();
            if (dateTime.isNull()) {
                continue;
            }
            if (latestDateTime.isNull() || latestDateTime < dateTime) {
                latestDateTime = dateTime;
                latestIndicatorIndex = indicatorIndex;
            }
        }
    }

    if (latestIndicatorIndex.isValid()) {
        mSourceModel->activate(latestIndicatorIndex);
    }
}

#ifdef DUMP_MODELS
#include "modeldump.h"

using namespace ModelDump;

void MessageIndicator::dumpModels()
{
    RoleDumperHash hash;
    hash.insert(Qt::DisplayRole, new SimpleRoleDumper("display"));
    hash.insert(Qt::DecorationRole, new SimpleRoleDumper("decoration"));
    hash.insert(ListenerModel::ServerTypeRole, new SimpleRoleDumper("type"));
    hash.insert(ListenerModel::CountRole, new SimpleRoleDumper("count"));
    hash.insert(ListenerModel::IndicatorDateTimeRole, new SimpleRoleDumper("time"));
    hash.insert(ListenerModel::IndicatorDrawAttentionRole, new SimpleRoleDumper("attention"));
    hash.insert(Qt::UserRole + 1, new PointerRoleDumper<QIndicate::Listener::Server>("server"));
    hash.insert(Qt::UserRole + 2, new PointerRoleDumper<QIndicate::Listener::Indicator>("indicator"));
    hash.insert(Qt::UserRole + 3, new SimpleRoleDumper("outdated"));
    qDebug();
    qDebug() << "## mSourceModel";
    dump(hash, mSourceModel);
}
#else
void MessageIndicator::dumpModels()
{
}
#endif

#include "message-indicator.moc"
