/*
 * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
 * Copyright (C) 2009 Intel Corporation
 *
 * 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 "config.h"
#include <stddef.h>
#include <iostream>
#include <memory>
using namespace std;

#include <libgen.h>
#ifdef HAVE_GLIB
#include <glib-object.h>
#endif

#include <syncevo/Cmdline.h>
#include "EvolutionSyncSource.h"
#include <syncevo/SyncContext.h>
#include <syncevo/LogRedirect.h>
#include "CmdlineSyncClient.h"

#include <dlfcn.h>
#include <signal.h>

#include <syncevo/declarations.h>

#ifdef DBUS_SERVICE

#include <gdbus-cxx-bridge.h>

struct SourceStatus {
    string m_mode;
    string m_status;
    uint32_t m_error;
};
template<> struct dbus_traits<SourceStatus> :
public dbus_struct_traits<SourceStatus,
       dbus_member<SourceStatus, string, &SourceStatus::m_mode,
       dbus_member<SourceStatus, string, &SourceStatus::m_status,
       dbus_member_single<SourceStatus, uint32_t, &SourceStatus::m_error> > > >
{};
#endif

SE_BEGIN_CXX

#if defined(ENABLE_MAEMO) && defined (ENABLE_EBOOK)

// really override the symbol, even if redefined by EDSAbiWrapper
#undef e_contact_new_from_vcard
extern "C" EContact *e_contact_new_from_vcard(const char *vcard)
{
    static typeof(e_contact_new_from_vcard) *impl;

    if (!impl) {
        impl = (typeof(impl))dlsym(RTLD_NEXT, "e_contact_new_from_vcard");
    }

    // Old versions of EDS-DBus parse_changes_array() call
    // e_contact_new_from_vcard() with a pointer which starts
    // with a line break; Evolution is not happy with that and
    // refuses to parse it. This code forwards until it finds
    // the first non-whitespace, presumably the BEGIN:VCARD.
    while (*vcard && isspace(*vcard)) {
        vcard++;
    }

    return impl ? impl(vcard) : NULL;
}
#endif

/**
 * This is a class derived from Cmdline. The purpose
 * is to implement the factory method 'createSyncClient' to create
 * new implemented 'CmdlineSyncClient' objects.
 */
class KeyringSyncCmdline : public Cmdline {
 public:
    KeyringSyncCmdline(int argc, const char * const * argv, ostream &out, ostream &err):
        Cmdline(argc, argv, out, err) 
    {}
    /**
     * create a user implemented sync client.
     */
    SyncContext* createSyncClient() {
        return new CmdlineSyncClient(m_server, true, m_keyring);
    }
};

#ifdef DBUS_SERVICE
class RemoteSession;
typedef map<string, StringMap> Config_t;

/**
 * Act as a dbus server. All requests to dbus server
 * are passed through this class.
 */
class RemoteDBusServer : public DBusRemoteObject
{
public:
    RemoteDBusServer();

    virtual const char *getDestination() const {return "org.syncevolution";}
    virtual const char *getPath() const {return "/org/syncevolution/Server";}
    virtual const char *getInterface() const {return "org.syncevolution.Server";}
    virtual DBusConnection *getConnection() const {return m_conn.get();}
    GMainLoop *getLoop() { return m_loop; }

    /** 
     * Check whether the server is started and can be attached.
     * Printing an error message is optional, some callers might
     * prefer a different kind of error handling.
     */
    bool checkStarted(bool printError = true);

    /**
     * execute arguments from command line
     * @param args the arguments of command line
     * @param config the config name parsed from arguments if has
     * @param runSync arguments to run a sync
     * @return true if successfully
     */
    bool execute(const vector<string> &args, const string &config, bool runSync);

    /**
     * To implement the feature of '--monitor' option, monitor a
     * given config if there is a session running.
     * If config is empty, then peak a running session to monitor.
     * @param config the config name parsed from arguments if has
     * @return true if successfully
     */ 
    bool monitor(const string &config);

    /**
     * To implement the feature of '--status' without a server.
     * get and print all running sessions in the dbus server
     */
    bool runningSessions();

    /** whether the dbus call(s) has/have completed */
    bool done() { return m_replyTotal == m_replyCounter; }

    /** one reply returns. Increase reply counter. */
    void replyInc();

    /** set whether there is an error */
    void setResult(bool result) { m_result = result; }

    /** call 'Server.InfoResponse' */
    void infoResponse(const string &id, const string &state, const StringMap &resp);

private:
    /** call 'Attach' until it returns */
    void attachSync();

    /** 
     * callback of 'Server.Attach'
     * also set up a watch and add watch callback when the daemon is gone
     */
    void attachCb(const boost::shared_ptr<Watch> &watch, const string &error);

