/*
 *  Copyright 2014 Marco Martin <mart@kde.org>
 *
 *  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 "view.h"

#include <QApplication>
#include <QDebug>
#include <QDesktopWidget>
#include <QQuickItem>
#include <QQmlContext>
#include <QScreen>
#include <QQmlEngine>
#include <QClipboard>

#include <KWindowSystem>
#include <KWindowEffects>
#include <KAuthorized>
#include <KGlobalAccel>
#include <QAction>
#include <KLocalizedString>
#include <KDirWatch>
#include <KCrash/KCrash>

#include <kdeclarative/qmlobject.h>

#include <Plasma/Package>

#include "appadaptor.h"

#include "shellpluginloader.h"

View::View(QWindow *parent)
    : PlasmaQuick::Dialog(),
      m_offset(.5),
      m_floating(false)
{
    setClearBeforeRendering(true);
    setColor(QColor(Qt::transparent));
    setFlags(Qt::FramelessWindowHint);

    KCrash::setFlags(KCrash::AutoRestart);

    m_config = KConfigGroup(KSharedConfig::openConfig("krunnerrc"), "General");

    setFreeFloating(m_config.readEntry("FreeFloating", false));

    new AppAdaptor(this);
    QDBusConnection::sessionBus().registerObject(QLatin1String("/App"), this);

    if (KAuthorized::authorize(QLatin1String("run_command"))) {
        QAction *a = new QAction(0);
        QObject::connect(a, SIGNAL(triggered(bool)), SLOT(displayOrHide()));
        a->setText(i18n("Run Command"));
        a->setObjectName("run command");
        KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT + Qt::Key_Space), KGlobalAccel::NoAutoloading);
        KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT + Qt::Key_Space) << QKeySequence(Qt::ALT + Qt::Key_F2) << Qt::Key_Search);

        a = new QAction(0);
        QObject::connect(a, SIGNAL(triggered(bool)), SLOT(displayWithClipboardContents()));
        a->setText(i18n("Run Command on clipboard contents"));
        a->setObjectName("run command on clipboard contents");
        KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT+Qt::SHIFT+Qt::Key_F2));
        KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT+Qt::SHIFT+Qt::Key_F2));
    }

    ShellPluginLoader::init();
    Plasma::Package pkg = Plasma::PluginLoader::self()->loadPackage("Plasma/LookAndFeel");
    pkg.setPath("org.kde.lookandfeel");

    m_qmlObj = new KDeclarative::QmlObject(this);
    m_qmlObj->setInitializationDelayed(true);
    m_qmlObj->setSource(QUrl::fromLocalFile(pkg.filePath("runcommandmainscript")));
    m_qmlObj->engine()->rootContext()->setContextProperty("runnerWindow", this);
    m_qmlObj->completeInitialization();
    setMainItem(qobject_cast<QQuickItem *>(m_qmlObj->rootObject()));

    auto controlScreen = [=](QScreen* screen) {
        connect(screen, SIGNAL(geometryChanged(QRect)), SLOT(screenGeometryChanged()));
        connect(screen, SIGNAL(destroyed(QObject*)), SLOT(screenGeometryChanged()));
        screenGeometryChanged();
    };
    foreach(QScreen* s, QGuiApplication::screens())
        controlScreen(s);
    connect(qApp, &QGuiApplication::screenAdded, this, controlScreen);

    connect(KWindowSystem::self(), SIGNAL(workAreaChanged()), this, SLOT(resetScreenPos()));

    connect(this, SIGNAL(visibleChanged(bool)), this, SLOT(resetScreenPos()));

    KDirWatch::self()->addFile(m_config.name());

    // Catch both, direct changes to the config file ...
    connect(KDirWatch::self(), &KDirWatch::dirty, this, &View::reloadConfig);
    connect(KDirWatch::self(), &KDirWatch::created, this, &View::reloadConfig);

    if (m_qmlObj->rootObject()) {
        connect(m_qmlObj->rootObject(), SIGNAL(widthChanged()), this, SLOT(resetScreenPos()));
    }

    if (m_floating) {
        setLocation(Plasma::Types::Floating);
    } else {
        setLocation(Plasma::Types::TopEdge);
    }
}

View::~View()
{
}

bool View::freeFloating() const
{
    return m_floating;
}

void View::setFreeFloating(bool floating)
{
    if (m_floating == floating) {
        return;
    }

    m_floating = floating;
    if (m_floating) {
        setLocation(Plasma::Types::Floating);
    } else {
        setLocation(Plasma::Types::TopEdge);
    }

    positionOnScreen();
}

void View::reloadConfig()
{
    m_config.config()->reparseConfiguration();
    setFreeFloating(m_config.readEntry("FreeFloating", false));
}

bool View::event(QEvent *event)
{
    // QXcbWindow overwrites the state in its show event. There are plans
    // to fix this in 5.4, but till then we must explicitly overwrite it
    // each time.
    const bool retval = Dialog::event(event);
    if (event->type() != QEvent::DeferredDelete) {
        KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager | NET::KeepAbove | NET::StaysOnTop);
    }
    return retval;
}

void View::showEvent(QShowEvent *event)
{
    KWindowSystem::setOnAllDesktops(winId(), true);
    KWindowSystem::setState(winId(), NET::KeepAbove);
    Dialog::showEvent(event);
    positionOnScreen();
    requestActivate();
}

void View::screenGeometryChanged()
{
    if (isVisible()) {
        positionOnScreen();
    }
}

void View::resetScreenPos()
{
    if (isVisible() && !m_floating) {
        positionOnScreen();
    }
}

void View::positionOnScreen()
{
    QScreen* shownOnScreen = 0;
    if (QGuiApplication::screens().count() <= 1) {
        shownOnScreen = QGuiApplication::screens().first();
    } else {
        Q_FOREACH (QScreen* screen, QGuiApplication::screens()) {
            if (screen->geometry().contains(QCursor::pos()))
                shownOnScreen = screen;
        }
    }
    Q_ASSERT(shownOnScreen);

    setScreen(shownOnScreen);
    const QRect r = shownOnScreen->availableGeometry();

    if (m_floating && !m_customPos.isNull()) {
        int x = qBound(r.left(), m_customPos.x(), r.right() - width());
        int y = qBound(r.top(), m_customPos.y(), r.bottom() - height());
        setPosition(x, y);
        show();
        return;
    }

    const int w = width();
    int x = r.left() + (r.width() * m_offset) - (w / 2);

    int y = r.top();
    if (m_floating) {
        y += r.height() / 3;
    }

    x = qBound(r.left(), x, r.right() - width());
    y = qBound(r.top(), y, r.bottom() - height());

    setPosition(x, y);

    if (m_floating) {
        KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
        KWindowSystem::setType(winId(), NET::Normal);
        //Turn the sliding effect off
        KWindowEffects::slideWindow(winId(), KWindowEffects::NoEdge, 0);
    } else {
        KWindowSystem::setOnAllDesktops(winId(), true);
        KWindowEffects::slideWindow(winId(), KWindowEffects::TopEdge, 0);
    }

    KWindowSystem::forceActiveWindow(winId());
    //qDebug() << "moving to" << m_screenPos[screen];
}

void View::displayOrHide()
{
    setVisible(!isVisible());
}

void View::display()
{
    setVisible(true);
}

void View::displaySingleRunner(const QString &runnerName)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", runnerName);
    m_qmlObj->rootObject()->setProperty("query", QString());
}

void View::displayWithClipboardContents()
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", QString());
    m_qmlObj->rootObject()->setProperty("query", QGuiApplication::clipboard()->text(QClipboard::Selection));
}

void View::query(const QString &term)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", QString());
    m_qmlObj->rootObject()->setProperty("query", term);
}

void View::querySingleRunner(const QString &runnerName, const QString &term)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", runnerName);
    m_qmlObj->rootObject()->setProperty("query", term);
}

void View::switchUser()
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", "desktopsessions");
    m_qmlObj->rootObject()->setProperty("query", "SESSIONS");
}

#include "moc_view.cpp"
