/*
 * Copyright (C) 2009 Intel Corporation
 * Copyright (C) 2011 Symbio, Ville Nummela
 *
 * 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) version 3.
 *
 * 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 <fstream>

#include <syncevo/GLibSupport.h>
#include <syncevo/timeout.h>

#include "server.h"
#include "info-req.h"
#include "connection.h"
#include "bluez-manager.h"
#include "session.h"
#include "restart.h"
#include "client.h"
#include "auto-sync-manager.h"
#include "connman-client.h"
#include "network-manager-client.h"
#include "presence-status.h"

using namespace GDBusCXX;

SE_BEGIN_CXX

class ServerLogger : public Logger
{
    Logger::Handle m_parentLogger;
    // Currently a strong reference. Would be a weak reference
    // if we had proper reference counting for Server.
    std::shared_ptr<Server> m_server;

public:
    ServerLogger(const std::shared_ptr<Server> &server) :
        m_parentLogger(Logger::instance()),
        m_server(server)
    {
    }

    virtual void remove() throw ()
    {
        // Hold the Logger mutex while cutting our connection to the
        // server. The code using m_server below does the same and
        // holds the mutex while logging. That way we prevent threads
        // from holding onto the server while it tries to shut down.
        //
        // This is important because the server's live time is not
        // really controlled via the std::shared_ptr, it may
        // destruct while there are still references. See
        // Server::m_logger instantiation below.
        RecMutex::Guard guard = lock();
        m_server.reset();
    }

    virtual void messagev(const MessageOptions &options,
                          const char *format,
                          va_list args)
    {
        // Ensure that remove() cannot proceed while we have the
        // server in use.
        RecMutex::Guard guard = lock();
        Server *server = m_server.get();
        message2DBus(server,
                     options,
                     format,
                     args,
                     server ? server->getPath() : "",
                     getProcessName());
    }

    /**
     * @param server    may be nullptr, in which case logging only goes to parent
     */
    void message2DBus(Server *server,
                      const MessageOptions &options,
                      const char *format,
                      va_list args,
                      const std::string &dbusPath,
                      const std::string &procname)
    {
        // Keeps logging consistent: otherwise thread A might log to
        // parent, thread B to parent and D-Bus, then thread A
        // finishes its logging via D-Bus.  The order of log messages
        // would then not be the same in the parent and D-Bus.
        RecMutex::Guard guard = lock();

        // iterating over args in messagev() is destructive, must make a copy first
        va_list argsCopy;
        va_copy(argsCopy, args);
        m_parentLogger.messagev(options, format, args);

        if (server) {
            try {
                if (options.m_level <= server->getDBusLogLevel()) {
                    string log = StringPrintfV(format, argsCopy);
                    server->logOutput(dbusPath, options.m_level, log, procname);
                }
            } catch (...) {
                remove();
                // Give up on server logging silently.
            }
        }
        va_end(argsCopy);
    }
};

void Server::clientGone(Client *c)
{
    for (auto it = m_clients.begin();
        it != m_clients.end();
        ++it) {
        if (it->second.get() == c) {
            SE_LOG_DEBUG(NULL, "D-Bus client %s has disconnected",
                         c->m_ID.c_str());
            autoTermUnref(it->second->getAttachCount());
            m_clients.erase(it);
            return;
        }
    }
    SE_LOG_DEBUG(NULL, "unknown client has disconnected?!");
}

std::string Server::getNextSession()
{
    // Make the session ID somewhat random. This protects to
    // some extend against injecting unwanted messages into the
    // communication.
    m_lastSession++;
    if (!m_lastSession) {
        m_lastSession++;
    }
    return StringPrintf("%u%u", rand(), m_lastSession);
}

vector<string> Server::getCapabilities()
{
    // Note that this is tested by test-dbus.py in
    // TestServer.testCapabilities, update the test when adding
    // capabilities.
    vector<string> capabilities;

    capabilities.push_back("ConfigChanged");
    capabilities.push_back("GetConfigName");
    capabilities.push_back("NamedConfig");
    capabilities.push_back("Notifications");
    capabilities.push_back("Version");
    capabilities.push_back("SessionFlags");
    capabilities.push_back("SessionAttach");
    capabilities.push_back("DatabaseProperties");
    return capabilities;
}

