/* BEGIN_COMMON_COPYRIGHT_HEADER
 * (c)LGPL2+
 *
 * LXDE-Qt - a lightweight, Qt based, desktop toolset
 * http://razor-qt.org
 *
 * Copyright: 2010-2011 Razor team
 * Authors:
 *   Alexander Sokoloff <sokoloff.a@gmail.com>
 *   Petr Vanek <petr@scribus.info>
 *
 * This program or library is free software; you can redistribute it
 * and/or modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.

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

#include "dialog.h"
#include "ui_dialog.h"
#include "widgets.h"
#include "commanditemmodel.h"
#include "configuredialog/configuredialog.h"

#include <LXQt/Settings>
#include <LXQt/HtmlDelegate>
#include <XdgIcon>
#include <LXQt/PowerManager>
#include <LXQt/ScreenSaver>
#include <LXQtGlobalKeys/Action>
#include <LXQtGlobalKeys/Client>
#include <QDebug>
#include <QCloseEvent>
#include <QDesktopWidget>
#include <QProcess>
#include <QLineEdit>
#include <QAction>
#include <QMessageBox>
#include <QApplication>
#include <QMenu>
#include <QWindow>
#include <QScrollBar>

#include <KWindowSystem/KWindowSystem>

#define DEFAULT_SHORTCUT "Alt+F2"

/************************************************

 ************************************************/
Dialog::Dialog(QWidget *parent) :
    QDialog(parent, Qt::Dialog | Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint),
    ui(new Ui::Dialog),
    mSettings(new LXQt::Settings("lxqt-runner", this)),
    mGlobalShortcut(0),
    mLockCascadeChanges(false),
    mDesktopChanged(false),
    mConfigureDialog(0)
{
    ui->setupUi(this);
    setWindowTitle("LXQt Runner");
    setAttribute(Qt::WA_TranslucentBackground);

    connect(LXQt::Settings::globalSettings(), SIGNAL(iconThemeChanged()), this, SLOT(update()));
    connect(ui->closeButton, SIGNAL(clicked()), this, SLOT(hide()));
    connect(mSettings, SIGNAL(settingsChanged()), this, SLOT(applySettings()));

    ui->commandEd->installEventFilter(this);

    connect(ui->commandEd, SIGNAL(textChanged(QString)), this, SLOT(setFilter(QString)));
    connect(ui->commandEd, SIGNAL(returnPressed()), this, SLOT(runCommand()));

    mCommandItemModel = new CommandItemModel(this);
    ui->commandList->installEventFilter(this);
    ui->commandList->setModel(mCommandItemModel);
    ui->commandList->setEditTriggers(QAbstractItemView::NoEditTriggers);
    connect(ui->commandList, SIGNAL(clicked(QModelIndex)), this, SLOT(runCommand()));
    setFilter(QString());
    dataChanged();

    ui->commandList->setItemDelegate(new LXQt::HtmlDelegate(QSize(32, 32), ui->commandList));

    // Popup menu ...............................
    QAction *a = new QAction(XdgIcon::fromTheme("configure"), tr("Configure"), this);
    connect(a, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
    addAction(a);

    a = new QAction(XdgIcon::fromTheme("edit-clear-history"), tr("Clear History"), this);
    connect(a, SIGNAL(triggered()), mCommandItemModel, SLOT(clearHistory()));
    addAction(a);

    mPowerManager = new LXQt::PowerManager(this);
    addActions(mPowerManager->availableActions());
    mScreenSaver = new LXQt::ScreenSaver(this);
    addActions(mScreenSaver->availableActions());

    setContextMenuPolicy(Qt::ActionsContextMenu);

    QMenu *menu = new QMenu(this);
    menu->addActions(actions());
    ui->actionButton->setMenu(menu);
    // End of popup menu ........................

    applySettings();

    connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), SLOT(realign()));
    connect(QApplication::desktop(), SIGNAL(workAreaResized(int)), SLOT(realign()));
    connect(mGlobalShortcut, SIGNAL(activated()), this, SLOT(showHide()));
    connect(mGlobalShortcut, SIGNAL(shortcutChanged(QString,QString)), this, SLOT(shortcutChanged(QString,QString)));
    connect(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId)), this, SLOT(onActiveWindowChanged(WId)));
    connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &Dialog::onCurrentDesktopChanged);

    resize(mSettings->value("dialog/width", 400).toInt(), size().height());

    // TEST
    connect(mCommandItemModel, SIGNAL(layoutChanged()), this, SLOT(dataChanged()));
}


