/*
 * Icon Task Manager
 *
 * Copyright 2011 Craig Drummond <craig@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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "mediabuttons.h"
#include "dbusstatus.h"
#include "playerv1interface.h"
#include "playerv2interface.h"
#include <KConfig>
#include <KConfigGroup>
#include <KGlobal>
#include <KStandardDirs>
#include <KSycoca>
#include <KService>
#include <KServiceTypeTrader>
#include <QtDBus/QDBusServiceWatcher>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QDebug>

K_GLOBAL_STATIC(MediaButtons, mediaBtns)

static const QString constV2Prefix = QLatin1String("org.mpris.MediaPlayer2.");
static const QString constV1Prefix = QLatin1String("org.mpris.");

static QString playbackStatus(OrgFreedesktopMediaPlayerInterface *iface)
{
    DBusStatus status = iface->GetStatus();

    switch (status.play) {
    case DBusStatus::Mpris_Playing: return "Playing";
    case DBusStatus::Mpris_Paused:  return "Paused";
    case DBusStatus::Mpris_Stopped: return "Stopped";
    }
    return QString();
}

MediaButtons::Interface::~Interface()
{
    if (v1) {
        delete v1;
    }
    if (v2) {
        delete v2;
    }
}

void MediaButtons::Interface::next()
{
    if (v2) {
        v2->Next();
    } else if (v1) {
        v1->Next();
    }
}

void MediaButtons::Interface::previous()
{
    if (v2) {
        v2->Previous();
    } else if (v1) {
        v1->Prev();
    }
}

void MediaButtons::Interface::playPause()
{
    if (v2) {
        v2->PlayPause();
    } else if (v1) {
        if ("Playing" ==::playbackStatus(v1)) {
            v1->Pause();
        } else {
            v1->Play();
        }
    }
}

QString MediaButtons::Interface::playbackStatus()
{
    if (v2) {
        return v2->playbackStatus();
    } else if (v1) {
        return ::playbackStatus(v1);
    }

    return QString();
}

QString MediaButtons::Interface::service()
{
    if (v2) {
        return v2->service();
    } else if (v1) {
        return v1->service();
    }

    return QString();
}

MediaButtons * MediaButtons::self()
{
    return mediaBtns;
}

MediaButtons::MediaButtons()
    : m_watcher(0)
    , m_enabled(false)
{
    qDBusRegisterMetaType<DBusStatus>();
}

void MediaButtons::setEnabled(bool en)
{
    if (en != m_enabled) {
        m_enabled = en;
        if (m_enabled) {
            m_watcher = new QDBusServiceWatcher(this);
            m_watcher->setConnection(QDBusConnection::sessionBus());
            m_watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
            connect(m_watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), this, SLOT(serviceOwnerChanged(QString, QString, QString)));
            readConfig();
        } else if (m_watcher) {
            disconnect(m_watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), this, SLOT(serviceOwnerChanged(QString, QString, QString)));

            foreach (Interface * iface, m_interfaces.values()) {
                delete iface;
            }
            m_interfaces.clear();

            delete m_watcher;
        }
    }
}

void MediaButtons::serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner)
{
    bool isV2 = name.startsWith(constV2Prefix);
    QString n = QString(name).remove(isV2 ? constV2Prefix : constV1Prefix).toLower();
    QMap<QString, Interface *>::iterator it(m_interfaces.find(n)),
         end(m_interfaces.end());

    if (newOwner.isEmpty()) {
        if (it != end) {
            if ((*it)->isV2() == isV2) {
                delete(*it);
                m_interfaces.erase(it);
            }
        }
    } else if (oldOwner.isEmpty()) {
        if (isV2) {
            OrgMprisMediaPlayer2PlayerInterface *iface = new OrgMprisMediaPlayer2PlayerInterface(name, "/org/mpris/MediaPlayer2", QDBusConnection::sessionBus(), this);
            if (iface->canControl()) {
                if (it != end) {
                    delete(*it);
                    m_interfaces.erase(it);
                }
                m_interfaces.insert(n, new Interface(iface));
            } else {
                delete iface;
            }
        } else if (it == end || !(*it)->isV2()) {
            OrgFreedesktopMediaPlayerInterface *iface = new OrgFreedesktopMediaPlayerInterface(name, "/Player", QDBusConnection::sessionBus(), this);
            if (it != end) {
                delete(*it);
                m_interfaces.erase(it);
            }
            m_interfaces.insert(n, new Interface(iface));
        }
    }
}

void MediaButtons::next(const QString &name, int pid)
{
    if (m_enabled) {
        Interface *iface = getInterface(name, pid);
        if (iface) {
            iface->next();
        }
    }
}

void MediaButtons::previous(const QString &name, int pid)
{
    if (m_enabled) {
        Interface *iface = getInterface(name, pid);
        if (iface) {
            iface->previous();
        }
    }
}

void MediaButtons::playPause(const QString &name, int pid)
{
    if (m_enabled) {
        Interface *iface = getInterface(name, pid);
        if (iface) {
            iface->playPause();
        }
    }
}

QString MediaButtons::playbackStatus(const QString &name, int pid)
{
    if (m_enabled) {
        Interface *iface = getInterface(name, pid);
        if (iface) {
            return iface->playbackStatus();
        }
    }

    return QString();
}

void MediaButtons::readConfig()
{
    m_aliases.clear();
    m_ignore.clear();

    QStringList files(KGlobal::dirs()->findAllResources("data", "kdeplasma-addons/mediabuttonsrc"));

    foreach (const QString &file, files) {
        KConfig cfg(file);
        KConfigGroup ag(&cfg, "Aliases");
        KConfigGroup gen(&cfg, "General");

        m_ignore += gen.readEntry("Ignore", QStringList()).toSet();
        foreach (const QString &key, ag.keyList()) {
            foreach (const QString &alias, ag.readEntry(key, QStringList())) {
                m_aliases[alias.toLower()] = key.toLower();
            }
        }
    }
}

bool MediaButtons::isMediaApp(const QString &desktopEntry)
{
    if (!m_enabled) {
        return false;
    }

    QString name = desktopEntry;
    if (m_aliases.contains(name)) {
        name = m_aliases[name];
    }

    if (m_ignore.contains(name)) {
        return false;
    }

    MediaButtons::Interface *i = getV2Interface(name);
    // qDebug() << Q_FUNC_INFO << desktopEntry << i;
    if (i) {
        return true;
    }

    i = getV1Interface(name);
    // qDebug() << Q_FUNC_INFO << desktopEntry << i;
    if (i) {
        return i;
    }

    return false;
}

MediaButtons::Interface * MediaButtons::getInterface(const QString &name, int pid)
{
    QStringList names;

    if (m_aliases.contains(name)) {
        QString alias = m_aliases[name];
        names << alias << alias + "." + QString::number(pid) << alias + "-" + QString::number(pid);
    }
    names << name << name + "." + QString::number(pid) << name + "-" + QString::number(pid);

    foreach (const QString &n, names) {
        if (m_interfaces.contains(n)) {
            return m_interfaces[n];
        }
    }

    foreach (const QString &n, names) {
        MediaButtons::Interface *i = getV2Interface(n);
        if (i) {
            return i;
        }
    }

    foreach (const QString &n, names) {
        MediaButtons::Interface *i = getV1Interface(n);
        if (i) {
            return i;
        }
    }

    return 0;
}

MediaButtons::Interface * MediaButtons::getV2Interface(const QString &name)
{
    QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(constV2Prefix + name);

    if (reply.isValid() && reply.value()) {
        serviceOwnerChanged(constV2Prefix + name, QString(), QLatin1String("X"));
        if (m_interfaces.contains(name)) {
            m_watcher->addWatchedService(constV2Prefix + name);
            return m_interfaces[name];
        }
    }

    QDBusReply<QStringList> registeredReply = QDBusConnection::sessionBus().interface()->registeredServiceNames();
    if (!registeredReply.isValid()) {
        return nullptr;
    }
    foreach (const QString &value, registeredReply.value()) {
        if (value.startsWith(constV2Prefix + name + QLatin1Char('.'))) {
            serviceOwnerChanged(value, QString(), QLatin1String("X"));
            // qDebug() << Q_FUNC_INFO << value << m_interfaces;
            QString unsuffixedName = value.mid(constV2Prefix.size(), value.size() - constV2Prefix.size());
            if (m_interfaces.contains(unsuffixedName)) {
                m_watcher->addWatchedService(value);
                return m_interfaces[unsuffixedName];
            }
        }
    }

    return nullptr;
}

MediaButtons::Interface * MediaButtons::getV1Interface(const QString &name)
{
    QDBusReply<bool> reply = QDBusConnection::sessionBus().interface()->isServiceRegistered(constV1Prefix + name);

    if (reply.isValid() && reply.value()) {
        serviceOwnerChanged(constV1Prefix + name, QString(), QLatin1String("X"));
        if (m_interfaces.contains(name)) {
            m_watcher->addWatchedService(constV1Prefix + name);
            return m_interfaces[name];
        }
    }

    return 0;
}

#include "moc_mediabuttons.cpp"