StringMap Server::getVersions()
{
    StringMap versions;

    versions["version"] = VERSION;
    versions["system"] = EDSAbiWrapperInfo();
    versions["backends"] = SyncSource::backendsInfo();
    return versions;
}

void Server::attachClient(const Caller_t &caller,
                          const std::shared_ptr<Watch> &watch)
{
    std::shared_ptr<Client> client = addClient(caller, watch);
    autoTermRef();
    client->increaseAttachCount();
}

void Server::detachClient(const Caller_t &caller)
{
    std::shared_ptr<Client> client = findClient(caller);
    if (client) {
        autoTermUnref();
        client->decreaseAttachCount();
    }
}

void Server::setNotifications(bool enabled,
                              const Caller_t &caller,
                              const string & /* notifications */)
{
    std::shared_ptr<Client> client = findClient(caller);
    if (client && client->getAttachCount()) {
        client->setNotificationsEnabled(enabled);
    } else {
        SE_THROW("client not attached, not allowed to change notifications");
    }
}

bool Server::notificationsEnabled()
{
    for (auto it = m_clients.begin();
        it != m_clients.end();
        ++it) {
        if (!it->second->getNotificationsEnabled()) {
            return false;
        }
    }
    return true;
}

void Server::connect(const Caller_t &caller,
                     const std::shared_ptr<Watch> &watch,
                     const StringMap &peer,
                     bool must_authenticate,
                     const std::string &session,
                     DBusObject_t &object)
{
    if (m_shutdownRequested) {
        // don't allow new connections, we cannot activate them
        SE_THROW("server shutting down");
    }

    if (!session.empty()) {
        // reconnecting to old connection is not implemented yet
        throw std::runtime_error("not implemented");
    }
    std::string new_session = getNextSession();

    auto c = make_weak_shared::make<Connection>(*this,
                                                getConnection(),
                                                new_session,
                                                peer,
                                                must_authenticate);
    SE_LOG_DEBUG(NULL, "connecting D-Bus client %s with connection %s '%s'",
                 caller.c_str(),
                 c->getPath(),
                 c->m_description.c_str());

    std::shared_ptr<Client> client = addClient(caller,
                                                 watch);
    client->attach(c);
    c->activate();

    object = c->getPath();
}

void Server::startSessionWithFlags(const Caller_t &caller,
                                   const std::shared_ptr<Watch> &watch,
                                   const std::string &server,
                                   const std::vector<std::string> &flags,
                                   DBusObject_t &object)
{
    if (m_shutdownRequested) {
        // don't allow new sessions, we cannot activate them
        SE_THROW("server shutting down");
    }

    std::shared_ptr<Client> client = addClient(caller,
                                                 watch);
    std::string new_session = getNextSession();
    auto session = make_weak_shared::make<Session>(*this,
                                                   "is this a client or server session?",
                                                   server,
                                                   new_session,
                                                   flags);
    client->attach(session);
    session->activate();
    enqueue(session);
    object = session->getPath();
}


std::shared_ptr<Session> Server::startInternalSession(const std::string &server,
                                                        SessionFlags flags,
                                                        const std::function<void (const std::weak_ptr<Session> &session)> &callback)
{
    if (m_shutdownRequested) {
        // don't allow new sessions, we cannot activate them
        SE_THROW("server shutting down");
    }

    std::vector<std::string> dbusFlags;
    if (flags & SESSION_FLAG_NO_SYNC) {
        dbusFlags.push_back("no-sync");
    }
    if (flags & SESSION_FLAG_ALL_CONFIGS) {
        dbusFlags.push_back("all-configs");
    }

    std::string new_session = getNextSession();
    auto session = make_weak_shared::make<Session>(*this,
                                                   "is this a client or server session?",
                                                   server,
                                                   new_session,
                                                   dbusFlags);
    session->m_sessionActiveSignal.connect([weakSessionPtr=std::weak_ptr<Session>(session), callback] () { callback(weakSessionPtr); });
    session->activate();
    enqueue(session);
    return session;
}


void Server::checkPresence(const std::string &server,
                           std::string &status,
                           std::vector<std::string> &transports)
{
    return getPresenceStatus().checkPresence(server, status, transports);
}

