// -*- c++ -*-

/*!
 *
 * Copyright (C) 2015 Jolla Ltd.
 *
 * Contact: Valerio Valerio <valerio.valerio@jolla.com>
 * Author: Andres Gomez <andres.gomez@jolla.com>
 *
 * This 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
 */


#include "mprisplayer.h"

#include "mprisplayer_p.h"

#include <qqmlinfo.h>

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingCallWatcher>
#include <QDBusReply>

static const QString serviceNamePrefix = QStringLiteral("org.mpris.MediaPlayer2.");
static const QString mprisObjectPath = QStringLiteral("/org/mpris/MediaPlayer2");
static const QString dBusPropertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties");
static const QString dBusPropertiesChangedSignal = QStringLiteral("PropertiesChanged");

static inline QDBusConnection getDBusConnection()
{
#ifdef USE_SYSTEM_DBUS
    return QDBusConnection::systemBus();
#else
    return QDBusConnection::sessionBus();
#endif
}


MprisPlayer::MprisPlayer(QObject *parent)
    : QObject(parent)
    , QDBusContext()
    , m_mprisRootAdaptor(new MprisRootAdaptor(this))
    , m_mprisPlayerAdaptor(new MprisPlayerAdaptor(this))
    , m_canQuit(false)
    , m_canRaise(false)
    , m_canSetFullscreen(false)
    , m_fullscreen(false)
    , m_hasTrackList(false)
    , m_canControl(false)
    , m_canGoNext(false)
    , m_canGoPrevious(false)
    , m_canPause(false)
    , m_canPlay(false)
    , m_canSeek(false)
    , m_loopStatus(Mpris::None)
    , m_maximumRate(1)
    , m_minimumRate(1)
    , m_playbackStatus(Mpris::Stopped)
    , m_position(0)
    , m_rate(1)
    , m_shuffle(false)
    , m_volume(0)
{
    QDBusConnection connection = getDBusConnection();

    if (!connection.isConnected()) {
        qmlInfo(this) << "Failed attempting to connect to DBus";
    } else if (!connection.registerObject(mprisObjectPath, this)) {
        qmlInfo(this) << "Failed attempting to register object path. Already registered?";
    }
}

MprisPlayer::~MprisPlayer()
{
    unregisterService();
}

QString MprisPlayer::serviceName() const
{
    return m_serviceName;
}

void MprisPlayer::setServiceName(const QString &serviceName)
{
    if (m_serviceName == serviceName) {
        return;
    }

    unregisterService();
    m_serviceName = serviceName;
    registerService();

    Q_EMIT serviceNameChanged();
}


// Mpris2 Root Interface
bool MprisPlayer::canQuit() const
{
    return m_canQuit;
}

void MprisPlayer::setCanQuit(bool canQuit)
{
    if (m_canQuit == canQuit) {
        return;
    }

    m_canQuit = canQuit;
    Q_EMIT canQuitChanged();
}

bool MprisPlayer::canRaise() const
{
    return m_canRaise;
}

void MprisPlayer::setCanRaise(bool canRaise)
{
    if (m_canRaise == canRaise) {
        return;
    }

    m_canRaise = canRaise;
    Q_EMIT canRaiseChanged();
}

bool MprisPlayer::canSetFullscreen() const
{
    return m_canSetFullscreen;
}

void MprisPlayer::setCanSetFullscreen(bool canSetFullscreen)
{
    if (m_canSetFullscreen == canSetFullscreen) {
        return;
    }

    m_canSetFullscreen = canSetFullscreen;
    Q_EMIT canSetFullscreenChanged();
}

QString MprisPlayer::desktopEntry() const
{
    return m_desktopEntry;
}

void MprisPlayer::setDesktopEntry(const QString &desktopEntry)
{
    if (m_desktopEntry == desktopEntry) {
        return;
    }

    m_desktopEntry = desktopEntry;
    Q_EMIT desktopEntryChanged();
}

bool MprisPlayer::fullscreen() const
{
    return m_fullscreen;
}

void MprisPlayer::setFullscreen(bool fullscreen)
{
    if (m_fullscreen == fullscreen) {
        return;
    }

    m_fullscreen = fullscreen;
    Q_EMIT fullscreenChanged();
}

bool MprisPlayer::hasTrackList() const
{
    return m_hasTrackList;
}

void MprisPlayer::setHasTrackList(bool hasTrackList)
{
    if (m_hasTrackList == hasTrackList) {
        return;
    }

    m_hasTrackList = hasTrackList;
    Q_EMIT hasTrackListChanged();
}