    /** callback of 'Server.GetSessions' */
    void getSessionsCb(const vector<string> &sessions, const string &error);

    /** callback of 'Server.SessionChanged' */
    void sessionChangedCb(const DBusObject_t &object, bool active);

    /** callback of 'Server.LogOutput' */
    void logOutputCb(const DBusObject_t &object, const string &level, const string &log);

    /** callback of 'Server.InfoRequest' */
    void infoReqCb(const string &,
                   const DBusObject_t &,
                   const string &,
                   const string &,
                   const string &,
                   const StringMap &);

    /** callback of Server.InfoResponse */
    void infoResponseCb(const string &error);

    /** callback of calling 'Server.StartSession' */
    void startSessionCb(const DBusObject_t &session, const string &error);

    /** update active session vector according to 'SessionChanged' signal */
    void updateSessions(const string &session, bool active);

    /** check m_session is active */
    bool isActive();

    /** get all running sessions. Used internally. */
    void getRunningSessions();

    /** called when daemon has gone */
    void daemonGone();

    /** set the total number of replies we must wait */
    void resetReplies(int total = 1) 
    { 
        m_replyTotal = total;
        m_replyCounter = 0; 
    }

    /** signal handler for 'CTRL-C' */
    static void handleSignal(int sig);

    // session used for signal handler, 
    // used to call 'suspend' and 'abort'
    static boost::weak_ptr<RemoteSession> g_session;

    // the main loop
    GMainLoop *m_loop;
    // connection
    DBusConnectionPtr m_conn;
    // whether client can attach to the daemon. 
    // It is also used to indicate whether daemon is ready to use.
    bool m_attached;
    // error flag
    bool m_result;
    // config name
    string m_configName;
    // active session object path
    boost::shared_ptr<string> m_activeSession;
    // session created or monitored
    boost::shared_ptr<RemoteSession> m_session;
    // active sessions after listening to 'SessionChanged' signals
    vector<string> m_activeSessions;
    // all sessions in dbus server
    vector<boost::shared_ptr<RemoteSession> >  m_sessions;
    // the number of total dbus calls  
    unsigned int m_replyTotal;
    // the number of returned dbus calls 
    unsigned int m_replyCounter;
    // sessions which are running
    vector<boost::weak_ptr<RemoteSession> > m_runSessions;
    // listen to dbus server signal 'SessionChanged'
    SignalWatch2<DBusObject_t, bool> m_sessionChanged;
    // listen to dbus server signal 'LogOutput'
    SignalWatch3<DBusObject_t, string, string> m_logOutput;
    // listen to dbus server signal 'InfoRequest'
    SignalWatch6<string, 
                 DBusObject_t,
                 string,
                 string,
                 string,
                 StringMap > m_infoReq; 

    /** watch daemon whether it is gone */
    boost::shared_ptr<Watch> m_daemonWatch;
};

/**
 * Act as a session. All requests to a session are passed
 * through this class.
 */
class RemoteSession : public DBusRemoteObject
{
public:
    RemoteSession(RemoteDBusServer &server, const std::string &path);
    virtual const char *getDestination() const {return "org.syncevolution";}
    virtual const char *getPath() const {return m_path.c_str();}
    virtual const char *getInterface() const {return "org.syncevolution.Session";}
    virtual DBusConnection *getConnection() const {return m_server.getConnection();}
    RemoteDBusServer &getServer() { return m_server; }

    /**
     * call 'Execute' method of 'Session' in dbus server
     * without waiting for return
     */
    void executeAsync(const vector<string> &args);

    /**
     * call 'GetStatus' method of 'Session' in dbus server
     * without waiting for return
     */
    void getStatusAsync();

    /**
     * call 'Suspend' method of 'Session' in dbus server
     * without waiting for return
     */
    void suspendAsync();

    /**
     * call 'Abort' method of 'Session' in dbus server
     * without waiting for return
     */
    void abortAsync();

    /**
     * call 'GetConfig' method of 'Session' in dbus server
     * without waiting for return
     */
    void getConfigAsync();

    /** get config name of this session */
    string configName() { return m_configName; }

    /** status 'done' is sent by session */
    bool statusDone() { return boost::iequals(m_status, "done"); }

    /** get current status */
    string status() { return m_status; }

    /** set the flag to indicate the session is running sync */
    void setRunSync(bool runSync) { m_runSync = runSync; }

    /** monitor status of the sesion until it is done */
    void monitorSync();

    /** pass through logoutput and print them if m_output is true */
    void logOutput(Logger::Level level, const string &log);

    /** set whether to print output */
    void setOutput(bool output) { m_output = output; }