void Server::getSessions(std::vector<DBusObject_t> &sessions)
{
    sessions.reserve(m_workQueue.size() + 1);
    if (m_activeSession) {
        sessions.push_back(m_activeSession->getPath());
    }
    for (std::weak_ptr<Session> &session: m_workQueue) {
        std::shared_ptr<Session> s = session.lock();
        if (s) {
            sessions.push_back(s->getPath());
        }
    }
}

Server::Server(GMainLoop *loop,
               std::shared_ptr<Restart> &restart,
               const DBusConnectionPtr &conn,
               int duration) :
    DBusObjectHelper(conn,
                     SessionCommon::SERVER_PATH,
                     SessionCommon::SERVER_IFACE,
                     [this] () { autoTermCallback(); }),
    m_loop(loop),
    m_suspendFlagsSource(0),
    m_shutdownRequested(false),
    m_restart(restart),
    m_conn(conn),
    m_lastSession(time(nullptr)),
    m_activeSession(nullptr),
    m_lastInfoReq(0),
    m_bluezManager(new BluezManager(*this)),
    sessionChanged(*this, "SessionChanged"),
    presence(*this, "Presence"),
    templatesChanged(*this, "TemplatesChanged"),
    configChanged(*this, "ConfigChanged"),
    infoRequest(*this, "InfoRequest"),
    m_logOutputSignal(*this, "LogOutput"),
    m_autoTerm(m_loop, m_shutdownRequested, duration),
    m_dbusLogLevel(Logger::INFO),
    // TODO (?): turn Server into a proper reference counted instance.
    // This would help with dangling references to it when other threads
    // use it for logging, see ServerLogger. However, with mutex locking
    // in ServerLogger that shouldn't be a problem.
    m_logger(new ServerLogger(std::shared_ptr<Server>(this, NopDestructor())))
{
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    srand(tv.tv_usec);
    add(this, &Server::getCapabilities, "GetCapabilities");
    add(this, &Server::getVersions, "GetVersions");
    add(this, &Server::attachClient, "Attach");
    add(this, &Server::detachClient, "Detach");
    add(this, &Server::enableNotifications, "EnableNotifications");
    add(this, &Server::disableNotifications, "DisableNotifications");
    add(this, &Server::notificationAction, "NotificationAction");
    add(this, &Server::connect, "Connect");
    add(this, &Server::startSession, "StartSession");
    add(this, &Server::startSessionWithFlags, "StartSessionWithFlags");
    add(this, &Server::getConfigs, "GetConfigs");
    add(this, &Server::getConfig, "GetConfig");
    add(this, &Server::getReports, "GetReports");
    add(this, &Server::checkSource, "CheckSource");
    add(this, &Server::getDatabases, "GetDatabases");
    add(this, &Server::checkPresence, "CheckPresence");
    add(this, &Server::getSessions, "GetSessions");
    add(this, &Server::infoResponse, "InfoResponse");
    add(sessionChanged);
    add(templatesChanged);
    add(configChanged);
    add(presence);
    add(infoRequest);
    add(m_logOutputSignal);

    // Log entering and leaving idle state and
    // allow/prevent auto-termination.
    auto onIdleChange = [this] (bool idle) {
        SE_LOG_DEBUG(NULL, "server is %s", idle ? "idle" : "not idle");
        if (idle) {
            autoTermUnref();
        } else {
            autoTermRef();
        }
    };
    m_idleSignal.connect(onIdleChange);

    // connect ConfigChanged signal to source for that information
    m_configChangedSignal.connect([this] (const std::string &configName) { configChanged(); });
}

