/*
 * QtTapioca, the Tapioca Qt4 Client Library
 * Copyright (C) 2006 by INdT
 *  @author Andre Moreira Magalhaes <andre.magalhaes@indt.org>
 *  @author Abner Jose de Faria Silva <abner.silva@indt.org.br>
 *
 * 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 "config.h"

#include "QtTapioca/ConnectionManager"
#include "QtTapioca/Connection"

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QHash>
#include <QtCore/QMutex>
#include <QtCore/QRegExp>
#include <QtCore/QSettings>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusMetaType>
#include <QtTelepathy/Client/ConnectionManager>

#define CONNECTION_MANAGER_BUS_NAME_PREFIX      "org.freedesktop.Telepathy.ConnectionManager."
#define CONNECTION_MANAGER_OBJECT_PATH_PREFIX   "/org/freedesktop/Telepathy/ConnectionManager/"

namespace QtTapioca {

class ConnectionManagerPrivate {
public:
    ConnectionManagerPrivate(const QString &cf, const QString &n)
        : configFile(cf),
          name(n),
          bus(QDBusConnection::sessionBus()),
          cm(0)
    {
    }
    ~ConnectionManagerPrivate()
    {
        if (cm) {
            delete cm;
        }
    }

    QString configFile;
    QString name;
    QDBusConnection bus;
    org::freedesktop::Telepathy::ConnectionManager *cm;
    QHash<QString, Connection*> connections;
    QStringList supportedProtocols;
    QHash<QString, QList<ConnectionManager::Parameter> > parameters;
    QMutex mutex;
};

}

using namespace QtTapioca;

ConnectionManager::ConnectionManager(const QString cf, const QString &name, QObject *parent)
     : DBusProxyObject(CONNECTION_MANAGER_BUS_NAME_PREFIX + name, CONNECTION_MANAGER_OBJECT_PATH_PREFIX + name, 0),
       d(new ConnectionManagerPrivate(cf, name))
{
    Q_ASSERT(d);

    readConfig();
    loadConnections();
}

ConnectionManager::~ConnectionManager()
{
    delete d;
}

QString ConnectionManager::name() const
{
    return d->name;
}

bool ConnectionManager::isRunning() const
{
    return d->bus.interface()->isServiceRegistered(d->cm->service());
}

QList<Connection*> ConnectionManager::connections() const
{
    return d->connections.values();
}

Connection *ConnectionManager::requestConnection(const QString &protocol, const QList<ConnectionManager::Parameter> &paramsList)
{
    QDBusObjectPath objPath;
    QMap<QString, QVariant> paramsMap;
    ConnectionManager::Parameter param;

    foreach (param, paramsList) {
        paramsMap[param.name()] = param.value();
    }

    /* RequestConnection will cause the emission of NewConnection, so we need to lock here
     * to make sure the connection object will not be inserted on d->connections twice. */
    d->mutex.lock();
    QDBusReply<QString> reply = d->cm->RequestConnection(protocol, paramsMap, objPath);
    if (!reply.isValid())
    {
        QDBusError error(reply.error());
        qDebug() << "QtTapioca: Error requesting connection:" << error.type()
                 << error.message();
    }

    QString serviceName(reply.value());
    if (serviceName.isEmpty()) {
        /* TODO catch errors */
        d->mutex.unlock();
        return 0;
    }
    Connection *conn = addConnection(serviceName, objPath);
    emit newConnection(conn);
    d->mutex.unlock();
    return conn;
}

bool ConnectionManager::supports(const QString &protocol) const
{
    return supportedProtocols().contains(protocol);
}

const QStringList ConnectionManager::supportedProtocols() const
{
    return d->parameters.keys();
}

const QList<ConnectionManager::Parameter> &ConnectionManager::protocolParameters(const QString &protocol)
{
    return d->parameters[protocol];
}