    /** process signals from daemon */
    void infoReq(const string &id,
                 const DBusObject_t &session,
                 const string &state,
                 const string &handler,
                 const string &type,
                 const StringMap &params);

    /** remove InfoReq objects from map */
    void removeInfoReq(const string &id);

    typedef std::map<std::string, SourceStatus> SourceStatuses_t;

private:
    /**
     * InfoReq to handle info requests from daemon and
     * call 'Server.InfoResponse' to send its response
     */
    class InfoReq
    {
        /** the session reference */
        RemoteSession &m_session;
        /** the id of InfoRequest */
        string m_id;
        /** the type of InfoRequest */
        string m_type;
        /** the response map sent to the daemon*/
        StringMap m_resp;

        /** InfoRequest state */
        enum State {
            INIT, // init
            WORKING, //'working'
            RESPONSE, // 'response'
            DONE // 'done'
        };
        /** the current state of InfoRequest */
        State m_state;
    public:
        InfoReq(RemoteSession &session, const string &id, const string &type);

        /**
         * process the info request dispatched by session
         */
        void process(const string &id,
                     const DBusObject_t &session,
                     const string &state,
                     const string &handler,
                     const string &type,
                     const StringMap &params);
    };

    /** callback of calling 'Session.Execute' */
    void executeCb(const string &error);

    /** callback of 'Session.GetStatus' */
    void getStatusCb(const string &status,
                     uint32_t errorCode,
                     const SourceStatuses_t &sourceStatus,
                     const string &error);

    /** callback of 'Session.GetConfig' */
    void getConfigCb(const Config_t &config, const string &error);

    /** callback of 'Session.StatusChanged' */
    void statusChangedCb(const string &status,
                         uint32_t errorCode,
                         const SourceStatuses_t &sourceStatus);

    /** callback of 'Session.Suspend' */
    void suspendCb(const string &);

    /** callback of 'Session.Abort' */
    void abortCb(const string &);

    /**
     * implement requirements from info req. Called by InfoReq.
     */
    void handleInfoReq(const string &type, const StringMap &params, StringMap &resp);

    /** dbus server */
    RemoteDBusServer &m_server;

    /* whether to log output */
    bool m_output;

    /** object path */
    string m_path;

    /** config name of the session */
    string m_configName;

    /** current status */
    string m_status;
    
    /** session is running sync */
    bool m_runSync;

    /** signal watch 'StatusChanged' */
    SignalWatch3<std::string, uint32_t, SourceStatuses_t> m_statusChanged;

    /** InfoReq map. store all infoReq belongs to this session */
    map<string, boost::shared_ptr<InfoReq> > m_infoReqs;
};

/**
 * Get current known environment variables, which might be used
 * in executing command line arguments. This is only necessary
 * when using dbus daemon.
 * @param vars the returned environment variables
 */
static void getEnvVars(map<string, string> &vars);

#endif

