/*
 * Copyright (C) 2017 Konsulko Group
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "dbus.h"


DbusService::DbusService(QObject *parent) : QObject(parent)
{
}

/*
 * Light Media Scanner
 */

bool DbusService::enableLMS()
{
    QDBusConnection session_bus = QDBusConnection::sessionBus();
    QDBusConnection system_bus = QDBusConnection::systemBus();
    bool ret;

    if (!session_bus.isConnected())
        return false;

    if (!system_bus.isConnected())
        return false;

    ret = session_bus.connect(QString("org.lightmediascanner"), QString("/org/lightmediascanner/Scanner1"), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(lmsUpdate(QString,QVariantMap,QStringList)));
    if (!ret)
        return false;

    /* Only subscribe to DeviceRemoved events, since we need lms scan to complete on insert */
    return system_bus.connect(QString("org.freedesktop.UDisks"), QString("/org/freedesktop/UDisks"), "org.freedesktop.UDisks", "DeviceRemoved", this, SLOT(mediaRemoved(QDBusObjectPath)));
}

void DbusService::mediaRemoved(const QDBusObjectPath&)
{
        emit stopPlayback();
}

#if defined(HAVE_LIGHTMEDIASCANNER)
void DbusService::lmsUpdate(const QString&, const QVariantMap& map, const QStringList&)
{
    QVariantList mediaFiles;
    QString music;

    if (!map.contains("IsScanning") && !map.contains("WriteLocked"))
        return;

    if (map["IsScanning"].toBool() || map["WriteLocked"].toBool())
        return;

    mediaFiles = LightMediaScanner::processLightMediaScanner();

    if (!mediaFiles.isEmpty())
        emit processPlaylistUpdate(mediaFiles);
    else
        emit processPlaylistHide();
}
#else
void DbusService::lmsUpdate(const QString&, const QVariantMap&, const QStringList&)
{
}
#endif

/*
 * Bluetooth
 */

void DbusService::setBluezPath(const QString& path)
{
    this->bluezPath = path;
}

QString DbusService::getBluezPath() const
{
    return this->bluezPath;
}

bool DbusService::enableBluetooth()
{
    QDBusConnection system_bus = QDBusConnection::systemBus();
    QString interface = "org.freedesktop.DBus.ObjectManager";
    bool ret;

    if (!system_bus.isConnected())
        return false;

    if (deviceConnected(system_bus))
        initialBluetoothData(system_bus);

    ret = system_bus.connect(QString("org.bluez"), QString("/"), interface, "InterfacesAdded", this, SLOT(newBluetoothDevice(QDBusObjectPath,QVariantMap)));

    if (!ret)
        return false;

    ret = system_bus.connect(QString("org.bluez"), QString("/"), interface, "InterfacesRemoved", this, SLOT(removeBluetoothDevice(QDBusObjectPath,QStringList)));

    /*
     * Unregister InterfacesAdded on error condition
     */
    if (!ret)
        system_bus.disconnect(QString("org.bluez"), QString("/"), interface, "InterfacesAdded", this, SLOT(newBluetoothDevice(QString,QVariantMap)));

    return ret;
}

bool DbusService::checkIfPlayer(const QString& path) const
{
    QRegExp regex("^.*/player\\d$");
    if (regex.exactMatch(path))
        return true;

    return false;
}

bool DbusService::deviceConnected(const QDBusConnection& system_bus)
{
    QDBusInterface interface("org.bluez", "/", "org.freedesktop.DBus.ObjectManager", system_bus);
    QDBusMessage result = interface.call("GetManagedObjects");
    const QDBusArgument argument = result.arguments().at(0).value<QDBusArgument>();
    bool ret = false;

    if (argument.currentType() != QDBusArgument::MapType)
        return false;

    argument.beginMap();

    while (!argument.atEnd()) {
        QString key;

        argument.beginMapEntry();
        argument >> key;
        argument.endMapEntry();

        ret = checkIfPlayer(key);

        if (ret) {
            newBluetoothDevice(QDBusObjectPath(key), QVariantMap());
            break;
        }
    }

    argument.endMap();

    return ret;
}

