/*
 * SPDX-FileCopyrightText: 2021 George Florea Bănuș <georgefb899@gmail.com>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include "mediaplayer2player.h"

#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusObjectPath>
#include <QFileInfo>
#include <QImage>
#include <QThreadPool>

#include <KFileMetaData/EmbeddedImageData>

#include <filesystem>

#include "miscutils.h"
#include "mpvitem.h"
#include "mpvproperties.h"
#include "playlistsettings.h"
#include "videosettings.h"

using namespace Qt::StringLiterals;

MediaPlayer2Player::MediaPlayer2Player(MpvItem *parent)
    : QDBusAbstractAdaptor(parent)
    , m_mpv{parent}
{
    connect(this, &MediaPlayer2Player::findAudioCoverFinished, this, &MediaPlayer2Player::onFindAudioCoverFinished, Qt::QueuedConnection);

    connect(m_mpv, &MpvItem::mediaTitleChanged, this, [this]() {
        auto title = m_mpv->mediaTitle();
        if (title.isEmpty() || title.startsWith(u"watch"_s)) {
            return;
        }
        propertiesChanged(u"Metadata"_s, Metadata());
        Q_EMIT metadataChanged();
    });

    connect(m_mpv, &MpvItem::durationChanged, this, [this]() {
        m_metadata.insert(u"mpris:length"_s, static_cast<qint64>(m_mpv->duration() * 1000 * 1000));
        propertiesChanged(u"Metadata"_s, m_metadata);
    });

    connect(m_mpv, &MpvItem::pauseChanged, this, [this]() {
        propertiesChanged(u"PlaybackStatus"_s, PlaybackStatus());
        Q_EMIT playbackStatusChanged();
    });

    connect(m_mpv, &MpvItem::volumeChanged, this, [this]() {
        propertiesChanged(u"Volume"_s, Volume());
        Q_EMIT volumeChanged();
    });

    connect(this, &MediaPlayer2Player::playpause, this, [this]() {
        m_mpv->setPause(!m_mpv->pause());
    });
    connect(this, &MediaPlayer2Player::play, this, [this]() {
        m_mpv->setPause(false);
    });
    connect(this, &MediaPlayer2Player::pause, this, [this]() {
        m_mpv->setPause(true);
    });
    connect(this, &MediaPlayer2Player::stop, this, [this]() {
        m_mpv->setPosition(0);
        m_mpv->setPause(true);
        m_mpv->stop();
    });
    connect(this, &MediaPlayer2Player::next, m_mpv, &MpvItem::playNext);
    connect(this, &MediaPlayer2Player::previous, m_mpv, &MpvItem::playPrevious);
    connect(this, &MediaPlayer2Player::seek, this, [this](int offset) {
        m_mpv->command(QStringList() << u"add"_s << u"time-pos"_s << QString::number(offset));
    });
    connect(this, &MediaPlayer2Player::openUri, m_mpv, &MpvItem::openUri);

    connect(PlaylistSettings::self(), &PlaylistSettings::PlaybackBehaviorChanged, this, [this]() {
        propertiesChanged(u"LoopStatus"_s, LoopStatus());
    });
    connect(PlaylistSettings::self(), &PlaylistSettings::RandomPlaybackChanged, this, [this]() {
        propertiesChanged(u"Shuffle"_s, Shuffle());
    });
}

void MediaPlayer2Player::propertiesChanged(const QString &property, const QVariant &value)
{
    QVariantMap properties;
    properties[property] = value;

    QDBusMessage msg = QDBusMessage::createSignal(u"/org/mpris/MediaPlayer2"_s, u"org.freedesktop.DBus.Properties"_s, u"PropertiesChanged"_s);
    msg << u"org.mpris.MediaPlayer2.Player"_s << properties << QStringList();

    QDBusConnection::sessionBus().send(msg);
}

QVariantMap MediaPlayer2Player::Metadata()
{
    auto url = m_mpv->currentUrl();
    QString mimeType = MiscUtils::mimeType(url);

    m_metadata.insert(u"xesam:title"_s, m_mpv->mediaTitle());
    m_metadata.insert(u"mpris:trackid"_s, QVariant::fromValue<QDBusObjectPath>(QDBusObjectPath(u"/org/kde/haruna"_s)));
    if (mimeType.startsWith(u"audio"_s)) {
        m_metadata.insert(u"mpris:artUrl"_s, getThumbnail(url.toLocalFile()));
    } else {
        m_metadata.insert(u"mpris:artUrl"_s, QString{});
    }
    m_metadata.insert(u"xesam:url"_s, url.toString());

    return m_metadata;
}

QString MediaPlayer2Player::getThumbnail(const QString &path)
{
    auto url = QUrl::fromUserInput(path);
    if (!url.isLocalFile()) {
        return {};
    }

    const auto metadata = MiscUtils::metadata(url);
    if (metadata.has_value()) {
        QString base64 = u"data:image/png;base64,"_s;
        auto imageData = metadata.value().imageData;
        QByteArray data;
        if (!imageData.isEmpty()) {
            // look for image inside the file
            using namespace KFileMetaData;
            if (imageData.contains(EmbeddedImageData::MovieScreenCapture)) {
                data = imageData.value(EmbeddedImageData::MovieScreenCapture);
            } else if (imageData.contains(EmbeddedImageData::FrontCover)) {
                data = imageData.value(EmbeddedImageData::FrontCover);
            } else if (imageData.contains(EmbeddedImageData::BackCover)) {
                data = imageData.value(EmbeddedImageData::BackCover);
            } else if (imageData.contains(EmbeddedImageData::Other)) {
                data = imageData.value(EmbeddedImageData::Other);
            } else {
                data = imageData.value(EmbeddedImageData::Unknown);
            }
            return base64.append(QString::fromUtf8(data.toBase64()));
        }

        QPointer self(this);
        QThreadPool::globalInstance()->start([self, path]() {
            QFileInfo fileInfo(path);
            if (fileInfo.isDir()) {
                return;
            }

            QString image = self->findAudioCover(fileInfo.absolutePath());
            if (!self) {
                return;
            }
            QMetaObject::invokeMethod(self, &MediaPlayer2Player::findAudioCoverFinished, image);
        });
    }
    return {};
}

QString MediaPlayer2Player::findAudioCover(const QString &filePath)
{
    static const QStringList coverNames = {u"cover"_s, u"front"_s, u"folder"_s};
    static const QStringList extensions = {u"png"_s, u"jpeg"_s, u"jpg"_s, u"webp"_s};

    for (const auto &name : coverNames) {
        for (const auto &ext : extensions) {
            const auto path = std::filesystem::path(filePath.toStdString());
            const auto filePath = path / QString(name % u"."_s % ext).toStdString();

            if (QFileInfo::exists(QString::fromStdString(filePath))) {
                return QString::fromStdString(filePath);
            }
        }
    }

    return {};
}

void MediaPlayer2Player::onFindAudioCoverFinished(const QString &image)
{
    QImage img(image);
    if (img.isNull()) {
        m_metadata.insert(u"mpris:artUrl"_s, VideoSettings::defaultCover());
        propertiesChanged(u"Metadata"_s, m_metadata);
        return;
    }

    m_metadata.insert(u"mpris:artUrl"_s, {u"file://"_s % image});
    propertiesChanged(u"Metadata"_s, m_metadata);
}

void MediaPlayer2Player::Next()
{
    Q_EMIT next();
}

void MediaPlayer2Player::Previous()
{
    Q_EMIT previous();
}

void MediaPlayer2Player::Pause()
{
    Q_EMIT pause();
}

void MediaPlayer2Player::PlayPause()
{
    Q_EMIT playpause();
}

void MediaPlayer2Player::Stop()
{
    Q_EMIT stop();
}

void MediaPlayer2Player::Play()
{
    Q_EMIT play();
}

void MediaPlayer2Player::Seek(qlonglong offset)
{
    Q_EMIT seek(offset / 1000 / 1000);
}

void MediaPlayer2Player::SetPosition(const QDBusObjectPath &trackId, qlonglong pos)
{
    Q_UNUSED(trackId)
    Q_EMIT m_mpv->setProperty(MpvProperties::self()->Position, pos / 1000 / 1000);
}

void MediaPlayer2Player::OpenUri(const QString &uri)
{
    Q_EMIT openUri(uri);
}

QString MediaPlayer2Player::PlaybackStatus()
{
    bool isPaused = m_mpv->pause();
    double position = m_mpv->position();

    return isPaused && position == 0 ? u"Stopped"_s : (isPaused ? u"Paused"_s : u"Playing"_s);
}

double MediaPlayer2Player::Volume()
{
    return m_mpv->getProperty(MpvProperties::self()->Volume).toDouble() / 100;
}

void MediaPlayer2Player::setVolume(double vol)
{
    Q_EMIT m_mpv->setProperty(MpvProperties::self()->Volume, vol * 100);
}

void MediaPlayer2Player::setShuffle(bool shuffle)
{
    PlaylistSettings::setRandomPlayback(shuffle);
    PlaylistSettings::self()->save();
    Q_EMIT PlaylistSettings::self()->RandomPlaybackChanged();
}

void MediaPlayer2Player::setLoopStatus(QString loop)
{
    if (loop == u"None"_s) {
        loop = u"StopAfterLast"_s;
    } else if (loop == u"Track"_s) {
        loop = u"RepeatItem"_s;
    } else if (loop == u"Playlist"_s) {
        loop = u"RepeatPlaylist"_s;
    }

    PlaylistSettings::setPlaybackBehavior(loop);
    PlaylistSettings::self()->save();
    Q_EMIT PlaylistSettings::self()->PlaybackBehaviorChanged();
}

qlonglong MediaPlayer2Player::Position()
{
    return m_mpv->position() * 1000 * 1000;
}

void MediaPlayer2Player::setPosition(int pos)
{
    Q_EMIT m_mpv->setProperty(MpvProperties::self()->Position, pos);
}

bool MediaPlayer2Player::CanGoNext()
{
    return true;
}

bool MediaPlayer2Player::CanGoPrevious()
{
    return true;
}

bool MediaPlayer2Player::CanPlay()
{
    return true;
}

bool MediaPlayer2Player::CanPause()
{
    return true;
}

bool MediaPlayer2Player::CanSeek()
{
    return true;
}

bool MediaPlayer2Player::CanControl()
{
    return true;
}

bool MediaPlayer2Player::Shuffle()
{
    return PlaylistSettings::randomPlayback();
}

QString MediaPlayer2Player::LoopStatus()
{
    if (PlaylistSettings::playbackBehavior() == u"RepeatItem"_s) {
        return u"Track"_s;
    }
    if (PlaylistSettings::playbackBehavior() == u"RepeatPlaylist"_s) {
        return u"Playlist"_s;
    }
    // StopAfterLast or StopAfterItem
    return u"None"_s;
}

#include "moc_mediaplayer2player.cpp"