extern "C"
int main( int argc, char **argv )
{
#ifdef ENABLE_MAEMO
    // EDS-DBus uses potentially long-running calls which may fail due
    // to the default 25s timeout. Some of these can be replaced by
    // their async version, but e_book_async_get_changes() still
    // triggered it.
    //
    // The workaround for this is to link the binary against a libdbus
    // which has the dbus-timeout.patch and thus let's users and
    // the application increase the default timeout.
    setenv("DBUS_DEFAULT_TIMEOUT", "600000", 0);
#endif

    // Intercept stderr and route it through our logging.
    // stdout is printed normally. Deconstructing it when
    // leaving main() does one final processing of pending
    // output.
    LogRedirect redirect(false);

#if defined(HAVE_GLIB)
    // this is required when using glib directly or indirectly
    g_type_init();
    g_thread_init(NULL);
    g_set_prgname("syncevolution");
#endif

    setvbuf(stderr, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    // Expand PATH to cover the directory we were started from?
    // This might be needed to find normalize_vcard.
    char *exe = strdup(argv[0]);
    if (strchr(exe, '/') ) {
        char *dir = dirname(exe);
        string path;
        char *oldpath = getenv("PATH");
        if (oldpath) {
            path += oldpath;
            path += ":";
        }
        path += dir;
        setenv("PATH", path.c_str(), 1);
    }
    free(exe);

    try {
        /*
         * don't log errors to cerr: LogRedirect cannot distinguish
         * between our valid error messages and noise from other
         * libs, therefore it would get suppressed (logged at
         * level DEVELOPER, while output is at most INFO)
         */
        KeyringSyncCmdline cmdline(argc, argv, std::cout, std::cout);
        vector<string> parsedArgs;
        if(!cmdline.parse(parsedArgs)) {
            return 1;
        }

        if (cmdline.dontRun()) {
            return 0;
        }

        Cmdline::Bool useDaemon = cmdline.useDaemon();

        if(cmdline.monitor()) {

#ifdef DBUS_SERVICE
            // monitor a session
            RemoteDBusServer server;
            if(server.checkStarted() && server.monitor(cmdline.getConfigName())) {
                return 0;
            }
            return 1;
#else
            SE_LOG_ERROR(NULL, NULL, "this syncevolution binary was compiled without support for monitoring a background sync");
            return 1;
#endif
        } else if(cmdline.status() && 
                  cmdline.getConfigName().empty()) {

#ifdef DBUS_SERVICE
            // '--status' and no server name, try to get running sessions 
            RemoteDBusServer server;
            if(server.checkStarted() && server.runningSessions()) {
                return 0;
            }
            return 1;
#else
            SE_LOG_SHOW(NULL, NULL, "this syncevolution binary was compiled without support for monitoring a background sync");
            return 1;
#endif
        } else if (useDaemon ||
                   !useDaemon.wasSet()) {
#ifdef DBUS_SERVICE
            RemoteDBusServer server;

            // Running execute() without the server available will print errors.
            // Avoid that unless the user explicitly asked for the daemon.
            bool result = server.checkStarted(false);
            if (useDaemon.wasSet() || result) {
                return !server.execute(parsedArgs, cmdline.getConfigName(), cmdline.isSync());
            } else {
                // User didn't select --use-daemon and thus doesn't need to know about it
                // not being available.
                // SE_LOG_SHOW(NULL, NULL, "WARNING: cannot run syncevolution as daemon. "
                //             "Trying to run it without daemon.");
            }
#else
            if (useDaemon.wasSet()) {
                SE_LOG_SHOW(NULL, NULL, "ERROR: this syncevolution binary was compiled without support of daemon. "
                            "Either run syncevolution with '--use-daemon=no' or without that option."); 
                return 1;
            }
#endif
        } 

        // if forcing not using daemon or trying to use daemon with failures,
        // run arguments in the process
        if (!useDaemon.wasSet() ||
            !useDaemon) {
            EDSAbiWrapperInit();

            /*
             * don't log errors to cerr: LogRedirect cannot distinguish
             * between our valid error messages and noise from other
             * libs, therefore it would get suppressed (logged at
             * level DEVELOPER, while output is at most INFO)
             */
            if (cmdline.run()) {
                return 0;
            } else {
                return 1;
            }
        }
    } catch ( const std::exception &ex ) {
        SE_LOG_ERROR(NULL, NULL, "%s", ex.what());
    } catch (...) {
        SE_LOG_ERROR(NULL, NULL, "unknown error");
    }

    return 1;
}

#ifdef DBUS_SERVICE
/********************** RemoteDBusServer implementation **************************/
RemoteDBusServer::RemoteDBusServer()
    :m_attached(false), m_result(true),
     m_replyTotal(0), m_replyCounter(0),
     m_sessionChanged(*this,"SessionChanged"),
     m_logOutput(*this, "LogOutput"),
     m_infoReq(*this, "InfoRequest")
{
    m_loop = g_main_loop_new (NULL, FALSE);
    m_conn = b_dbus_setup_bus(DBUS_BUS_SESSION, NULL, true, NULL);

    if(m_conn) {
        //check whether we can attach to the daemon
        //also set up the daemon watch when attaching to server
        attachSync();
        if(m_attached) {
            m_sessionChanged.activate(boost::bind(&RemoteDBusServer::sessionChangedCb, this, _1, _2));
            m_logOutput.activate(boost::bind(&RemoteDBusServer::logOutputCb, this, _1, _2, _3));
            m_infoReq.activate(boost::bind(&RemoteDBusServer::infoReqCb, this, _1, _2, _3, _4, _5, _6));
        }
    }
}

bool RemoteDBusServer::checkStarted(bool printError)
{
    if(!m_attached) {
        if (printError) {
            SE_LOG_ERROR(NULL, NULL, "SyncEvolution D-Bus server not available.");
        }
        return false;
    }
    return true;
}

void RemoteDBusServer::attachSync()
{
    resetReplies();
    DBusClientCall1<boost::shared_ptr<Watch> > attach(*this, "Attach");
    attach(boost::bind(&RemoteDBusServer::attachCb, this, _1, _2));
    while(!done()) {
        g_main_loop_run(m_loop);
    }
}

void RemoteDBusServer::attachCb(const boost::shared_ptr<Watch> &watch, const string &error)
{
    replyInc();
    if(error.empty()) {
        // don't print error information, leave it to caller
        m_attached = true;
        //if attach is successful, watch server whether it is gone
        m_daemonWatch = watch;
        m_daemonWatch->setCallback(boost::bind(&RemoteDBusServer::daemonGone,this));
    }
}

void RemoteDBusServer::logOutputCb(const DBusObject_t &object,
                                   const string &level,
                                   const string &log)
{
    if (m_session && 
        (boost::equals(object, getPath()) ||
         boost::equals(object, m_session->getPath()))) {
        m_session->logOutput(Logger::strToLevel(level.c_str()), log);
    }
}

void RemoteDBusServer::infoReqCb(const string &id,
                                 const DBusObject_t &session,
                                 const string &state,
                                 const string &handler,
                                 const string &type,
                                 const StringMap &params)
{
    // if m_session is null, just ignore
    if(m_session) {
        m_session->infoReq(id, session, state, handler, type, params);
    }
}

void RemoteDBusServer::infoResponse(const string &id,
                                    const string &state,
                                    const StringMap &resp)
{
    //call Server.InfoResponse
    DBusClientCall0 call(*this, "InfoResponse");
    call(id, state, resp, boost::bind(&RemoteDBusServer::infoResponseCb, this, _1));
}

void RemoteDBusServer::infoResponseCb(const string &error)
{
    replyInc();
    if(!error.empty()) {
        SE_LOG_ERROR(NULL, NULL, "information response failed.");
        m_result = false;
    }
    g_main_loop_quit(m_loop);
}

void RemoteDBusServer::sessionChangedCb(const DBusObject_t &object, bool active)
{
    // update active sessions if needed
    updateSessions(object, active);
    g_main_loop_quit(m_loop);
}

void RemoteDBusServer::daemonGone()
{
    //print error info and exit
    SE_LOG_ERROR(NULL, NULL, "Background sync daemon has gone.");
    exit(1);
}

/**
 * Don't hang onto a shared_ptr here!
 *
 * RemoteSessions contain a reference to the
 * RemoteDBusServer which created them. Once that
 * server destructs, all sessions must have been
 * deleted earlier, otherwise they'll call a destructed
 * object.
 */
boost::weak_ptr<RemoteSession> RemoteDBusServer::g_session;
void RemoteDBusServer::handleSignal(int sig)
{
    SyncContext::handleSignal(sig);
    boost::shared_ptr<RemoteSession> session = g_session.lock();
    if (session) {
        const SuspendFlags &flags = SyncContext::getSuspendFlags(); 
        if(flags.state == SuspendFlags::CLIENT_SUSPEND) {
            session->suspendAsync();
        } else if(flags.state == SuspendFlags::CLIENT_ABORT) {
            session->abortAsync();
        }
    }
}

bool RemoteDBusServer::execute(const vector<string> &args, const string &peer, bool runSync)
{
    //the basic workflow is:
    //1) start a session 
    //2) waiting for the session becomes active
    //3) execute 'arguments' once it is active

    // start a new session
    DBusClientCall1<DBusObject_t> call(*this, "StartSession");
    call(peer, boost::bind(&RemoteDBusServer::startSessionCb, this, _1, _2));

    // wait until 'StartSession' returns
    resetReplies();
    while(!done()) {
        g_main_loop_run(m_loop);
    }

    if(m_session) {
        m_session->setRunSync(true);

        //if session is not active, just wait
        while(!isActive()) {
            g_main_loop_run(m_loop);
        }
        // Logger::Level level = LoggerBase::instance().getLevel();
        // LoggerBase::instance().setLevel(Logger::DEBUG);
        resetReplies();
        m_session->executeAsync(args);

        while(!done()) {
            g_main_loop_run(m_loop);
        }

        //if encoutering errors, return
        if(!m_result) {
            return m_result;
        }

        //g_session is used to pass 'abort' or 'suspend' commands
        //make sure session is ready to run
        g_session = m_session;

        //set up signal handlers to send 'suspend' or 'abort' to dbus server
        //only do this once session is executing and can suspend and abort
        struct sigaction new_action, old_action;
        struct sigaction old_term_action;

        if(runSync) {
            memset(&new_action, 0, sizeof(new_action));
            new_action.sa_handler = handleSignal;
            sigemptyset(&new_action.sa_mask);
            sigaction(SIGINT, NULL, &old_action);
            if (old_action.sa_handler == SIG_DFL) {
                sigaction(SIGINT, &new_action, NULL);
            }

            sigaction(SIGTERM, NULL, &old_term_action);
            if (old_term_action.sa_handler == SIG_DFL) {
                sigaction(SIGTERM, &new_action, NULL);
            }   
        }

        //wait until status is 'done'
        while(!m_session->statusDone()) {
            g_main_loop_run(m_loop);
        }

        if(runSync) {
            sigaction (SIGINT, &old_action, NULL);
            sigaction (SIGTERM, &old_term_action, NULL);
        }

        //reset session
        g_session.reset();
        //restore logging level
        // LoggerBase::instance().setLevel(level);
        m_session->setRunSync(false);
    }
    return m_result;
}

void RemoteDBusServer::startSessionCb(const DBusObject_t &sessionPath, const string &error)
{
    replyInc();
    if(!error.empty()) {
        SE_LOG_ERROR(NULL, NULL, "starting D-Bus session failed: %s", error.c_str());
        m_result = false;
        g_main_loop_quit(m_loop);
        return;
    }
    m_session.reset(new RemoteSession(*this, sessionPath));
    g_main_loop_quit(m_loop);
}

bool RemoteDBusServer::isActive()
{
    /** if current session is active and then start to call 'Execute' method */
    if(m_session) {
        BOOST_FOREACH(const string &session, m_activeSessions) {
            if(boost::equals(m_session->getPath(), session.c_str())) {
                return true;
            }
        }
    }
    return false;
}

void RemoteDBusServer::getRunningSessions()
{
    //get all sessions
    DBusClientCall1<vector<string> > sessions(*this, "GetSessions");
    sessions(boost::bind(&RemoteDBusServer::getSessionsCb, this, _1, _2));
    resetReplies();
    while(!done()) {
        g_main_loop_run(m_loop);
    }

    // get status of each session
    resetReplies(m_sessions.size());
    BOOST_FOREACH(boost::shared_ptr<RemoteSession> &session, m_sessions) {
        session->getStatusAsync();
    }

    // waiting for all sessions 'GetStatus'
    while(!done()) {
        g_main_loop_run(m_loop);
    }

    // collect running sessions
    BOOST_FOREACH(boost::shared_ptr<RemoteSession> &session, m_sessions) {
        if(boost::istarts_with(session->status(), "running")) {
            m_runSessions.push_back(boost::weak_ptr<RemoteSession>(session));
        }
    }
}

bool RemoteDBusServer::runningSessions()
{
    //the basic working flow is:
    //1) get all sessions
    //2) check each session and collect running sessions
    //3) get config name of running sessions and print them
    getRunningSessions();

    if(m_runSessions.empty()) {
        SE_LOG_SHOW(NULL, NULL, "Background sync daemon is idle.");
    } else {
        SE_LOG_SHOW(NULL, NULL, "Running session(s): ");

        resetReplies(m_runSessions.size());
        BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
            boost::shared_ptr<RemoteSession> lock = session.lock();
            if(lock) {
                lock->getConfigAsync();
            }
        }

        //wait for 'GetConfig' returns
        while(!done()) {
            g_main_loop_run(m_loop);
        }

        // print all running sessions
        BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
            boost::shared_ptr<RemoteSession> lock = session.lock();
            if(!lock->configName().empty()) {
                SE_LOG_SHOW(NULL, NULL, "   %s (%s)", lock->configName().c_str(), lock->getPath());
            }
        }
    }
    return m_result;
}