void Server::activate()
{
    // Watch SuspendFlags fd to react to signals quickly.
    int fd = SuspendFlags::getSuspendFlags().getEventFD();
    GIOChannelCXX channel(g_io_channel_unix_new(fd), TRANSFER_REF);
    auto onSuspendFlagsChange = [] (GIOChannel *source,
                                    GIOCondition condition,
                                    gpointer data) throw () {
        Server *me = static_cast<Server *>(data);
        try {
            if (!SuspendFlags::getSuspendFlags().isNormal()) {
                me->m_shutdownRequested = true;
                g_main_loop_quit(me->m_loop);
                SE_LOG_INFO(NULL, "server shutting down because of SIGINT or SIGTERM");
            }
        } catch (...) {
            Exception::handle();
        }
        // Keep watching, just in case that we catch multiple signals.
        return (gboolean)TRUE;
    };
    m_suspendFlagsSource = g_io_add_watch(channel, G_IO_IN, onSuspendFlagsChange, this);

    // Activate our D-Bus object *before* interacting with D-Bus
    // any further. Otherwise GIO D-Bus will start processing
    // messages for us while we start up and reject them because
    // out object isn't visible to it yet.
    GDBusCXX::DBusObjectHelper::activate();

    // Push ourselves as logger for the time being.
    m_logger->setLevel(Logger::DEBUG);
    m_pushLogger.reset(m_logger);

    m_presence.reset(new PresenceStatus(*this));

    // Assume that Bluetooth is available. Neither ConnMan nor Network
    // manager can tell us about that. The "Bluetooth" ConnMan technology
    // is about IP connection via Bluetooth - not what we need.
    getPresenceStatus().updatePresenceStatus(true, PresenceStatus::BT_TRANSPORT);

    m_connman.reset(new ConnmanClient(*this));
    m_networkManager.reset(new NetworkManagerClient(*this));

    if ((!m_connman || !m_connman->isAvailable()) &&
        (!m_networkManager || !m_networkManager->isAvailable())) {
        // assume that we are online if no network manager was found at all
        getPresenceStatus().updatePresenceStatus(true, PresenceStatus::HTTP_TRANSPORT);
    }

    // create auto sync manager, now that server is ready
    m_autoSync = AutoSyncManager::createAutoSyncManager(*this);
}

Server::~Server()
{
    // make sure all other objects are gone before destructing ourselves
    if (m_suspendFlagsSource) {
        g_source_remove(m_suspendFlagsSource);
    }
    m_syncSession.reset();
    m_workQueue.clear();
    m_clients.clear();
    m_autoSync.reset();
    m_infoReqMap.clear();
    m_timeouts.clear();
    m_delayDeletion.clear();
    m_connman.reset();
    m_networkManager.reset();
    m_presence.reset();

    m_pushLogger.reset();
    m_logger.reset();
}

bool Server::shutdown()
{
    Timespec now = Timespec::monotonic();
    bool autosync = m_autoSync && m_autoSync->preventTerm();
    SE_LOG_DEBUG(NULL, "shut down or restart server at %lu.%09lu because of file modifications, auto sync %s",
                 now.tv_sec, now.tv_nsec, autosync ? "on" : "off");
    if (autosync) {
        // suitable exec() call which restarts the server using the same environment it was in
        // when it was started
        SE_LOG_INFO(NULL, "server restarting because files loaded into memory were modified on disk");
        m_restart->restart();
    } else {
        // leave server now
        g_main_loop_quit(m_loop);
        SE_LOG_INFO(NULL, "server shutting down because files loaded into memory were modified on disk");
    }

    return false;
}

void Server::fileModified(const std::string &file)
{
    SE_LOG_DEBUG(NULL, "file %s modified, %s shutdown: %s, %s",
                 file.c_str(),
                 m_shutdownRequested ? "continuing" : "initiating",
                 m_shutdownTimer ? "timer already active" : "timer not yet active",
                 m_activeSession ? "waiting for active session to finish" : "setting timer");
    m_lastFileMod = Timespec::monotonic();
    if (!m_activeSession) {
        m_shutdownTimer.activate(SHUTDOWN_QUIESENCE_SECONDS,
                                 [this] () { return shutdown(); });
    }
    m_shutdownRequested = true;
}