void DbusService::initialBluetoothData(const QDBusConnection& system_bus)
{
    QDBusInterface interface("org.bluez", getBluezPath(), "org.freedesktop.DBus.Properties", system_bus);

    QDBusMessage result = interface.call("GetAll", "org.bluez.MediaPlayer1");
    const QDBusArgument argument = result.arguments().at(0).value<QDBusArgument>();
    QString status, artist, title;
    int position = 0, duration = 0;

    if (argument.currentType() != QDBusArgument::MapType)
        return;

    argument.beginMap();

    while (!argument.atEnd()) {
        QString key;
        QVariant value;

        argument.beginMapEntry();
        argument >> key >> value;

        if (key == "Position") {
            position = value.toInt();
        } else if (key == "Status") {
            status = value.toString();
        } else if (key == "Track") {
            const QDBusArgument argument1 = qvariant_cast<QDBusArgument>(value);
            QString key1;
            QVariant value1;

            argument1.beginMap();

            while (!argument1.atEnd()) {
                argument1.beginMapEntry();
                argument1 >> key1 >> value1;
                if (key1 == "Artist")
                    artist = value1.toString();
                else if (key1 == "Title")
                    title = value1.toString();
                else if (key1 == "Duration")
                    duration = value1.toInt();
                argument1.endMapEntry();
            }
            argument1.endMap();
        }
        argument.endMapEntry();
    }
    argument.endMap();

    emit processPlaylistHide();
    emit displayBluetoothMetadata(artist, title, duration);
    emit updatePlayerStatus(status);
    emit updatePosition(position);
}

void DbusService::newBluetoothDevice(const QDBusObjectPath& item, const QVariantMap&)
{
    QString path = item.path();
    if (!checkIfPlayer(path))
        return;

    if (!getBluezPath().isEmpty()) {
        qWarning() << "Another Bluetooth Player already connected";
        return;
    }

    emit processPlaylistHide();

    QDBusConnection system_bus = QDBusConnection::systemBus();
    system_bus.connect(QString("org.bluez"), path, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(processBluetoothEvent(QString,QVariantMap,QStringList)));

    setBluezPath(path);
}

void DbusService::removeBluetoothDevice(const QDBusObjectPath& item, const QStringList&)
{
    QString path = item.path();
    if (!checkIfPlayer(path))
        return;

    if (getBluezPath().isEmpty()) {
        qWarning() << "No Bluetooth Player connected";
        return;
    }

    QDBusConnection system_bus = QDBusConnection::systemBus();
    system_bus.disconnect(QString("org.bluez"), path, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(processBluetoothEvent(QString,QVariantMap,QStringList)));

    setBluezPath(QString());
    emit processPlaylistShow();
}

void DbusService::processBluetoothEvent(const QString&, const QVariantMap& map, const QStringList&)
{
    if (map.contains("Track")) {
        QVariantMap track;
        map["Track"].value<QDBusArgument>() >> track;
        emit displayBluetoothMetadata(track["Artist"].toString(), track["Title"].toString(), track["Duration"].toInt());
    }

    if (map.contains("Position"))
        emit updatePosition(map["Position"].toInt());

    if (map.contains("Status"))
        emit updatePlayerStatus(map["Status"].toString());
}

void DbusService::processQMLEvent(const QString& state)
{
    QDBusInterface interface("org.bluez", getBluezPath(), "org.bluez.MediaPlayer1", QDBusConnection::systemBus());
    interface.call(state);
}

long DbusService::getCurrentPosition()
{
    QDBusInterface interface("org.bluez", getBluezPath(), "org.bluez.MediaPlayer1", QDBusConnection::systemBus());
    return interface.property("Position").toInt();
}