void RemoteDBusServer::getSessionsCb(const vector<string> &sessions, const string &error)
{
    replyInc();
    if(!error.empty()) {
        SE_LOG_ERROR(NULL, NULL, "getting session failed: %s", error.c_str());
        m_result = false;
        g_main_loop_quit(m_loop);
        return;
    }

    //create local objects for sessions
    BOOST_FOREACH(const DBusObject_t &value, sessions) {
        boost::shared_ptr<RemoteSession> session(new RemoteSession(*this, value));
        m_sessions.push_back(session);
    }
}

void RemoteDBusServer::updateSessions(const string &session, bool active)
{
    if(active) {
        //add it into active list
        m_activeSessions.push_back(session);
    } else {
        //if inactive, remove it from active list
        for(vector<string>::iterator it = m_activeSessions.begin();
                it != m_activeSessions.end(); ++it) {
            if(boost::equals(session, *it)) {
                m_activeSessions.erase(it);
                break;
            }
        }
    }
}

void RemoteDBusServer::replyInc()
{
    // increase counter and check whether all replies are returned
    m_replyCounter++;
    if(done()) {
        g_main_loop_quit(m_loop);
    }
}

bool RemoteDBusServer::monitor(const string &peer)
{
    //the basic working flow is:
    //1) get all sessions
    //2) check each session and collect running sessions
    //3) peak one session with the given peer and monitor it
    getRunningSessions();
    if(peer.empty()) {
        //peak the first running sessions
        BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
            boost::shared_ptr<RemoteSession> lock = session.lock();
            if(lock) {
                m_session = lock;
                resetReplies();
                m_session->getConfigAsync();
                while(!done()) {
                    g_main_loop_run(m_loop);
                }
                m_session->monitorSync();
                return m_result;
            }
        }
        //if no running session
        SE_LOG_SHOW(NULL, NULL, "Background sync daemon is idle, no session available to be be monitored.");
    } else {
        string peerNorm = SyncConfig::normalizeConfigString(peer);

        // get config names of running sessions
        resetReplies(m_runSessions.size());
        BOOST_FOREACH(boost::weak_ptr<RemoteSession> &session, m_runSessions) {
            boost::shared_ptr<RemoteSession> lock = session.lock();
            lock->getConfigAsync();
        }
        //wait for 'GetConfig' returns
        while(!done()) {
            g_main_loop_run(m_loop);
        }

        //find a session with the given name
        vector<boost::shared_ptr<RemoteSession> >::iterator it = m_sessions.begin();
        while(it != m_sessions.end()) {
            string tempNorm = (*it)->configName();
            if (peerNorm == tempNorm) {
                m_session = *it;
                //monitor the session status
                m_session->monitorSync();
                return m_result;
            }
            it++;
        }
        SE_LOG_SHOW(NULL, NULL, "'%s' is not running.", peer.c_str());
    }
    return m_result;
}