void Server::run()
{
    // This has the intended side effect that it loads everything into
    // memory which might be dynamically loadable, like backend
    // plugins.
    StringMap map = getVersions();
    SE_LOG_DEBUG(NULL, "D-Bus server ready to run, versions:");
    for (const auto &entry: map) {
        SE_LOG_DEBUG(NULL, "%s: %s", entry.first.c_str(), entry.second.c_str());
    }

    // Now that everything is loaded, check memory map for files which we have to monitor.
    set<string> files;
    std::ifstream in("/proc/self/maps");
    while (!in.eof()) {
        string line;
        getline(in, line);
        size_t off = line.find('/');
        if (off != line.npos &&
            line.find(" r-xp ") != line.npos) {
            files.insert(line.substr(off));
        }
    }
    in.close();
    for (const string &file: files) {
        try {
            SE_LOG_DEBUG(NULL, "watching: %s", file.c_str());
            auto notify = std::make_shared<GLibNotify>(file.c_str(), [this, file] (GFile *, GFile *, GFileMonitorEvent) { fileModified(file); });
            m_files.push_back(notify);
        } catch (...) {
            // ignore errors for indidividual files
            Exception::handle();
        }
    }

    SE_LOG_INFO(NULL, "ready to run");
    // Note that with GDBus GIO, this will also finally request the
    // "org.syncevolution" name. This relies on preserving the name in
    // m_conn that we originally passed to dbus_get_bus_connection().
    // getConnection() works with a plain GDBusConnection and doesn't
    // have the name, so we really need our own copy of
    // DBusConnectionPtr here.
    dbus_bus_connection_undelay(m_conn);
    if (!m_shutdownRequested) {
        g_main_loop_run(m_loop);
    }

    SE_LOG_DEBUG(NULL, "%s", "Exiting Server::run");
}


/**
 * look up client by its ID
 */
std::shared_ptr<Client> Server::findClient(const Caller_t &ID)
{
    for (auto it = m_clients.begin();
        it != m_clients.end();
        ++it) {
        if (it->second->m_ID == ID) {
            return it->second;
        }
    }
    return std::shared_ptr<Client>();
}

std::shared_ptr<Client> Server::addClient(const Caller_t &ID,
                                            const std::shared_ptr<Watch> &watch)
{
    std::shared_ptr<Client> client(findClient(ID));
    if (client) {
        return client;
    }
    client.reset(new Client(*this, ID));
    // add to our list *before* checking that peer exists, so
    // that clientGone() can remove it if the check fails
    m_clients.push_back(std::make_pair(watch, client));
    // Here we intentionally bind the raw pointer. It's only going to be used for
    // comparisons.
    watch->setCallback([this, clientPtr=client.get()] () { Server::clientGone(clientPtr); });
    return client;
}


void Server::detach(Resource *resource)
{
    for (const auto &client_entry: m_clients) {
        client_entry.second->detachAll(resource);
    }
}

void Server::enqueue(const std::shared_ptr<Session> &session)
{
    bool idle = isIdle();

    auto it = m_workQueue.end();
    while (it != m_workQueue.begin()) {
        --it;
        // skip over dead sessions, they will get cleaned up elsewhere
        std::shared_ptr<Session> session = it->lock();
        if (session && session->getPriority() <= session->getPriority()) {
            ++it;
            break;
        }
    }
    m_workQueue.insert(it, session);
    checkQueue();

    if (idle) {
        m_idleSignal(false);
    }
}

void Server::killSessionsAsync(const std::string &peerDeviceID,
                               const SimpleResult &onResult)
{
    auto it = m_workQueue.begin();
    while (it != m_workQueue.end()) {
        std::shared_ptr<Session> session = it->lock();
        if (session && session->getPeerDeviceID() == peerDeviceID) {
            SE_LOG_DEBUG(NULL, "removing pending session %s because it matches deviceID %s",
                         session->getSessionID().c_str(),
                         peerDeviceID.c_str());
            // remove session and its corresponding connection
            std::shared_ptr<Connection> c = session->getStubConnection().lock();
            if (c) {
                c->shutdown();
            }
            it = m_workQueue.erase(it);
        } else {
            ++it;
        }
    }

    // Check active session. We need to wait for it to shut down cleanly.
    std::shared_ptr<Session> active = m_activeSessionRef.lock();
    if (active &&
        active->getPeerDeviceID() == peerDeviceID) {
        SE_LOG_DEBUG(NULL, "aborting active session %s because it matches deviceID %s",
                     active->getSessionID().c_str(),
                     peerDeviceID.c_str());
        // hand over work to session
        active->abortAsync(onResult);
    } else {
        onResult.done();
    }
}

