/***************************************************************************
*   Copyright 2007 by Enrico Ros <enrico.ros@gmail.com>                   *
*   Copyright 2007 by Riccardo Iaconelli <ruphy@kde.org>                  *
*   Copyright 2008 by Aaron Seigo <aseigo@kde.org>                        *
*   Copyright 2008 by Davide Bettio <davide.bettio@kdemail.net>           *
*                                                                         *
*   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 "resultscene.h"

#include <QtCore/QDebug>
#include <QtGui/QKeyEvent>
#include <QtCore/QMutexLocker>
#include <QtGui/QPainter>
#include <QtCore/QTimeLine>
#include <QtGui/QGraphicsGridLayout>
#include <QtGui/QGraphicsWidget>
#include <QtGui/QGraphicsProxyWidget>
#include <QtCore/QCoreApplication>

#include <KDE/KDebug>
#include <KDE/KIconLoader>
#include <KDE/KLineEdit>

#include <Plasma/AbstractRunner>
#include <Plasma/RunnerManager>

#include "selectionbar.h"

ResultScene::ResultScene(SharedResultData *resultData, Plasma::RunnerManager *manager, QWidget *focusBase, QObject *parent)
    : QGraphicsScene(parent),
      m_runnerManager(manager),
      m_viewableHeight(0),
      m_currentIndex(0),
      m_focusBase(focusBase),
      m_resultData(resultData)
{
    setItemIndexMethod(NoIndex);

    connect(m_runnerManager, SIGNAL(matchesChanged(QList<Plasma::QueryMatch>)),
            this, SLOT(setQueryMatches(QList<Plasma::QueryMatch>)));

    m_clearTimer.setSingleShot(true);
    m_clearTimer.setInterval(200);
    connect(&m_clearTimer, SIGNAL(timeout()), this, SLOT(clearMatches()));

    m_arrangeTimer.setSingleShot(true);
    m_arrangeTimer.setInterval(50);
    connect(&m_arrangeTimer, SIGNAL(timeout()), this, SLOT(arrangeItems()));

    m_selectionBar = new SelectionBar(0);
    connect(m_selectionBar, SIGNAL(appearanceChanged()), this, SLOT(updateItemMargins()));
    connect(m_selectionBar, SIGNAL(targetItemReached(QGraphicsItem*)), this, SLOT(highlightItem(QGraphicsItem*)));
    m_selectionBar->hide();
    updateItemMargins();

    addItem(m_selectionBar);
}

ResultScene::~ResultScene()
{
    clearMatches();
    delete m_selectionBar;
}

QSize ResultScene::minimumSizeHint() const
{
    QFontMetrics fm(font());
    return QSize(KIconLoader::SizeMedium * 4, (fm.height() * 5) * 3);
}

void ResultScene::setWidth(int width)
{
    const bool resizeItems = width != sceneRect().width();
    m_selectionBar->resize(width, m_selectionBar->size().height());

    if (resizeItems) {
        foreach (ResultItem *item, m_items) {
            item->calculateSize(width);
        }

        setSceneRect(itemsBoundingRect());
    }
}

void ResultScene::clearMatches()
{
    clearSelection();
    Plasma::QueryMatch dummy(0);
    foreach (ResultItem *item, m_items) {
        item->hide();
        item->setMatch(dummy);
    }

    m_viewableHeight = 0;
    emit matchCountChanged(0);
}

bool ResultScene::canMoveItemFocus() const
{
    // We prevent a late query result from stealing the item focus from the user
    // The item focus can be moved only if
    // 1) there is no item currently focused
    // 2) the currently focused item is not visible anymore
    // 3) the focusBase widget (the khistorycombobox) has focus (i.e. the user is still typing or waiting) AND the currently focused item has not been hovered

    ResultItem * focusedItem = currentlyFocusedItem();

    return !(focusedItem) ||
            (!m_items.contains(focusedItem)) ||
            (m_focusBase->hasFocus() && !focusedItem->mouseHovered()) ;
}

int ResultScene::viewableHeight() const
{
    return m_viewableHeight;
}

void ResultScene::setQueryMatches(const QList<Plasma::QueryMatch> &m)
{
    //kDebug() << "============================" << endl << "matches retrieved: " << m.count();
    /*
    foreach (const Plasma::QueryMatch &match, m) {
        kDebug() << "    " << match.id() << match.text();
    }
    */

    if (m.isEmpty()) {
        //kDebug() << "clearing";
        //resize(width(), 0);
        m_clearTimer.start();
        return;
    }

    m_clearTimer.stop();
    const int maxItemsAllowed = 50;

    if (m_items.isEmpty()) {
        //QTime t;
        //t.start();
        for (int i = 0; i < maxItemsAllowed; ++i) {
            ResultItem *item = new ResultItem(m_resultData, 0);
            item->setContentsMargins(m_itemMarginLeft, m_itemMarginTop,
                                     m_itemMarginRight, m_itemMarginBottom);
            item->hide();
            item->setIndex(i);
            connect(item, SIGNAL(ensureVisibility(QGraphicsItem*)), this, SIGNAL(ensureVisibility(QGraphicsItem*)));
            connect(item, SIGNAL(activated(ResultItem*)), this, SIGNAL(itemActivated(ResultItem*)));
            connect(item, SIGNAL(sizeChanged(ResultItem*)), this, SLOT(scheduleArrangeItems()));

            m_items << item;
            addItem(item);
        }

        arrangeItems();
        //kDebug() << "creating all items took" << t.elapsed();
    }

    // we keep track of what was previously focused so if the user changes focus
    // and more items come in ... we don't reset that on them unecessarily
    // see the keepFocus bool down below
    ResultItem *currentFocus = currentlyFocusedItem();
    QString lastFocusId;
    if (currentFocus && currentFocus->isValid()) {
        lastFocusId = currentFocus->id();
    }

    QList<Plasma::QueryMatch> matches = m;
    qSort(matches.begin(), matches.end());
    QListIterator<Plasma::QueryMatch> mit(matches);
    mit.toBack();
    QListIterator<ResultItem *> rit(m_items);
    QGraphicsWidget *prevTabItem = 0;

    const int viewableHeight = m_viewableHeight;
    while (mit.hasPrevious() && rit.hasNext()) {
        ResultItem * item = rit.next();
        item->setMatch(mit.previous());
        prevTabItem = item->arrangeTabOrder(prevTabItem);
        item->show();
        m_viewableHeight = item->sceneBoundingRect().bottom();
    }

    Plasma::QueryMatch dummy(0);
    while (rit.hasNext()) {
        ResultItem *item = rit.next();
        item->hide();
        if (item->isValid()) {
            item->setMatch(dummy);
        }
    }

    const bool keepFocus = currentFocus && currentFocus->isValid() && currentFocus->id() == lastFocusId;
    if (keepFocus) {
        m_selectionBar->show();
        emit ensureVisibility(currentFocus);
    } else {
        clearSelection();
        ResultItem *first = m_items.at(0);
        setFocusItem(first);
        first->setSelected(true);
        first->highlight(true);
        emit ensureVisibility(first);
    }

    emit matchCountChanged(qMin(m.count(), maxItemsAllowed));
    if (viewableHeight != m_viewableHeight) {
        emit viewableHeightChanged();
    }
}

