/***************************************************************************
 *   Copyright 2006 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 "interface.h"

#include <QAction>
#include <QApplication>
#include <QDesktopWidget>
#include <QGraphicsView>
#include <QHBoxLayout>
#include <QHideEvent>
#include <QLabel>
#include <QShortcut>
#include <QToolButton>
#include <QVBoxLayout>

#include <KActionCollection>
#include <KHistoryComboBox>
#include <KCompletion>
#include <KCompletionBox>
#include <KDebug>
#include <KDialog>
#include <KLineEdit>
#include <KLocale>
#include <KGlobalSettings>
#include <KPushButton>
#include <KTitleWidget>
#include <KWindowSystem>

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

#include "krunnersettings.h"
#include "interfaces/default/resultscene.h"
#include "interfaces/default/resultitem.h"

static const int MIN_WIDTH = 420;

Interface::Interface(Plasma::RunnerManager *runnerManager, QWidget *parent)
    : KRunnerDialog(runnerManager, parent),
      m_delayedRun(false),
      m_running(false),
      m_queryRunning(false)
{
    m_hideResultsTimer.setSingleShot(true);
    connect(&m_hideResultsTimer, SIGNAL(timeout()), this, SLOT(hideResultsArea()));

    QWidget *w = mainWidget();
    m_layout = new QVBoxLayout(w);
    m_layout->setMargin(0);

    m_buttonContainer = new QWidget(w);
    QHBoxLayout *bottomLayout = new QHBoxLayout(m_buttonContainer);
    bottomLayout->setMargin(0);

    m_configButton = new QToolButton(m_buttonContainer);
    m_configButton->setText(i18n("Settings"));
    m_configButton->setToolTip(i18n("Settings"));
    m_configButton->setIcon(m_iconSvg->pixmap("configure"));
    connect(m_configButton, SIGNAL(clicked()), SLOT(showConfigDialog()));
    bottomLayout->addWidget( m_configButton );

    /*
    KPushButton *m_optionsButton = new KPushButton(KStandardGuiItem::configure(), m_buttonContainer);
    m_optionsButton->setDefault(false);
    m_optionsButton->setAutoDefault(false);
    m_optionsButton->setText(i18n("Show Options"));
    m_optionsButton->setEnabled(false);
    m_optionsButton->setCheckable(true);
    connect(m_optionsButton, SIGNAL(toggled(bool)), SLOT(showOptions(bool)));
    bottomLayout->addWidget( m_optionsButton );
    */

    m_activityButton = new QToolButton(m_buttonContainer);
//    m_activityButton->setDefault(false);
//    m_activityButton->setAutoDefault(false);
    m_activityButton->setText(i18n("Show System Activity"));
    m_activityButton->setToolTip(i18n("Show System Activity"));
    m_activityButton->setIcon(m_iconSvg->pixmap("status"));
    connect(m_activityButton, SIGNAL(clicked()), qApp, SLOT(showTaskManager()));
    connect(m_activityButton, SIGNAL(clicked()), this, SLOT(close()));
    bottomLayout->addWidget(m_activityButton);
    //bottomLayout->addStretch(10);

    m_closeButton = new QToolButton(m_buttonContainer);
    KGuiItem guiItem = KStandardGuiItem::close();
    m_closeButton->setText(guiItem.text());
    m_closeButton->setToolTip(guiItem.text().remove('&'));
    m_closeButton->setIcon(m_iconSvg->pixmap("close"));