void Server::dequeue(Session *session)
{
    bool idle = isIdle();

    if (m_syncSession.get() == session) {
        // This is the running sync session.
        // It's not in the work queue and we have to
        // keep it active, so nothing to do.
        return;
    }

    for (auto it = m_workQueue.begin();
         it != m_workQueue.end();
         ++it) {
        if (it->lock().get() == session) {
            // remove from queue
            m_workQueue.erase(it);
            break;
        }
    }

    if (m_activeSession == session) {
        // The session is releasing the lock, so someone else might
        // run now.
        sessionChanged(session->getPath(), false);
        m_activeSession = nullptr;
        m_activeSessionRef.reset();
        if (m_lastFileMod && !m_shutdownTimer) {
            // File modification was detected while a session was active.
            // Trigger the shutdown now that it is gone. As without a
            // a session, the goal is to shut down SHUTDOWN_QUIESENCE_SECONDS
            // after the last file modification.
            Timespec now = Timespec::monotonic();
            Timespec shutdown = m_lastFileMod + SHUTDOWN_QUIESENCE_SECONDS;
            if (shutdown >= now) {
                SE_LOG_DEBUG(NULL, "file modified, initiating shutdown after session finished");
                m_shutdownTimer.activate((shutdown - now).seconds(),
                                         [this] () { return this->shutdown(); });
            }
        }
        checkQueue();
    }

    if (!idle && isIdle()) {
        m_idleSignal(true);
    }
}

void Server::addSyncSession(Session *session)
{
    // Only one session can run a sync, and only the active session
    // can make itself the sync session.
    if (m_syncSession) {
        if (m_syncSession.get() != session) {
            SE_THROW("already have a sync session");
        } else {
            return;
        }
    }
    m_syncSession = m_activeSessionRef.lock();
    m_newSyncSessionSignal(m_syncSession);
    if (!m_syncSession) {
        SE_THROW("session should not start a sync, all clients already detached");
    }
    if (m_syncSession.get() != session) {
        m_syncSession.reset();
        SE_THROW("inactive session asked to become sync session");
    }
}

void Server::removeSyncSession(Session *session)
{
    if (session == m_syncSession.get()) {
        // Normally the owner calls this, but if it is already gone,
        // then do it again and thus effectively start counting from
        // now.
        delaySessionDestruction(m_syncSession);
        m_syncSession.reset();
    } else {
        SE_LOG_DEBUG(NULL, "ignoring removeSyncSession() for session %s, it is not the sync session",
                     session->getSessionID().c_str());
    }
}

void Server::checkQueue()
{
    if (m_activeSession) {
        // still busy
        return;
    }

    if (m_shutdownRequested) {
        // Don't schedule new sessions. Instead return to Server::run().
        // But don't do it immediately: when done inside the Session.Detach()
        // call, the D-Bus response was not delivered reliably to the client
        // which caused the shutdown. When Server::fileModified() already set
        // a timeout then we don't need to do it again (solves a race
        // condition in test-dbus.py's testSession2: without this check,
        // if the file modification is detected before handling the Detach(),
        // then the server shut down immediately after Detach()).
        if (!m_shutdownTimer) {
            SE_LOG_DEBUG(NULL, "shutting down in checkQueue(), idle and shutdown was requested");
            addTimeout([this] () {
                    SE_LOG_DEBUG(NULL, "stopping server's event loop");
                    g_main_loop_quit(m_loop);
                },
                0);
        }
        return;
    }

    while (!m_workQueue.empty()) {
        std::shared_ptr<Session> session = m_workQueue.front().lock();
        m_workQueue.pop_front();
        if (session) {
            // activate the session
            m_activeSession = session.get();
            m_activeSessionRef = session;
            SE_LOG_DEBUG(NULL, "activating session %p", m_activeSession);
            session->activateSession();
            sessionChanged(session->getPath(), true);
            return;
        }
    }
}

void Server::delaySessionDestruction(const std::shared_ptr<Session> &session)
{
    if (!session) {
        return;
    }

    SE_LOG_DEBUG(NULL, "delaying destruction of session %s by one minute",
                 session->getSessionID().c_str());
    // The trick here is to bind the shared pointer to the timeout instance
    // for the duration of the timeout.
    addTimeout([session] () {
            SE_LOG_DEBUG(NULL, "session %s expired",
                         session->getSessionID().c_str());
        },
        60 /* 1 minute */);
}