QString MprisPlayer::identity() const
{
    return m_identity;
}

void MprisPlayer::setIdentity(const QString &identity)
{
    if (m_identity == identity) {
        return;
    }

    m_identity = identity;
    Q_EMIT identityChanged();
}

QStringList MprisPlayer::supportedUriSchemes() const
{
    return m_supportedUriSchemes;
}

void MprisPlayer::setSupportedUriSchemes(const QStringList &supportedUriSchemes)
{
    if (m_supportedUriSchemes == supportedUriSchemes) {
        return;
    }

    m_supportedUriSchemes = supportedUriSchemes;
    Q_EMIT supportedUriSchemesChanged();
}

QStringList MprisPlayer::supportedMimeTypes() const
{
    return m_supportedMimeTypes;
}

void MprisPlayer::setSupportedMimeTypes(const QStringList &supportedMimeTypes)
{
    if (m_supportedMimeTypes == supportedMimeTypes) {
        return;
    }

    m_supportedMimeTypes = supportedMimeTypes;
    Q_EMIT supportedMimeTypesChanged();
}

// Mpris2 Player Interface
bool MprisPlayer::canControl() const
{
    return m_canControl;
}

void MprisPlayer::setCanControl(bool canControl)
{
    if (m_canControl == canControl) {
        return;
    }

    m_canControl = canControl;
    Q_EMIT canControlChanged();
}

bool MprisPlayer::canGoNext() const
{
    return m_canGoNext;
}

void MprisPlayer::setCanGoNext(bool canGoNext)
{
    if (m_canGoNext == canGoNext) {
        return;
    }

    m_canGoNext = canGoNext;
    Q_EMIT canGoNextChanged();
}

bool MprisPlayer::canGoPrevious() const
{
    return m_canGoPrevious;
}

void MprisPlayer::setCanGoPrevious(bool canGoPrevious)
{
    if (m_canGoPrevious == canGoPrevious) {
        return;
    }

    m_canGoPrevious = canGoPrevious;
    Q_EMIT canGoPreviousChanged();
}

bool MprisPlayer::canPause() const
{
    return m_canPause;
}

void MprisPlayer::setCanPause(bool canPause)
{
    if (m_canPause == canPause) {
        return;
    }

    m_canPause = canPause;
    Q_EMIT canPauseChanged();
}

bool MprisPlayer::canPlay() const
{
    return m_canPlay;
}

void MprisPlayer::setCanPlay(bool canPlay)
{
    if (m_canPlay == canPlay) {
        return;
    }

    m_canPlay = canPlay;
    Q_EMIT canPlayChanged();
}

bool MprisPlayer::canSeek() const
{
    return m_canSeek;
}

void MprisPlayer::setCanSeek(bool canSeek)
{
    if (m_canSeek == canSeek) {
        return;
    }

    m_canSeek = canSeek;
    Q_EMIT canSeekChanged();
}

Mpris::LoopStatus MprisPlayer::loopStatus() const
{
    return m_loopStatus;
}

void MprisPlayer::setLoopStatus(Mpris::LoopStatus loopStatus)
{
    if (m_loopStatus == loopStatus) {
        return;
    }

    m_loopStatus = loopStatus;
    Q_EMIT loopStatusChanged();
}

double MprisPlayer::maximumRate() const
{
    return m_maximumRate;
}

void MprisPlayer::setMaximumRate(double maximumRate)
{
    if (m_maximumRate == maximumRate) {
        return;
    }

    m_maximumRate = maximumRate;
    Q_EMIT maximumRateChanged();
}

QVariantMap MprisPlayer::metadata() const
{
    return m_typedMetadata;
}

void MprisPlayer::setMetadata(const QVariantMap &metadata)
{
    if (m_metadata == metadata) {
        return;
    }

    m_metadata = metadata;
    m_typedMetadata = typeMetadata(metadata);
    Q_EMIT metadataChanged();
}

double MprisPlayer::minimumRate() const
{
    return m_minimumRate;
}

void MprisPlayer::setMinimumRate(double minimumRate)
{
    if (m_minimumRate == minimumRate) {
        return;
    }

    m_minimumRate = minimumRate;
    Q_EMIT minimumRateChanged();
}

Mpris::PlaybackStatus MprisPlayer::playbackStatus() const
{
    return m_playbackStatus;
}