//    m_closeButton->setDefault(false);
//    m_closeButton->setAutoDefault(false);
    connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(close()));
    bottomLayout->addWidget(m_closeButton);

    m_layout->addWidget(m_buttonContainer);

    m_searchTerm = new KHistoryComboBox(false, w);
    m_searchTerm->setDuplicatesEnabled(false);

    KLineEdit *lineEdit = new KLineEdit(m_searchTerm);
    QAction *focusEdit = new QAction(this);
    focusEdit->setShortcut(Qt::Key_F6);
    
    // in therory, the widget should detect the direction from the content
    // but this is not available in Qt4.4/KDE 4.2, so the best default for this widget
    // is LTR: as it's more or less a "command line interface"
    // FIXME remove this code when KLineEdit has automatic direction detection of the "paragraph"
    m_searchTerm->setLayoutDirection( Qt::LeftToRight );
    
    connect(focusEdit, SIGNAL(triggered(bool)), lineEdit, SLOT(setFocus()));
    addAction(focusEdit);

    // the order of these next few lines if very important.
    // QComboBox::setLineEdit sets the autoComplete flag on the lineedit,
    // and KComboBox::setAutoComplete resets the autocomplete mode! ugh!
    m_searchTerm->setLineEdit(lineEdit);

    m_completion = new KCompletion();
    lineEdit->setCompletionObject(m_completion);
    lineEdit->setCompletionMode(static_cast<KGlobalSettings::Completion>(KRunnerSettings::queryTextCompletionMode()));
    lineEdit->setClearButtonShown(true);
    QStringList pastQueryItems = KRunnerSettings::pastQueries();
    m_searchTerm->setHistoryItems(pastQueryItems);
    m_completion->insertItems(pastQueryItems);
    bottomLayout->insertWidget(2, m_searchTerm, 10);

    m_statusLayout = new QHBoxLayout();
    m_descriptionLabel = new QLabel(w);
    m_descriptionLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
    m_descriptionLabel->hide();
    m_statusLayout->addWidget(m_descriptionLabel, 10, Qt::AlignLeft | Qt::AlignTop);

    m_previousPage = new QLabel(w);
    m_previousPage->setText("<a href=\"prev\">&lt;&lt;</a>");
    m_previousPage->hide();
    m_statusLayout->addWidget(m_previousPage, 0, Qt::AlignLeft | Qt::AlignTop);

    m_nextPage = new QLabel(w);
    m_nextPage->setText("<a href=\"next\">&gt;&gt;</a>");
    m_nextPage->hide();
    m_statusLayout->addWidget(m_nextPage, 0, Qt::AlignLeft | Qt::AlignTop);

    m_layout->addLayout(m_statusLayout);

    m_dividerLine = new QWidget(w);
    m_dividerLine->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Minimum);
    m_dividerLine->setFixedHeight(1);
    m_dividerLine->setAutoFillBackground(true);
    m_dividerLine->hide();
    m_layout->addWidget(m_dividerLine);

    m_resultsView = new QGraphicsView(w);
    m_resultsView->setFrameStyle(QFrame::NoFrame);
    m_resultsView->viewport()->setAutoFillBackground(false);
    m_resultsView->setInteractive(true);
    m_resultsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_resultsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_resultsView->setOptimizationFlag(QGraphicsView::DontSavePainterState);
    m_resultsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);

    //kDebug() << "size:" << m_resultsView->size() << m_resultsView->minimumSize();
    m_resultsScene = new ResultScene(runnerManager, this);
    m_resultsView->setScene(m_resultsScene);
    m_resultsView->setMinimumSize(m_resultsScene->minimumSizeHint());
    connect(m_resultsScene, SIGNAL(matchCountChanged(int)), this, SLOT(matchCountChanged(int)));
    connect(m_resultsScene, SIGNAL(itemActivated(ResultItem *)), this, SLOT(run(ResultItem *)));
    connect(m_resultsScene, SIGNAL(itemHoverEnter(ResultItem *)), this, SLOT(updateDescriptionLabel(ResultItem *)));
    connect(m_resultsScene, SIGNAL(itemHoverLeave(ResultItem *)), m_descriptionLabel, SLOT(clear()));
    connect(m_previousPage, SIGNAL(linkActivated(const QString&)), m_resultsScene, SLOT(previousPage()));
    connect(m_nextPage, SIGNAL(linkActivated(const QString&)), m_resultsScene, SLOT(nextPage()));

    m_layout->addWidget(m_resultsView);

    connect(lineEdit, SIGNAL(userTextChanged(QString)), this, SLOT(queryTextEdited(QString)));
    connect(m_searchTerm, SIGNAL(returnPressed()), this, SLOT(runDefaultResultItem()));

    themeUpdated();
    connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeUpdated()));

    new QShortcut(QKeySequence( Qt::Key_Escape ), this, SLOT(close()));

    m_layout->addStretch(1);
    setTabOrder(0, m_configButton);
    setTabOrder(m_configButton, m_activityButton);
    setTabOrder(m_activityButton, m_searchTerm);
    setTabOrder(m_searchTerm, m_previousPage);
    setTabOrder(m_previousPage, m_nextPage);
    setTabOrder(m_nextPage, m_resultsView);
    setTabOrder(m_resultsView, m_closeButton);

    //kDebug() << "size:" << m_resultsView->size() << m_resultsView->minimumSize();
    // we restore the original size, which will set the results view back to its
    // normal size, then we hide the results view and resize the dialog
    setMinimumSize(QSize(MIN_WIDTH , 0));
    if (KGlobal::config()->hasGroup("Interface")) {
        KConfigGroup interfaceConfig(KGlobal::config(), "Interface");
        restoreDialogSize(interfaceConfig);
    }

    //kDebug() << "size:" << minimumSizeHint() << minimumSize();
    QTimer::singleShot(0, this, SLOT(resetInterface()));
}