inline void insertPair(std::map<string, string> &params,
                       const string &key,
                       const string &value)
{
    if(!value.empty()) {
        params.insert(pair<string, string>(key, value));
    }
}


std::shared_ptr<InfoReq> Server::passwordRequest(const string &descr,
                                                   const ConfigPasswordKey &key,
                                                   const std::weak_ptr<Session> &s)
{
    std::shared_ptr<Session> session = s.lock();
    if (!session) {
        // already gone, ignore request
        return std::shared_ptr<InfoReq>();
    }

    std::map<string, string> params;
    insertPair(params, "description", descr);
    insertPair(params, "user", key.user);
    insertPair(params, "SyncML server", key.server);
    insertPair(params, "domain", key.domain);
    insertPair(params, "object", key.object);
    insertPair(params, "protocol", key.protocol);
    insertPair(params, "authtype", key.authtype);
    insertPair(params, "port", key.port ? StringPrintf("%u",key.port) : "");
    std::shared_ptr<InfoReq> req = createInfoReq("password", params, *session);
    // Return password or failure to Session and thus the session helper.
    req->m_responseSignal.connect([this, s] (const InfoReq::InfoMap &response) { passwordResponse(response, s); });
    // Tell session about timeout.
    req->m_timeoutSignal.connect(InfoReq::TimeoutSignal_t::slot_type(&Session::passwordResponse,
                                                                     session.get(),
                                                                     true,
                                                                     false,
                                                                     "").track_foreign(s));
    // Request becomes obsolete when session is done.
    session->m_doneSignal.connect([this, id=req->getId()] (SyncEvo::SyncMLStatus, const SyncEvo::SyncReport &) { removeInfoReq(id); });

    return req;
}

void Server::passwordResponse(const InfoReq::InfoMap &response,
                              const std::weak_ptr<Session> &s)
{
    std::shared_ptr<Session> session = s.lock();
    if (!session) {
        // already gone, ignore request
        return;
    }

    auto it = response.find("password");
    if (it == response.end()) {
        // no password provided, user wants to abort
        session->passwordResponse(false, true, "");
    } else {
        // password provided, might be empty
        session->passwordResponse(false, false, it->second);
    }
}


void Server::addTimeout(const std::function<void ()> &callback,
                        int seconds)
{
    auto timeout = std::make_shared<Timeout>();
    m_timeouts.push_back(timeout);
    auto callTimeout = [this, timeoutWeak=std::weak_ptr<Timeout>(timeout), callback] () {
        auto timeout = timeoutWeak.lock();
        if (timeout) {
            callback();
            // We are executing the timeout, don't invalidate the instance
            // until later when our caller is no longer using the instance to
            // call us.
            delayDeletion(timeout);
            m_timeouts.remove(timeout);
        }
        return false;
    };
    timeout->activate(seconds, callTimeout);
}

void Server::infoResponse(const Caller_t &caller,
                          const std::string &id,
                          const std::string &state,
                          const std::map<string, string> &response)
{
    auto it = m_infoReqMap.find(id);
    // if not found, ignore
    if (it != m_infoReqMap.end()) {
        const std::shared_ptr<InfoReq> infoReq = it->second.lock();
        if (infoReq) {
            infoReq->setResponse(caller, state, response);
        }
    }
}

std::shared_ptr<InfoReq> Server::createInfoReq(const string &type,
                                               const std::map<std::string, std::string> &parameters,
                                               const Session &session)
{
    auto infoReq = std::make_shared<InfoReq>(*this, type, parameters, session.getPath());
    m_infoReqMap.insert(std::make_pair(infoReq->getId(), infoReq));
    // will be removed automatically
    auto remove = [this, id=infoReq->getId()] () { removeInfoReq(id); };
    infoReq->m_responseSignal.connect([this, id=infoReq->getId()] (const std::map<std::string, std::string> &) { removeInfoReq(id); });
    infoReq->m_timeoutSignal.connect([this, id=infoReq->getId()] () { removeInfoReq(id); });
    return infoReq;
}

std::string Server::getNextInfoReq()
{
    return StringPrintf("%u", ++m_lastInfoReq);
}