void ConnectionManager::readConfig()
{
    QSettings settings(d->configFile, QSettings::IniFormat);

    d->cm = new org::freedesktop::Telepathy::ConnectionManager(
            serviceName(), objectPath(), d->bus, parent());
    QObject::connect(d->cm, SIGNAL(NewConnection(const QString &, const QDBusObjectPath &, const QString &)),
                     this, SLOT(onNewConnection(const QString &, const QDBusObjectPath &, const QString &)));

    /* read supported protocols and parameters */
    QString protocol;
    QString group;
    QStringList groups = settings.childGroups();
    foreach (group, groups) {
        if (group.startsWith("Protocol ")) {
            protocol = group.right(group.length() - 9);
            settings.beginGroup(group);

            QString param;
            QStringList params = settings.childKeys();
            QString paramName;
            QVariant paramValue;
            QString paramType;
            uint flags;
            QList<ConnectionManager::Parameter> paramList;

            foreach (param, params) {
                flags = ConnectionManager::Parameter::None;
                if (param.startsWith("param-")) {
                    paramName = param.right(param.length() - 6);
                    QStringList values = settings.value(param).toString().split(QChar(' '));

                    paramValue = charToVariant(values[0][0]);
                    if (values.contains("required")) {
                        flags |= ConnectionManager::Parameter::Required;
                    }
                    if (values.contains("register")) {
                        flags |= ConnectionManager::Parameter::Register;
                    }

                    ConnectionManager::Parameter parameter(paramName, paramValue, (ConnectionManager::Parameter::Flags) flags);
                    paramList << parameter;
                }
            }

            /* now that we have all param-* created, let's find their default values */
            foreach (param, params) {
                if (param.startsWith("default-")) {
                    paramName = param.right(param.length() - 8);
                    Parameter p(paramName, "");
                    int index;

                    index = paramList.indexOf(p);
                    if (index != -1) {
                        Parameter &lparam = paramList[index];
                        lparam.m_flags |= ConnectionManager::Parameter::HasDefault;
                        paramValue = settings.value(param);
                        if (lparam.m_value.type() == QVariant::String)
                            lparam.m_value = paramValue;
                        else if (lparam.m_value.type() == QVariant::Int)
                            lparam.m_value = paramValue.toInt();
                        else if (lparam.m_value.type() == QVariant::UInt)
                            lparam.m_value = paramValue.toUInt();
                        else if (lparam.m_value.type() == QVariant::Double)
                            lparam.m_value = paramValue.toDouble();
                        else if (lparam.m_value.type() == QVariant::Bool) {
                            if (paramValue.toString().toLower() == "true")
                                lparam.m_value = true;
                            else
                                lparam.m_value = false;
                        }
                        else {
                            lparam.m_value = paramValue;
                        }
                    }
                }
            }
            settings.endGroup();
            d->parameters.insert(protocol, paramList);
        }
    }
}

void ConnectionManager::loadConnections()
{
    /* TODO check if we should use loadConnections once and work
     * with NameOwnerChanged and NewConnection signals? */

    QString serviceName;
    QStringList registeredServiceNames =
        static_cast<QStringList>(d->bus.interface()->registeredServiceNames()).filter(
                QRegExp("org.freedesktop.Telepathy.Connection." + d->name + ".*"));
    QDBusObjectPath objPath;
    d->mutex.lock();
    foreach (serviceName, registeredServiceNames) {
        if (d->connections.contains(serviceName)) {
            continue;
        }
        objPath = QDBusObjectPath(QString("/" + serviceName).replace('.', '/'));
        addConnection(serviceName, objPath);
    }
    d->mutex.unlock();
}

Connection *ConnectionManager::addConnection(const QString &serviceName, const QDBusObjectPath &objPath)
{
    Connection *conn = new Connection(serviceName, objPath.path(), parent());
    QObject::connect(conn, SIGNAL(destroyed(QObject *)), this, SLOT(onConnectionDestroyed(QObject*)));
    d->connections[serviceName] = conn;
    return conn;
}

QVariant ConnectionManager::charToVariant(const QChar type)
{
    if (type.toLower() == 's')
        return QVariant(QVariant::String);
    else if (type.toLower() == 'i')
        return QVariant(QVariant::Int);
    else if (type.toLower() == 'u')
        return QVariant(QVariant::UInt);
    else if (type.toLower() == 'd')
        return QVariant(QVariant::Double);
    else if (type.toLower() == 'n')
        return QVariant(QVariant::Int);
    else if (type.toLower() == 'q')
        return QVariant(QVariant::UInt);
    else if (type.toLower() == 'b')
        return QVariant(QVariant::Bool);
    else {
        /* defaults to string */
        return QVariant(QVariant::String);
    }
}

void ConnectionManager::onNewConnection(const QString &serviceName, const QDBusObjectPath &objPath, const QString &proto)
{
    d->mutex.lock();
    if (!d->connections.contains(serviceName)) {
        Connection *conn = addConnection(serviceName, objPath);
        emit newConnection(conn);
    }
    d->mutex.unlock();
}

void ConnectionManager::onConnectionDestroyed(QObject *obj)
{
    Connection *iconn;
    Connection *conn = static_cast<Connection*>(obj);

    QList<Connection *> conns = d->connections.values();

    foreach (iconn, conns)
        if (iconn == conn)
            d->connections.remove(d->connections.key(iconn));
}

ConnectionManager::Parameter::Parameter()
    : m_flags(ConnectionManager::Parameter::None)
{
}

ConnectionManager::Parameter::Parameter(const QString &name, const QVariant value)
    : m_name(name),
      m_value(value),
      m_flags(ConnectionManager::Parameter::None)
{
}

ConnectionManager::Parameter::Parameter(const QString &name, const QVariant value, Flags flags)
    : m_name(name),
      m_value(value),
      m_flags(flags)
{
}

ConnectionManager::Parameter::~Parameter()
{
}

bool ConnectionManager::Parameter::operator==(const Parameter &param) const
{
    return (m_name == param.m_name);
}

bool ConnectionManager::Parameter::operator==(const QString &name) const
{
    return (m_name == name);
}