/********************** RemoteSession implementation **************************/
RemoteSession::RemoteSession(RemoteDBusServer &server,
        const string &path)
    :m_server(server), m_output(false), m_path(path), m_runSync(false),
    m_statusChanged(*this, "StatusChanged")
{
    m_statusChanged.activate(boost::bind(&RemoteSession::statusChangedCb, this, _1, _2, _3));
}

void RemoteSession::executeAsync(const vector<string> &args)
{
    //start to print outputs
    m_output = true;
    map<string, string> vars;
    getEnvVars(vars);
    DBusClientCall0 call(*this, "Execute");
    call(args, vars, boost::bind(&RemoteSession::executeCb, this, _1));
}

void RemoteSession::executeCb(const string &error)
{
    m_server.replyInc();
    if(!error.empty()) {
        SE_LOG_ERROR(NULL, NULL, "running the command line inside the D-Bus server failed");
        m_server.setResult(false);
        //end to print outputs
        m_output = false;
        return;
    }
}

void RemoteSession::statusChangedCb(const string &status,
        uint32_t errorCode,
        const SourceStatuses_t &sourceStatus)
{
    m_status = status;

    if (errorCode) {
        m_server.setResult(false);
        g_main_loop_quit(m_server.getLoop());
    }

    if(status == "done") {
        //if session is done, quit the loop
        g_main_loop_quit(m_server.getLoop());
        m_output = false;
    }
}