void ResultScene::scheduleArrangeItems()
{
    if (!m_arrangeTimer.isActive()) {
        m_arrangeTimer.start();
    }
}

void ResultScene::arrangeItems()
{
    int y = 0;
    QListIterator<ResultItem*> matchIt(m_items);
    const int viewableHeight = m_viewableHeight;
    while (matchIt.hasNext()) {
        ResultItem *item = matchIt.next();
        //kDebug()  << item->name() << item->id() << item->priority() << i;

        item->setPos(0, y);
        //kDebug() << item->pos();
        y += item->geometry().height();
        if (item->isVisible()) {
            m_viewableHeight = item->sceneBoundingRect().bottom();
        }
    }

    //kDebug() << "setting scene rect to" << itemsBoundingRect();
    const QRectF newRect = itemsBoundingRect();
    setSceneRect(newRect);
    if (viewableHeight != m_viewableHeight) {
        emit viewableHeightChanged();
    }
}

void ResultScene::highlightItem(QGraphicsItem *item)
{
    ResultItem *rItem = dynamic_cast<ResultItem *>(item);
    if (rItem) {
        rItem->highlight(true);
    }
}

void ResultScene::focusInEvent(QFocusEvent *focusEvent)
{

    // The default implementation of focusInEvent assumes that if the scene has no focus
    // then it has no focused item; thus, when a scene gains focus, focusInEvent gives
    // focus to the last focused item.
    // In our case this assumption is not true, as an item can be focused before the scene,
    // therefore we revert the behaviour by re-selecting the previously selected item

    ResultItem *currentFocus = currentlyFocusedItem();

    QGraphicsScene::focusInEvent(focusEvent);

    switch (focusEvent->reason()) {
    case Qt::TabFocusReason:
    case Qt::BacktabFocusReason:
    case Qt::OtherFocusReason:
        // on tab focus in, we want to actually select the second item
        // since the first item is always "passively" selected by default
        if (!currentFocus || currentFocus == m_items.first()) {
            ResultItem *newFocus = m_items[0];
            if (newFocus->firstTabItem() != newFocus) {
                setFocusItem(newFocus->firstTabItem());
            } else {
                newFocus = m_items[1];
                if (newFocus->isVisible()) {
                    setFocusItem(newFocus);
                    emit ensureVisibility(newFocus);
                }
            }
        } else if (currentFocus) {
            setFocusItem(currentFocus);
        }
        break;
    default:
        if (currentFocus) {
            setFocusItem(currentFocus);
        }
        break;
    }
}