void Interface::resizeEvent(QResizeEvent *event)
{
    Plasma::Theme *theme = Plasma::Theme::defaultTheme();
    int gradientWidth = contentsRect().width() - KDialog::marginHint()*2;
    QLinearGradient gr(0, 0, gradientWidth, 0);
    gr.setColorAt(0, Qt::transparent);
    gr.setColorAt(.35, theme->color(Plasma::Theme::TextColor));
    gr.setColorAt(.65, theme->color(Plasma::Theme::TextColor));
    gr.setColorAt(1, Qt::transparent);
    {
        QPalette p = palette();
        p.setBrush(QPalette::Background, gr);
        m_dividerLine->setPalette(p);
    }
    m_resultsScene->resize(m_resultsView->width(), m_resultsView->height());
    KRunnerDialog::resizeEvent(event);
}

Interface::~Interface()
{
    KRunnerSettings::setPastQueries(m_searchTerm->historyItems());
    KRunnerSettings::setQueryTextCompletionMode(m_searchTerm->completionMode());
    KRunnerSettings::self()->writeConfig();
    KConfigGroup interfaceConfig(KGlobal::config(), "Interface");
    saveDialogSize(interfaceConfig);
    KGlobal::config()->sync();
}

void Interface::themeUpdated()
{
    Plasma::Theme *theme = Plasma::Theme::defaultTheme();
    QColor buttonBgColor = theme->color(Plasma::Theme::BackgroundColor);
    QString buttonStyleSheet = QString("QToolButton { border: 1px solid %4; border-radius: 4px; padding: 2px;"
                                       " background-color: rgba(%1, %2, %3, %5); }")
                                      .arg(buttonBgColor.red())
                                      .arg(buttonBgColor.green())
                                      .arg(buttonBgColor.blue())
                                      .arg(theme->color(Plasma::Theme::HighlightColor).name(), "50%");
    buttonBgColor = theme->color(Plasma::Theme::TextColor);
    buttonStyleSheet += QString("QToolButton:hover { border: 2px solid %1; }")
                               .arg(theme->color(Plasma::Theme::HighlightColor).name());
    buttonStyleSheet += QString("QToolButton:focus { border: 2px solid %1; }")
                               .arg(theme->color(Plasma::Theme::HighlightColor).name());
    m_configButton->setStyleSheet(buttonStyleSheet);
    m_activityButton->setStyleSheet(buttonStyleSheet);
    m_closeButton->setStyleSheet(buttonStyleSheet);
    //kDebug() << "stylesheet is" << buttonStyleSheet;

    QPalette p = m_descriptionLabel->palette();
    p.setColor(QPalette::WindowText, theme->color(Plasma::Theme::TextColor));
    p.setColor(QPalette::Link, theme->color(Plasma::Theme::TextColor));
    p.setColor(QPalette::LinkVisited, theme->color(Plasma::Theme::TextColor));
    m_descriptionLabel->setPalette(p);
    m_previousPage->setPalette(p);
    m_nextPage->setPalette(p);

    //reset the icons
    m_configButton->setIcon(m_iconSvg->pixmap("configure"));
    m_activityButton->setIcon(m_iconSvg->pixmap("status"));
    m_closeButton->setIcon(m_iconSvg->pixmap("close"));
}

void Interface::clearHistory()
{
    m_searchTerm->clearHistory();
    KRunnerSettings::setPastQueries(m_searchTerm->historyItems());
}

void Interface::display(const QString &term)
{
    m_searchTerm->setFocus();

    KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());

    // TODO: set a nice welcome string when the string freeze lifts
    m_descriptionLabel->clear();

    show();
    resetInterface();
    centerOnScreen();
    KWindowSystem::forceActiveWindow(winId());

    if (!term.isEmpty()) {
        m_searchTerm->setItemText(0, term);
    }
}

void Interface::centerOnScreen()
{
    int screen = 0;
    if (QApplication::desktop()->numScreens() > 1) {
        screen = QApplication::desktop()->screenNumber(QCursor::pos());
    }

    if (m_resultsView->isVisibleTo(this)) {
        KDialog::centerOnScreen(this, screen);
        return;
    }

    // center it as if the results view was already visible
    QDesktopWidget *desktop = qApp->desktop();
    QRect r = desktop->screenGeometry(screen);
    int w = width();
    int h = height() + m_resultsView->height();
    move(r.left() + (r.width() / 2) - (w / 2),
         r.top() + (r.height() / 2) - (h / 2));
    //kDebug() << "moved to" << pos();
}

void Interface::setWidgetPalettes()
{
    // a nice palette to use with the widgets
    QPalette widgetPalette = palette();
    QColor bgColor = widgetPalette.color( QPalette::Active,
                                                QPalette::Base );
    bgColor.setAlpha( 200 );
    widgetPalette.setColor( QPalette::Base, bgColor );

    m_searchTerm->setPalette( widgetPalette );
}

