/*
 * Qt wrapper for libindicate
 *
 * Copyright 2009 Canonical Ltd.
 *
 * Authors:
 * - Aurélien Gâteau <aurelien.gateau@canonical.com>
 *
 * License: LGPL v2.1 or LGPL v3
 */
// Self
#include "qindicatelistener.h"

// Qt
#include <QDateTime>
#include <QDebug>
#include <QPointer>
#include <QVariant>

// libindicate
#include <libindicate/listener.h>

// Local

namespace QIndicate
{

/**
 * This helper function converts a slot as created with the SLOT() macro into
 * a string which can be used with QMetaObject::invokeMethod
 */
static QByteArray methodFromSlot(const char* slot)
{
    Q_ASSERT(slot);
    if (qstrlen(slot) < 4)
    {
        // A slot is a digit + function name + parenthesis, so that's at least
        // 4 chars
        qWarning() << "Invalid slot name" << slot;
        return QByteArray();
    }
    QByteArray method(slot + 1);
    int parenthesisIdx = method.indexOf('(');
    if (parenthesisIdx == -1)
    {
        qWarning() << "Could not find the '(' in slot" << slot;
        return QByteArray();
    }
    method.resize(parenthesisIdx);
    return method;
}

static QHash<IndicateListener*, Listener*> sQListenerFromGListener;

struct ListenerPrivate
{
    IndicateListener* mGListener;
};

static void serverAddedCB(IndicateListener* _listener, void* _server, gchar* _type)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    QString type = QString::fromUtf8(_type);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    QMetaObject::invokeMethod(listener, "serverAdded", Qt::QueuedConnection, Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(QString, type));
}

static void serverRemovedCB(IndicateListener* _listener, void* _server, gchar* _type)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    QString type = QString::fromUtf8(_type);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    QMetaObject::invokeMethod(listener, "serverRemoved", Qt::QueuedConnection, Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(QString, type));
}

static void serverCountChangedCB(IndicateListener* _listener, void* _server, guint value)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    QMetaObject::invokeMethod(listener, "serverCountChanged", Qt::QueuedConnection, Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(int, value));
}

static void indicatorAddedCB(IndicateListener* _listener, void* _server, void* _indicator)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    Listener::Indicator* indicator = reinterpret_cast<Listener::Indicator*>(_indicator);
    QMetaObject::invokeMethod(listener, "indicatorAdded", Qt::QueuedConnection,
        Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(QIndicate::Listener::Indicator*, indicator));
}

static void indicatorRemovedCB(IndicateListener* _listener, void* _server, void* _indicator)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    Listener::Indicator* indicator = reinterpret_cast<Listener::Indicator*>(_indicator);
    QMetaObject::invokeMethod(listener, "indicatorRemoved", Qt::QueuedConnection,
        Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(QIndicate::Listener::Indicator*, indicator));
}

static void indicatorModifiedCB(IndicateListener* _listener, void* _server, void* _indicator, gchar* _property)
{
    Listener* listener = sQListenerFromGListener.value(_listener);
    Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
    Listener::Indicator* indicator = reinterpret_cast<Listener::Indicator*>(_indicator);
    QString property = QString::fromUtf8(_property);
    QMetaObject::invokeMethod(listener, "indicatorModified", Qt::QueuedConnection,
        Q_ARG(QIndicate::Listener::Server*, server), Q_ARG(QIndicate::Listener::Indicator*, indicator), Q_ARG(QString, property));
}

Listener::Listener(QObject* parent)
: QObject(parent)
, d(new ListenerPrivate)
{
    g_type_init();
    init(indicate_listener_new());
}

Listener::Listener(IndicateListener* gListener)
: d(new ListenerPrivate)
{
    init(gListener);
}