void RemoteSession::getStatusAsync()
{
    DBusClientCall3<string, uint32_t, SourceStatuses_t> call(*this, "GetStatus");
    call(boost::bind(&RemoteSession::getStatusCb, this, _1, _2, _3, _4));
}

void RemoteSession::getStatusCb(const string &status,
        uint32_t errorCode,
        const SourceStatuses_t &sourceStatus,
        const string &error)
{
    m_server.replyInc();
    if(!error.empty()) {
        //ignore the error
        return;
    }
    m_status = status;
}

void RemoteSession::getConfigAsync()
{
    DBusClientCall1<Config_t> call(*this, "GetConfig");
    call(false, boost::bind(&RemoteSession::getConfigCb, this, _1, _2));
}

void RemoteSession::getConfigCb(const Config_t &config, const string &error)
{
    m_server.replyInc();
    if(!error.empty()) {
        //ignore the error
        return;
    }
    // set config name
    Config_t::const_iterator it = config.find("");
    if(it != config.end()) {
        StringMap global = it->second;
        StringMap::iterator git = global.find("configName");
        if(git != global.end()) {
            m_configName = git->second;
        }
    }
}

void RemoteSession::suspendAsync()
{
    DBusClientCall0 suspend(*this, "Suspend");
    suspend(boost::bind(&RemoteSession::suspendCb, this, _1));
}

void RemoteSession::suspendCb(const string &error)
{
    //avoid logging messages in handleSignal
    SyncContext::printSignals();
    if(!error.empty()) {
        m_server.setResult(false);
    }
}

void RemoteSession::abortCb(const string &error)
{
    //avoid logging messages in handleSignal
    SyncContext::printSignals();
    if(!error.empty()) {
        m_server.setResult(false);
    }
}

void RemoteSession::abortAsync()
{
    DBusClientCall0 abort(*this, "Abort");
    abort(boost::bind(&RemoteSession::abortCb, this, _1));
}