void Interface::resetInterface()
{
    setStaticQueryMode(false);
    m_delayedRun = false;
    m_searchTerm->setCurrentItem(QString(), true, 0);
    m_descriptionLabel->clear();
    m_descriptionLabel->hide();
    m_previousPage->hide();
    m_nextPage->hide();
    m_resultsScene->clearQuery();
    m_resultsView->hide();
    m_dividerLine->hide();
    setMinimumSize(QSize(MIN_WIDTH, 0));
    //kDebug() << size() << minimumSizeHint();
    resize(qMax(minimumSizeHint().width(), MIN_WIDTH), minimumSizeHint().height()+1);
}

void Interface::setStaticQueryMode(bool staticQuery)
{
    if (staticQuery) {
        m_statusLayout->addWidget(m_closeButton);
    } else {
        m_buttonContainer->layout()->addWidget(m_closeButton);
    }

    // don't show the search and other control buttons in the case of a static query
    m_buttonContainer->setVisible(!staticQuery);
/*    m_configButton->setVisible(visible);
    m_activityButton->setVisible(visible);
    m_closeButton->setVisible(visible);
    m_searchTerm->setVisible(visible);*/
}

void Interface::closeEvent(QCloseEvent *e)
{
    if (!m_running) {
        resetInterface();
    } else {
        m_delayedRun = false;
        m_resultsView->hide();
        m_descriptionLabel->hide();
        m_previousPage->hide();
        m_nextPage->hide();
        m_dividerLine->hide();
        setMinimumSize(QSize(MIN_WIDTH, 0));
        adjustSize();
    }
    e->accept();
}

void Interface::run(ResultItem *item)
{
    if (!item || item->group() < Plasma::QueryMatch::PossibleMatch) {
        m_delayedRun = true;
        return;
    }

    kDebug() << item->name() << item->id();
    m_delayedRun = false;
    m_searchTerm->addToHistory(m_searchTerm->currentText());

    if (item->group() == Plasma::QueryMatch::InformationalMatch) {
        QString info = item->data();

        if (!info.isEmpty()) {
            m_searchTerm->setItemText(0, info);
            m_searchTerm->setCurrentIndex(0);
        }
        return;
    }

    m_running = true;
    close();
    m_resultsScene->run(item);
    m_running = false;
    resetInterface();
}

void Interface::runDefaultResultItem()
{
    if (m_queryRunning) {
        m_delayedRun = true;
    } else {
        run(m_resultsScene->defaultResultItem());
    }
}

void Interface::queryTextEdited(const QString &query)
{
    m_delayedRun = false;

    if (query.isEmpty()) {
        resetInterface();
        m_queryRunning = false;
    } else {
        m_queryRunning = m_resultsScene->launchQuery(query) || m_queryRunning; //lazy OR?
    }
}

void Interface::updateDescriptionLabel(ResultItem *item)
{
    // we want it always visible once we start showing it
    // so that the interface isn't jumping all around
    m_descriptionLabel->setVisible(m_resultsView->isVisible());
    m_dividerLine->setVisible(m_resultsView->isVisible());
    if (!item) {
        m_descriptionLabel->setText(" ");
    } else if (item->description().isEmpty()) {
        m_descriptionLabel->setText(item->name());
    } else {
        m_descriptionLabel->setText(i18n("%1: %2",item->name() ,item->description()));
    }
}

void Interface::matchCountChanged(int count)
{
    m_queryRunning = false;
    bool show = count > 0;
    m_hideResultsTimer.stop();
    bool pages = m_resultsScene->pageCount() > 1;
    m_previousPage->setVisible(pages);
    m_nextPage->setVisible(pages);

    if (show && m_delayedRun) {
        kDebug() << "delayed run with" << count << "items";
        runDefaultResultItem();
        return;
    }

    if (m_resultsView->isVisible() == show) {
        return;
    }

    if (show) {
        //kDebug() << "showing!";
        m_resultsView->show();
        setMinimumSize(QSize(MIN_WIDTH, 0));
        adjustSize();
    } else {
        //kDebug() << "hiding ... eventually";
        m_delayedRun = false;
        m_hideResultsTimer.start(2000);
    }
}

void Interface::hideResultsArea()
{
    m_resultsView->hide();
    m_descriptionLabel->hide();
    m_descriptionLabel->clear();
    m_previousPage->hide();
    m_nextPage->hide();
    m_dividerLine->hide();
    m_searchTerm->setFocus();
    setMinimumSize(QSize(MIN_WIDTH, 0));
    adjustSize();
    resize(minimumSizeHint());
}

#include "interface.moc"