void Listener::init(IndicateListener* gListener)
{
    qRegisterMetaType<QIndicate::Listener::Server*>("QIndicate::Listener::Server*");
    qRegisterMetaType<QIndicate::Listener::Indicator*>("QIndicate::Listener::Indicator*");
    d->mGListener = gListener;

    #define gconnect(gsignal, callback) \
        g_signal_connect(G_OBJECT(d->mGListener), gsignal, G_CALLBACK(callback), NULL)
    gconnect(INDICATE_LISTENER_SIGNAL_SERVER_ADDED, serverAddedCB);
    gconnect(INDICATE_LISTENER_SIGNAL_SERVER_REMOVED, serverRemovedCB);
    gconnect(INDICATE_LISTENER_SIGNAL_SERVER_COUNT_CHANGED, serverCountChangedCB);
    gconnect(INDICATE_LISTENER_SIGNAL_INDICATOR_ADDED, indicatorAddedCB);
    gconnect(INDICATE_LISTENER_SIGNAL_INDICATOR_MODIFIED, indicatorModifiedCB);
    gconnect(INDICATE_LISTENER_SIGNAL_INDICATOR_REMOVED, indicatorRemovedCB);
    #undef gconnect

    sQListenerFromGListener.insert(gListener, this);
}

Listener::~Listener()
{
    sQListenerFromGListener.remove(d->mGListener);
    g_object_unref(d->mGListener);
    delete d;
}

Listener* Listener::defaultInstance()
{
    g_type_init();
    static Listener instance(indicate_listener_ref_default());
    return &instance;
}

static QVariant variantFromGVariant(GVariant* gvariant)
{
    if (!gvariant) {
        qWarning() << "variantFromGVariant(): gvariant is NULL";
        return QVariant();
    }
    if (g_variant_is_of_type(gvariant, G_VARIANT_TYPE_STRING)) {
        return QVariant(QByteArray(g_variant_get_string(gvariant, 0)));
    }
    if (g_variant_is_of_type(gvariant, G_VARIANT_TYPE_INT16)) {
        return QVariant(g_variant_get_int16(gvariant));
    }
    if (g_variant_is_of_type(gvariant, G_VARIANT_TYPE_INT32)) {
        return QVariant(g_variant_get_int32(gvariant));
    }
    if (g_variant_is_of_type(gvariant, G_VARIANT_TYPE_INT64)) {
        return QVariant(qlonglong(g_variant_get_int64(gvariant)));
    }
    if (g_variant_is_of_type(gvariant, G_VARIANT_TYPE_BOOLEAN)) {
        return QVariant(g_variant_get_boolean(gvariant));
    }
    qWarning() << "variantFromGVariant(): don't know how to handle GVariant of type " << g_variant_get_type_string(gvariant);
    return QVariant();
}

struct GetPropertyHelper {
    QPointer<QObject> mReceiver;
    QByteArray mMethod;

    GetPropertyHelper(QObject* receiver, const char* slot)
    : mReceiver(receiver)
    , mMethod(methodFromSlot(slot))
    {}

    static void indicatorGetPropertyCB(IndicateListener* listener,
        IndicateListenerServer* _server, IndicateListenerIndicator* _indicator,
        gchar* _key, const gchar* _value, gpointer data)
    {
        GetPropertyHelper* helper = static_cast<GetPropertyHelper*>(data);
        if (helper->mReceiver) {
            Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
            Listener::Indicator* indicator = reinterpret_cast<Listener::Indicator*>(_indicator);
            QString key = QString::fromUtf8(_key);
            QByteArray value(_value);

            QMetaObject::invokeMethod(helper->mReceiver, helper->mMethod.data(),
                Q_ARG(QIndicate::Listener::Server*, server),
                Q_ARG(QIndicate::Listener::Indicator*, indicator),
                Q_ARG(QString, key),
                Q_ARG(QByteArray, value));
        }
        delete helper;
    }

    static void indicatorGetPropertyAsVariantCB(IndicateListener* listener,
        IndicateListenerServer* _server, IndicateListenerIndicator* _indicator,
        gchar* _key, GVariant* gvariant, gpointer data)
    {
        GetPropertyHelper* helper = static_cast<GetPropertyHelper*>(data);
        if (helper->mReceiver) {
            Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);
            Listener::Indicator* indicator = reinterpret_cast<Listener::Indicator*>(_indicator);
            QString key = QString::fromUtf8(_key);

            QVariant variant = variantFromGVariant(gvariant);

            QMetaObject::invokeMethod(helper->mReceiver, helper->mMethod.data(),
                Q_ARG(QIndicate::Listener::Server*, server),
                Q_ARG(QIndicate::Listener::Indicator*, indicator),
                Q_ARG(QString, key),
                Q_ARG(QVariant, variant));
        }
        delete helper;
    }

    static void serverGetQByteArrayPropertyCB(IndicateListener* listener,
        IndicateListenerServer* server, const gchar* value, gpointer data)
    {
        serverGetPropertyCB(listener, server, Q_ARG(QByteArray, value), data);
    }

    static void serverGetUIntPropertyCB(IndicateListener* listener,
        IndicateListenerServer* server, guint value, gpointer data)
    {
        serverGetPropertyCB(listener, server, Q_ARG(int, value), data);
    }

    static void serverGetQStringPropertyCB(IndicateListener* listener,
        IndicateListenerServer* server, const gchar* value, gpointer data)
    {
        serverGetPropertyCB(listener, server, Q_ARG(QString, QString::fromAscii(value)), data);
    }