void Server::emitInfoReq(const InfoReq &req)
{
    infoRequest(req.getId(),
                req.getSessionPath(),
                req.getInfoStateStr(),
                req.getHandler(),
                req.getType(),
                req.getParam());
}

void Server::removeInfoReq(const std::string &id)
{
    // remove InfoRequest from hash map
    m_infoReqMap.erase(id);
}

PresenceStatus &Server::getPresenceStatus()
{
    if (!m_presence) {
        SE_THROW("internal error: Server::getPresenceStatus() called while server has no instance");
    }
    return *m_presence;
}

void Server::getDeviceList(SyncConfig::DeviceList &devices)
{
    //wait bluez or other device managers
    // TODO: make this asynchronous?!
    while(!m_bluezManager->isDone()) {
        g_main_loop_run(m_loop);
    }

    devices.clear();
    devices = m_syncDevices;
}

void Server::addPeerTempl(const string &templName,
                          const std::shared_ptr<SyncConfig::TemplateDescription> &peerTempl)
{
    std::string lower = templName;
    boost::to_lower(lower);
    m_matchedTempls.insert(MatchedTemplates::value_type(lower, peerTempl));
}

std::shared_ptr<SyncConfig::TemplateDescription> Server::getPeerTempl(const string &peer)
{
    std::string lower = peer;
    boost::to_lower(lower);
    auto it = m_matchedTempls.find(lower);
    if(it != m_matchedTempls.end()) {
        return it->second;
    } else {
        return std::shared_ptr<SyncConfig::TemplateDescription>();
    }
}

bool Server::getDevice(const string &deviceId, SyncConfig::DeviceDescription &device)
{
    SyncConfig::DeviceList::iterator syncDevIt;
    for (syncDevIt = m_syncDevices.begin(); syncDevIt != m_syncDevices.end(); ++syncDevIt) {
        if (boost::equals(syncDevIt->m_deviceId, deviceId)) {
            device = *syncDevIt;
            if (syncDevIt->m_pnpInformation) {
                device.m_pnpInformation = std::shared_ptr<SyncConfig::PnpInformation>(
                    new SyncConfig::PnpInformation(syncDevIt->m_pnpInformation->m_vendor,
                                                   syncDevIt->m_pnpInformation->m_product));
            }
            return true;
        }
    }
    return false;
}

void Server::addDevice(const SyncConfig::DeviceDescription &device)
{
    SyncConfig::DeviceList::iterator it;
    for (it = m_syncDevices.begin(); it != m_syncDevices.end(); ++it) {
        if (boost::iequals(it->m_deviceId, device.m_deviceId)) {
            break;
        }
    }
    if (it == m_syncDevices.end()) {
        m_syncDevices.push_back(device);
        templatesChanged();
    }
}

void Server::removeDevice(const string &deviceId)
{
    SyncConfig::DeviceList::iterator syncDevIt;
    for (syncDevIt = m_syncDevices.begin(); syncDevIt != m_syncDevices.end(); ++syncDevIt) {
        if (boost::equals(syncDevIt->m_deviceId, deviceId)) {
            m_syncDevices.erase(syncDevIt);
            templatesChanged();
            break;
        }
    }
}

void Server::updateDevice(const string &deviceId,
                          const SyncConfig::DeviceDescription &device)
{
    SyncConfig::DeviceList::iterator it;
    for (it = m_syncDevices.begin(); it != m_syncDevices.end(); ++it) {
        if (boost::iequals(it->m_deviceId, deviceId)) {
            (*it) = device;
            templatesChanged();
            break;
        }
    }
}

void Server::message2DBus(const Logger::MessageOptions &options,
                          const char *format,
                          va_list args,
                          const std::string &dbusPath,
                          const std::string &procname)
{
    // prefix is used to set session path
    // for general server output, the object path field is dbus server
    // the object path can't be empty for object paths prevent using empty string.
    m_logger->message2DBus(this, options, format, args, dbusPath, procname);
}

void Server::logOutput(const GDBusCXX::DBusObject_t &path,
                       Logger::Level level,
                       const std::string &explanation,
                       const std::string &procname)
{
    if (level <= m_dbusLogLevel) {
        string strLevel = Logger::levelToStr(level);
        m_logOutputSignal(path, strLevel, explanation, procname);
    }
}

SE_END_CXX