void RemoteSession::logOutput(Logger::Level level, const string &log)
{
    if(m_output) {
        SE_LOG(level, NULL, NULL, "%s", log.c_str());
    }
}

void RemoteSession::monitorSync()
{
    m_output = true;
    // Logger::Level level = LoggerBase::instance().getLevel();
    // LoggerBase::instance().setLevel(Logger::DEBUG);
    SE_LOG(Logger::SHOW, NULL, NULL, "Monitoring '%s' (%s)\n", m_configName.c_str(), getPath());

    while(!statusDone()) {
        g_main_loop_run(m_server.getLoop());
    }

    SE_LOG(Logger::SHOW, NULL, NULL, "Monitoring done");
    // LoggerBase::instance().setLevel(level);
    m_output = false;
}

void RemoteSession::infoReq(const string &id,
                            const DBusObject_t &session,
                            const string &state,
                            const string &handler,
                            const string &type,
                            const StringMap &params)
{
    //if command line runs a sync, then try to handle req
    if (m_runSync && boost::iequals(session, getPath())) {
        //only handle password now
        if (boost::iequals("password", type)) {
            map<string, boost::shared_ptr<InfoReq> >::iterator it = m_infoReqs.find(id);
            if (it != m_infoReqs.end()) {
                it->second->process(id, session, state, handler, type, params);
            } else {
                boost::shared_ptr<InfoReq> passwd(new InfoReq(*this, id, type));
                m_infoReqs[id] = passwd;
                passwd->process(id, session, state, handler, type, params);
            }
        } 
    }
}

void RemoteSession::handleInfoReq(const string &type, const StringMap &params, StringMap &resp)
{
    if (boost::iequals(type, "password")) {
        char buffer[256];

        string descr;
        StringMap::const_iterator it = params.find("description");
        if (it != params.end()) {
            descr = it->second;
        }
        printf("Enter password for %s: ", descr.c_str());
        fflush(stdout);
        if (fgets(buffer, sizeof(buffer), stdin) &&
                strcmp(buffer, "\n")) {
            size_t len = strlen(buffer);
            if (len && buffer[len - 1] == '\n') {
                buffer[len - 1] = 0;
            }
            resp["password"] = string(buffer);
        } else {
            SE_LOG_ERROR(NULL, NULL, "could not read password for %s", descr.c_str());
        }
    }
}

void RemoteSession::removeInfoReq(const string &id) 
{
    map<string, boost::shared_ptr<InfoReq> >::iterator it = m_infoReqs.find(id);
    if (it != m_infoReqs.end()) {
        m_infoReqs.erase(it);
    }
}

/********************** InfoReq implementation **************************/
RemoteSession::InfoReq::InfoReq(RemoteSession &session,
                                const string &id,
                                const string &type)
    :m_session(session), m_id(id), m_type(type), m_state(INIT)
{
}

void RemoteSession::InfoReq::process(const string &id,
                                     const DBusObject_t &session,
                                     const string &state,
                                     const string &handler,
                                     const string &type,
                                     const StringMap &params)
{
    //only handle info belongs to this InfoReq
    if (boost::equals(m_id, id)) {
        //check the state and response if necessary
        if (m_state == INIT && boost::iequals("request", state)) {
            m_session.getServer().infoResponse(m_id, "working", StringMap());
            m_state = WORKING;
            m_session.handleInfoReq(type, params, m_resp);
        } else if ((m_state == WORKING) && boost::iequals("waiting", state)) {
            m_session.getServer().infoResponse(m_id, "response", m_resp);
            m_state = RESPONSE;
        } else if (boost::iequals("done", state)) {
            //if request is 'done', remove it
            m_session.removeInfoReq(m_id);
        }
    }
}

void getEnvVars(map<string, string> &vars)
{
    //environment variables used to run command line
    static const char *varNames[] = {
        "http_proxy",
        "HOME",
        "PATH",
        "SYNCEVOLUTION_BACKEND_DIR",
        "SYNCEVOLUTION_DEBUG",
        "SYNCEVOLUTION_GNUTLS_DEBUG",
        "SYNCEVOLUTION_TEMPLATE_DIR",
        "SYNCEVOLUTION_XML_CONFIG_DIR",
        "SYNC_EVOLUTION_EVO_CALENDAR_DELAY",
        "XDG_CACHE_HOME",
        "XDG_CONFIG_HOME",
        "XDG_DATA_HOME"
    };

    for (unsigned int i = 0; i < sizeof(varNames) / sizeof(const char*); i++) {
        const char *value;
        //get values of environment variables if they are set
        if ((value = getenv(varNames[i])) != NULL) {
            vars.insert(make_pair(varNames[i], value));
        }
    }
}

#endif

SE_END_CXX