private:
    static void serverGetPropertyCB(IndicateListener* listener,
        IndicateListenerServer* _server, QGenericArgument arg, gpointer data)
    {
        GetPropertyHelper* helper = static_cast<GetPropertyHelper*>(data);
        if (helper) {
            Listener::Server* server = reinterpret_cast<Listener::Server*>(_server);

            QMetaObject::invokeMethod(helper->mReceiver, helper->mMethod.data(),
                Q_ARG(QIndicate::Listener::Server*, server), arg);
        }
        delete helper;
    }
};

void Listener::getIndicatorProperty(
    QIndicate::Listener::Server* server,
    QIndicate::Listener::Indicator* indicator,
    const QString& key,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_get_property(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        reinterpret_cast<IndicateListenerIndicator*>(indicator),
        key.toUtf8().data(), GetPropertyHelper::indicatorGetPropertyCB, helper);
}

void Listener::getIndicatorPropertyAsVariant(
    QIndicate::Listener::Server* server,
    QIndicate::Listener::Indicator* indicator,
    const QString& key,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_get_property_variant(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        reinterpret_cast<IndicateListenerIndicator*>(indicator),
        key.toUtf8().data(), GetPropertyHelper::indicatorGetPropertyAsVariantCB, helper);
}

void Listener::getServerDesktopFile(
    QIndicate::Listener::Server* server,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_server_get_desktop(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        GetPropertyHelper::serverGetQByteArrayPropertyCB, helper);
}

void Listener::getServerType(
    QIndicate::Listener::Server* server,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_server_get_type(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        GetPropertyHelper::serverGetQByteArrayPropertyCB, helper);
}

void Listener::getServerCount(
    QIndicate::Listener::Server* server,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_server_get_count(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        GetPropertyHelper::serverGetUIntPropertyCB, helper);
}

void Listener::getServerMenuObjectPath(
    QIndicate::Listener::Server* server,
    QObject* receiver,
    const char* slot)
{
    GetPropertyHelper* helper = new GetPropertyHelper(receiver, slot);

    indicate_listener_server_get_menu(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        GetPropertyHelper::serverGetQStringPropertyCB, helper);
}

QString Listener::getServerMenuDBusName(QIndicate::Listener::Server* server)
{
    const gchar* value = indicate_listener_server_get_dbusname(
        reinterpret_cast<IndicateListenerServer*>(server));

    return QString::fromAscii(value);
}

void Listener::display(
    QIndicate::Listener::Server* server,
    QIndicate::Listener::Indicator* indicator)
{
    uint timestamp = QDateTime::currentDateTime().toTime_t();
    indicate_listener_display(d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        reinterpret_cast<IndicateListenerIndicator*>(indicator),
        timestamp);
}

bool Listener::getInterest(QIndicate::Listener::Server* server, QIndicate::Interest interest)
{
    return indicate_listener_server_check_interest(
        d->mGListener,
        reinterpret_cast<IndicateListenerServer*>(server),
        IndicateInterests(interest));
}

void Listener::setInterest(QIndicate::Listener::Server* _server, QIndicate::Interest _interest, bool value)
{
    IndicateInterests interest = IndicateInterests(_interest);
    IndicateListenerServer* server = reinterpret_cast<IndicateListenerServer*>(_server);
    if (value) {
        indicate_listener_server_show_interest(d->mGListener, server, interest);
    } else {
        indicate_listener_server_remove_interest(d->mGListener, server, interest);
    }
}

} // namespace

#include "qindicatelistener.moc"