/************************************************

 ************************************************/
Dialog::~Dialog()
{
    delete ui;
}


/************************************************

 ************************************************/
void Dialog::closeEvent(QCloseEvent *event)
{
    hide();
    event->accept();
}


/************************************************

 ************************************************/
QSize Dialog::sizeHint() const
{
    QSize size = QDialog::sizeHint();
    size.setWidth(this->size().width());
    return size;
}


/************************************************

 ************************************************/
void Dialog::resizeEvent(QResizeEvent *event)
{
    mSettings->setValue("dialog/width", size().width());
}


/************************************************

 ************************************************/
bool Dialog::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);

        if (object == ui->commandEd)
            return editKeyPressEvent(keyEvent);

        if (object == ui->commandList)
            return listKeyPressEvent(keyEvent);
    }

    return QDialog::eventFilter(object, event);
}


/************************************************
 eventFilter for ui->commandEd
 ************************************************/
bool Dialog::editKeyPressEvent(QKeyEvent *event)
{
    switch (event->key())
    {
    case Qt::Key_N:
        if (event->modifiers().testFlag(Qt::ControlModifier))
        {
            QKeyEvent ev(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier);
            editKeyPressEvent(&ev);
            return true;
        }
            return false;

    case Qt::Key_P:
        if (event->modifiers().testFlag(Qt::ControlModifier))
        {
            QKeyEvent ev(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
            editKeyPressEvent(&ev);
            return true;
        }
            return false;

    case Qt::Key_Up:
    case Qt::Key_PageUp:
        if (ui->commandEd->text().isEmpty() &&
            ui->commandList->isVisible() &&
            ui->commandList->currentIndex().row() == 0
           )
        {
            setFilter(QString(), false);
            return true;
        }
        qApp->sendEvent(ui->commandList, event);
        return true;

    case Qt::Key_Down:
    case Qt::Key_PageDown:
        if (ui->commandEd->text().isEmpty() &&
            ui->commandList->isHidden()
           )
        {
            setFilter(QString(), true);

            // set focus to the list so that it highlights the first item correctly,
            // and then set it back to the textfield, where it belongs
            ui->commandList->setFocus();
            ui->commandEd->setFocus();
            return true;
        }
        qApp->sendEvent(ui->commandList, event);
        return true;

    case Qt::Key_Tab:
        const CommandProviderItem *command = mCommandItemModel->command(ui->commandList->currentIndex());
        if (command)
            ui->commandEd->setText(command->title());
        return true;
    }

    return QDialog::eventFilter(ui->commandList, event);
}


/************************************************
 eventFilter for ui->commandList
 ************************************************/
bool Dialog::listKeyPressEvent(QKeyEvent *event)
{
    switch (event->key())
    {
    case Qt::Key_Up:
    case Qt::Key_PageUp:
        if (ui->commandList->currentIndex().row() == 0)
        {
            ui->commandEd->setFocus();
            return true;
        }
        break;

    case Qt::Key_Left:
    case Qt::Key_Right:
        ui->commandEd->setFocus();
        qApp->sendEvent(ui->commandEd, event);
        return true;

    default:
        // Alphabetical or number key ...........
        if (!event->text().isEmpty())
        {
            ui->commandEd->setFocus();
            qApp->sendEvent(ui->commandEd, event);
            return true;
        }
    }

    return QDialog::eventFilter(ui->commandEd, event);
}


/************************************************

 ************************************************/
void Dialog::showHide()
{
    // Using KWindowSystem to detect the active window since
    // QWidget::isActiveWindow is not working reliably.
    if (isVisible() && (KWindowSystem::activeWindow() == winId()))
    {
        hide();
    }
    else
    {
        realign();
        show();
        KWindowSystem::forceActiveWindow(winId());
        ui->commandEd->setFocus();
    }
}


/************************************************

 ************************************************/
void Dialog::realign()
{
    QRect desktop;

    int screen = mMonitor;
    if (mMonitor < 0 || mMonitor > QApplication::desktop()->screenCount() - 1)
        screen = QApplication::desktop()->screenNumber(QCursor::pos());

    desktop = QApplication::desktop()->availableGeometry(screen).intersected(KWindowSystem::workArea(screen));

    QRect rect = this->geometry();
    rect.moveCenter(desktop.center());

    if (mShowOnTop)
        rect.moveTop(desktop.top());
    else
        rect.moveTop(desktop.center().y() - ui->panel->sizeHint().height());

    setGeometry(rect);
}


/************************************************

 ************************************************/
void Dialog::applySettings()
{
    if (mLockCascadeChanges)
        return;

    // Shortcut .................................
    QString shortcut = mSettings->value("dialog/shortcut", DEFAULT_SHORTCUT).toString();
    if (shortcut.isEmpty())
        shortcut = DEFAULT_SHORTCUT;

    if (!mGlobalShortcut)
        mGlobalShortcut = GlobalKeyShortcut::Client::instance()->addAction(shortcut, "/runner/show_hide_dialog", tr("Show/hide runner dialog"), this);
    else if (mGlobalShortcut->shortcut() != shortcut)
        mGlobalShortcut->changeShortcut(shortcut);

    mShowOnTop = mSettings->value("dialog/show_on_top", true).toBool();

    mMonitor = mSettings->value("dialog/monitor", -1).toInt();

    mCommandItemModel->showHistoryFirst(mSettings->value("dialog/history_first", true).toBool());

    realign();
    mSettings->sync();
}


/************************************************

 ************************************************/
void Dialog::shortcutChanged(const QString &/*oldShortcut*/, const QString &newShortcut)
{
    if (!newShortcut.isEmpty())
    {
        mLockCascadeChanges = true;

        mSettings->setValue("dialog/shortcut", newShortcut);
        mSettings->sync();

        mLockCascadeChanges = false;
    }
}


/************************************************

 ************************************************/
void Dialog::onActiveWindowChanged(WId id)
{
    if (isVisible() && 0 != id && id != winId())
    {
        if (mDesktopChanged)
        {
            mDesktopChanged = false;
            KWindowSystem::forceActiveWindow(winId());
        } else
        {
            hide();
        }
    }
}


/************************************************

 ************************************************/
void Dialog::onCurrentDesktopChanged(int screen)
{
    if (isVisible())
    {
        KWindowSystem::setOnDesktop(winId(), screen);
        KWindowSystem::forceActiveWindow(winId());
        //Note: workaround for changing desktop while runner is shown
        // The KWindowSystem::forceActiveWindow may fail to correctly activate runner if there
        // are any other windows on the new desktop (probably because of the sequence while WM
        // changes the virtual desktop (change desktop and activate any of the windows on it))
        mDesktopChanged = true;
    }
}


/************************************************

 ************************************************/
void Dialog::setFilter(const QString &text, bool onlyHistory)
{
    if (mCommandItemModel->isOutDated())
        mCommandItemModel->rebuild();

    QString trimmedText = text.simplified();
    mCommandItemModel->setCommand(trimmedText);
    mCommandItemModel->showOnlyHistory(onlyHistory);
    mCommandItemModel->setFilterRegExp(trimmedText);
    mCommandItemModel->invalidate();

    // tidy up layout and select first item
    ui->commandList->doItemsLayout();
    ui->commandList->setCurrentIndex(mCommandItemModel->index(0, 0));
}

/************************************************

 ************************************************/
void Dialog::dataChanged()
{
    if (mCommandItemModel->rowCount())
    {
       ui->commandList->setCurrentIndex(mCommandItemModel->appropriateItem(mCommandItemModel->command()));
        ui->commandList->scrollTo(ui->commandList->currentIndex());
        ui->commandList->show();
    }
    else
    {
        ui->commandList->hide();
    }

    adjustSize();

}


/************************************************

 ************************************************/
void Dialog::runCommand()
{
    bool res =false;
    const CommandProviderItem *command = mCommandItemModel->command(ui->commandList->currentIndex());

    if (command)
        res = command->run();

    if (res)
    {
        hide();
        ui->commandEd->clear();
    }

}


/************************************************

 ************************************************/
void Dialog::showConfigDialog()
{
    if (!mConfigureDialog)
        mConfigureDialog = new ConfigureDialog(mSettings, DEFAULT_SHORTCUT, this);
    mConfigureDialog->exec();
}

#undef DEFAULT_SHORTCUT