void ResultScene::keyPressEvent(QKeyEvent * keyEvent)
{
    //kDebug() << "m_items (size): " << m_items.size() << "\n";
    switch (keyEvent->key()) {
        case Qt::Key_Up:
        case Qt::Key_Left:
            selectPreviousItem();
            break;

        case Qt::Key_Down:
        case Qt::Key_Right:
            selectNextItem();
        break;

        default:
            // pass the event to the item
            QGraphicsScene::keyPressEvent(keyEvent);
            if (!keyEvent->isAccepted() && m_focusBase &&
                    (keyEvent->key() != Qt::Key_Tab) &&
                    (keyEvent->key() != Qt::Key_PageUp) &&
                    (keyEvent->key() != Qt::Key_PageDown) &&
                    (keyEvent->modifiers() == Qt::NoModifier)) {
                m_focusBase->setFocus();
                QCoreApplication::sendEvent(m_focusBase, keyEvent);
            }
            return;
        break;
    }
}

ResultItem* ResultScene::currentlyFocusedItem() const
{
    QGraphicsWidget* widget = static_cast<QGraphicsWidget*>(focusItem());
    if (!widget) {
        return 0;
    }

    ResultItem *currentFocus = qobject_cast<ResultItem*>(widget);
    if (!currentFocus) {
        //If we focused an action button, find the resultItem
        //FIXME: the config button
        currentFocus = qobject_cast<ResultItem*>(widget->parentWidget()->parentWidget());
    }

    return currentFocus;
}

void ResultScene::selectPreviousItem()
{
    ResultItem *currentFocus = currentlyFocusedItem();
    int currentIndex = currentFocus ? currentFocus->index() : 0;

    if (currentIndex == 0) {
        m_focusBase->setFocus();
        return;
    }

    currentFocus = m_items.at(currentIndex - 1);

    if (currentFocus->isVisible()) {
        setFocusItem(currentFocus);
        emit ensureVisibility(currentFocus);
    }
}

void ResultScene::selectNextItem()
{
    ResultItem *currentFocus = currentlyFocusedItem();
    int currentIndex = currentFocus ? currentFocus->index() : 0;

    do {
        ++currentIndex;
        if (currentIndex >= m_items.size()) {
            return;
        }
        currentFocus = m_items.at(currentIndex);
    } while (!currentFocus->isVisible() && currentIndex < m_items.size());

    if (currentFocus->isVisible()) {
        setFocusItem(currentFocus);
        emit ensureVisibility(currentFocus);
    }
}

void ResultScene::queryCleared()
{
    //m_selectionBar->setTargetItem(0);
    setFocusItem(0);
    clearSelection();
}

ResultItem* ResultScene::defaultResultItem() const
{
    if (m_items.isEmpty()) {
        kDebug() << "empty";
        return 0;
    }

    kDebug() << (QObject*) m_items[0] << m_items.count();
    return m_items[0];
}

void ResultScene::updateItemMargins()
{
    m_selectionBar->getContentsMargins(&m_itemMarginLeft, &m_itemMarginTop,
                                       &m_itemMarginRight, &m_itemMarginBottom);

    foreach (ResultItem *item, m_items) {
        item->setContentsMargins(m_itemMarginLeft, m_itemMarginTop,
                                m_itemMarginRight, m_itemMarginBottom);
    }
}

#include "resultscene.moc"

