/*
Gwenview: an image viewer
Copyright 2018 Friedrich W. H. Kossebau <kossebau@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 "mprismediaplayer2player.h"

#include <config-gwenview.h>

// lib
#include "gwenview_lib_debug.h"
#include <contextmanager.h>
#include <document/documentfactory.h>
#include <imagemetainfomodel.h>
#include <mimetypeutils.h>
#include <semanticinfo/semanticinfodirmodel.h>
#include <semanticinfo/sorteddirmodel.h>
#include <slideshow.h>
// KF
#include <KFileItem>
#include <KLocalizedString>
// Qt
#include <QAction>
#include <QDBusObjectPath>

namespace Gwenview
{
static const double MAX_RATE = 1.0;
static const double MIN_RATE = 1.0;

MprisMediaPlayer2Player::MprisMediaPlayer2Player(const QString &objectDBusPath,
                                                 SlideShow *slideShow,
                                                 ContextManager *contextManager,
                                                 QAction *toggleSlideShowAction,
                                                 QAction *fullScreenAction,
                                                 QAction *previousAction,
                                                 QAction *nextAction,
                                                 QObject *parent)
    : DBusAbstractAdaptor(objectDBusPath, parent)
    , mSlideShow(slideShow)
    , mContextManager(contextManager)
    , mToggleSlideShowAction(toggleSlideShowAction)
    , mFullScreenAction(fullScreenAction)
    , mPreviousAction(previousAction)
    , mNextAction(nextAction)
    , mSlideShowEnabled(mToggleSlideShowAction->isEnabled())
    , mPreviousEnabled(mPreviousAction->isEnabled())
    , mNextEnabled(mNextAction->isEnabled())
{
    updatePlaybackStatus();

    connect(mSlideShow, &SlideShow::stateChanged, this, &MprisMediaPlayer2Player::onSlideShowStateChanged);
    connect(mSlideShow, &SlideShow::intervalChanged, this, &MprisMediaPlayer2Player::onMetaInfoUpdated);
    connect(mContextManager, &ContextManager::currentUrlChanged, this, &MprisMediaPlayer2Player::onCurrentUrlChanged);
    connect(mSlideShow->randomAction(), &QAction::toggled, this, &MprisMediaPlayer2Player::onRandomActionToggled);
    connect(mToggleSlideShowAction, &QAction::changed, this, &MprisMediaPlayer2Player::onToggleSlideShowActionChanged);
    connect(mNextAction, &QAction::changed, this, &MprisMediaPlayer2Player::onNextActionChanged);
    connect(mPreviousAction, &QAction::changed, this, &MprisMediaPlayer2Player::onPreviousActionChanged);
}

MprisMediaPlayer2Player::~MprisMediaPlayer2Player() = default;

bool MprisMediaPlayer2Player::updatePlaybackStatus()
{
    const QString newStatus = mSlideShow->isRunning() ? QStringLiteral("Playing") : QStringLiteral("Stopped");

    const bool changed = (newStatus != mPlaybackStatus);
    if (changed) {
        mPlaybackStatus = newStatus;
    }

    return changed;
}

QString MprisMediaPlayer2Player::playbackStatus() const
{
    return mPlaybackStatus;
}

bool MprisMediaPlayer2Player::canGoNext() const
{
    return mNextEnabled;
}

void MprisMediaPlayer2Player::Next()
{
    mNextAction->trigger();
}

bool MprisMediaPlayer2Player::canGoPrevious() const
{
    return mPreviousEnabled;
}

void MprisMediaPlayer2Player::Previous()
{
    mPreviousAction->trigger();
}

bool MprisMediaPlayer2Player::canPause() const
{
    return mSlideShowEnabled;
}

void MprisMediaPlayer2Player::Pause()
{
    mSlideShow->pause();
}

void MprisMediaPlayer2Player::PlayPause()
{
    mToggleSlideShowAction->trigger();
}

void MprisMediaPlayer2Player::Stop()
{
    if (mFullScreenAction->isChecked()) {
        mFullScreenAction->trigger();
    }
}

bool MprisMediaPlayer2Player::canPlay() const
{
    return mSlideShowEnabled;
}

void MprisMediaPlayer2Player::Play()
{
    if (mSlideShow->isRunning()) {
        return;
    }
    mToggleSlideShowAction->trigger();
}

double MprisMediaPlayer2Player::volume() const
{
    return 0;
}

void MprisMediaPlayer2Player::setVolume(double volume)
{
    Q_UNUSED(volume);
}

void MprisMediaPlayer2Player::setShuffle(bool isShuffle)
{
    mSlideShow->randomAction()->setChecked(isShuffle);
}

QVariantMap MprisMediaPlayer2Player::metadata() const
{
    return mMetaData;
}

qlonglong MprisMediaPlayer2Player::position() const
{
    // milliseconds -> microseconds
    return mSlideShow->position() * 1000;
}

double MprisMediaPlayer2Player::rate() const
{
    return 1.0;
}

void MprisMediaPlayer2Player::setRate(double newRate)
{
    Q_UNUSED(newRate);
}

double MprisMediaPlayer2Player::minimumRate() const
{
    return MIN_RATE;
}

double MprisMediaPlayer2Player::maximumRate() const
{
    return MAX_RATE;
}

bool MprisMediaPlayer2Player::isShuffle() const
{
    return mSlideShow->randomAction()->isChecked();
}

bool MprisMediaPlayer2Player::canSeek() const
{
    return false;
}

bool MprisMediaPlayer2Player::canControl() const
{
    return true;
}

void MprisMediaPlayer2Player::Seek(qlonglong offset)
{
    Q_UNUSED(offset);
}

void MprisMediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos)
{
    Q_UNUSED(trackId);
    Q_UNUSED(pos);
}

void MprisMediaPlayer2Player::OpenUri(const QString &uri)
{
    Q_UNUSED(uri);
}

void MprisMediaPlayer2Player::onSlideShowStateChanged()
{
    if (!updatePlaybackStatus()) {
        return;
    }

    signalPropertyChange(QStringLiteral("Position"), position());
    signalPropertyChange(QStringLiteral("PlaybackStatus"), mPlaybackStatus);
}

void MprisMediaPlayer2Player::onCurrentUrlChanged(const QUrl &url)
{
    if (url.isEmpty()) {
        mCurrentDocument = Document::Ptr();
    } else {
        mCurrentDocument = DocumentFactory::instance()->load(url);
        connect(mCurrentDocument.data(), &Document::metaInfoUpdated, this, &MprisMediaPlayer2Player::onMetaInfoUpdated);
    }

    onMetaInfoUpdated();
    signalPropertyChange(QStringLiteral("Position"), position());
}

void MprisMediaPlayer2Player::onMetaInfoUpdated()
{
    QVariantMap updatedMetaData;

    if (mCurrentDocument) {
        const QUrl url = mCurrentDocument->url();
        ImageMetaInfoModel *metaInfoModel = mCurrentDocument->metaInfo();

        // We need some unique id mapping to urls. The index in the list is not reliable,
        // as images can be added/removed during a running slideshow
        // To allow some bidrectional mapping, convert the url to base64 to encode it for
        // matching the D-Bus object path spec
        const QString slideId = QString::fromLatin1(url.toString().toUtf8().toBase64(QByteArray::OmitTrailingEquals).replace('+', ""));
        const QDBusObjectPath trackId(QStringLiteral("/org/kde/gwenview/imagelist/") + slideId);
        updatedMetaData.insert(QStringLiteral("mpris:trackid"), QVariant::fromValue<QDBusObjectPath>(trackId));

        // TODO: for videos also get and report the length
        if (MimeTypeUtils::urlKind(url) != MimeTypeUtils::KIND_VIDEO) {
            // convert seconds in microseconds
            const auto duration = qlonglong(mSlideShow->interval() * 1000000);
            updatedMetaData.insert(QStringLiteral("mpris:length"), duration);
        }

        // TODO: update on metadata changes, given user can edit most of this data

        const QString name = metaInfoModel->getValueForKey(QStringLiteral("General.Name"));
        updatedMetaData.insert(QStringLiteral("xesam:title"), name);
        const QString comment = metaInfoModel->getValueForKey(QStringLiteral("General.Comment"));
        if (!comment.isEmpty()) {
            updatedMetaData.insert(QStringLiteral("xesam:comment"), comment);
        }
        updatedMetaData.insert(QStringLiteral("xesam:url"), url.toString());
        // slight bending of semantics :)
        const KFileItem folderItem(mContextManager->currentDirUrl());
        updatedMetaData.insert(QStringLiteral("xesam:album"), folderItem.text());
        // TODO: hook up with thumbnail cache and pass that as arturl
        // updatedMetaData.insert(QStringLiteral("mpris:artUrl"), url.toString());

#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
        const QModelIndex index = mContextManager->dirModel()->indexForUrl(url);
        if (index.isValid()) {
            const double rating = index.data(SemanticInfoDirModel::RatingRole).toInt() / 10.0;
            updatedMetaData.insert(QStringLiteral("xesam:userRating"), rating);
        }
#endif
        // consider export of other metadata where mapping works
    }

    if (updatedMetaData != mMetaData) {
        mMetaData = updatedMetaData;

        signalPropertyChange(QStringLiteral("Metadata"), mMetaData);
    }
}

void MprisMediaPlayer2Player::onRandomActionToggled(bool checked)
{
    signalPropertyChange(QStringLiteral("Shuffle"), checked);
}

void MprisMediaPlayer2Player::onToggleSlideShowActionChanged()
{
    const bool isEnabled = mToggleSlideShowAction->isEnabled();
    if (mSlideShowEnabled == isEnabled) {
        return;
    }

    mSlideShowEnabled = isEnabled;

    const bool playbackStatusChanged = updatePlaybackStatus();

    signalPropertyChange(QStringLiteral("CanPlay"), mSlideShowEnabled);
    signalPropertyChange(QStringLiteral("CanPause"), mSlideShowEnabled);
    if (playbackStatusChanged) {
        signalPropertyChange(QStringLiteral("Position"), position());
        signalPropertyChange(QStringLiteral("PlaybackStatus"), mPlaybackStatus);
    }
}

void MprisMediaPlayer2Player::onNextActionChanged()
{
    const bool isEnabled = mNextAction->isEnabled();
    if (mNextEnabled == isEnabled) {
        return;
    }

    mNextEnabled = isEnabled;

    signalPropertyChange(QStringLiteral("CanGoNext"), mNextEnabled);
}

void MprisMediaPlayer2Player::onPreviousActionChanged()
{
    const bool isEnabled = mPreviousAction->isEnabled();
    if (mPreviousEnabled == isEnabled) {
        return;
    }

    mPreviousEnabled = isEnabled;

    signalPropertyChange(QStringLiteral("CanGoPrevious"), mPreviousEnabled);
}

}

#include "moc_mprismediaplayer2player.cpp"