void MprisPlayer::setPlaybackStatus(Mpris::PlaybackStatus playbackStatus)
{
    if (m_playbackStatus == playbackStatus) {
        return;
    }

    m_playbackStatus = playbackStatus;
    Q_EMIT playbackStatusChanged();
}

qlonglong MprisPlayer::position() const
{
    return m_position;
}

void MprisPlayer::setPosition(qlonglong position)
{
    if (m_position == position) {
        return;
    }

    m_position = position;
    Q_EMIT positionChanged();
}

double MprisPlayer::rate() const
{
    return m_rate;
}

void MprisPlayer::setRate(double rate)
{
    if (m_rate == rate) {
        return;
    }

    m_rate = rate;
    Q_EMIT rateChanged();
}

bool MprisPlayer::shuffle() const
{
    return m_shuffle;
}

void MprisPlayer::setShuffle(bool shuffle)
{
    if (m_shuffle == shuffle) {
        return;
    }

    m_shuffle= shuffle;
    Q_EMIT shuffleChanged();
}

double MprisPlayer::volume() const
{
    return m_volume;
}

void MprisPlayer::setVolume(double volume)
{
    if (m_volume == volume) {
        return;
    }

    m_volume = volume;
    Q_EMIT volumeChanged();
}


// Private

QVariantMap MprisPlayer::typeMetadata(const QVariantMap &aMetadata)
{
    QVariantMap metadata;
    QVariantMap::const_iterator i = aMetadata.constBegin();
    while (i != aMetadata.constEnd()) {
        switch (Mpris::enumerationFromString<Mpris::Metadata>(i.key())) {
        case Mpris::TrackId:
            metadata.insert(i.key(), QVariant::fromValue(QDBusObjectPath(i.value().toString())));
            break;
        case Mpris::Length:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toLongLong()));
            break;
        case Mpris::ArtUrl:
        case Mpris::Url:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toUrl().toString()));
            break;
        case Mpris::Album:
        case Mpris::AsText:
        case Mpris::Title:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toString()));
            break;
        case Mpris::AlbumArtist:
        case Mpris::Artist:
        case Mpris::Comment:
        case Mpris::Composer:
        case Mpris::Genre:
        case Mpris::Lyricist:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toStringList()));
            break;
        case Mpris::AudioBPM:
        case Mpris::DiscNumber:
        case Mpris::TrackNumber:
        case Mpris::UseCount:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toInt()));
            break;
        case Mpris::AutoRating:
        case Mpris::UserRating:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toFloat()));
            break;
        case Mpris::ContentCreated:
        case Mpris::FirstUsed:
        case Mpris::LastUsed:
            metadata.insert(i.key(), QVariant::fromValue(i.value().toDate().toString(Qt::ISODate)));
            break;
        case Mpris::InvalidMetadata:
            // Passing with the original type and hoping the user used
            // a type supported by DBus
            metadata.insert(i.key(), i.value());
            break;
        default:
            // Nothing to do
            break;
        }

        ++i;
    }

    return metadata;
}

void MprisPlayer::registerService()
{
    if (m_serviceName.isEmpty()) {
        qmlInfo(this) << "Failed to register service: empty service name";
        return;
    }

    QDBusConnection connection = getDBusConnection();

    if (!connection.isConnected()) {
        qmlInfo(this) << "Failed attempting to connect to DBus";
        return;
    }

    if (!connection.registerService(QString(serviceNamePrefix).append(m_serviceName))) {
        qmlInfo(this) << "Failed attempting to register service: " << m_serviceName << " Already taken?";
    }

    return;
}

void MprisPlayer::unregisterService()
{
    if (!m_serviceName.isEmpty()) {
        QDBusConnection connection = getDBusConnection();
        connection.unregisterService(QString(serviceNamePrefix).append(m_serviceName));
    }
}

void MprisPlayer::notifyPropertiesChanged(const QString& interfaceName, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) const
{
    if (m_serviceName.isEmpty()) {
        return;
    }

    QDBusConnection connection = getDBusConnection();

    if (!connection.isConnected()) {
        qmlInfo(this) << "Failed attempting to connect to DBus";
        return;
    }

    QDBusMessage message = QDBusMessage::createSignal(mprisObjectPath,
                                                      dBusPropertiesInterface,
                                                      dBusPropertiesChangedSignal);

    QList<QVariant> arguments;
    arguments << QVariant(interfaceName) << QVariant(changedProperties) << QVariant(invalidatedProperties);
    message.setArguments(arguments);

    if (!connection.send(message)) {
        qmlInfo(this) << "Failed to send DBus property notification signal";
    }
}
