/*
 * Copyright (C) 2011 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 "read-operations.h"
#include "dbus-user-interface.h"
#include "server.h"
#include "dbus-sync.h"

#include <syncevo/IniConfigNode.h>

SE_BEGIN_CXX

ReadOperations::ReadOperations(const std::string &config_name, Server &server) :
    m_configName(config_name), m_server(server)
{}

void ReadOperations::getConfigs(bool getTemplates, std::vector<std::string> &configNames)
{
    if (getTemplates) {
        SyncConfig::DeviceList devices;

        // get device list from dbus server, currently only bluetooth devices
        m_server.getDeviceList(devices);

        // also include server templates in search
        devices.push_back(SyncConfig::DeviceDescription("", "", SyncConfig::MATCH_FOR_CLIENT_MODE));

        //clear existing templates in dbus server
        m_server.clearPeerTempls();

        SyncConfig::TemplateList list = SyncConfig::getPeerTemplates(devices);
        std::map<std::string, int> numbers;
        for (const auto &peer: list) {
            //if it is not a template for device
            if(peer->m_deviceName.empty()) {
                configNames.push_back(peer->m_templateId);
            } else {
                string templName = "Bluetooth_";
                templName += peer->m_deviceId;
                templName += "_";
                auto it = numbers.find(peer->m_deviceId);
                if(it == numbers.end()) {
                    numbers.insert(std::make_pair(peer->m_deviceId, 1));
                    templName += "1";
                } else {
                    it->second++;
                    stringstream seq;
                    seq << it->second;
                    templName += seq.str();
                }
                configNames.push_back(templName);
                m_server.addPeerTempl(templName, peer);
            }
        }
    } else {
        SyncConfig::ConfigList list = SyncConfig::getConfigs();
        for (const auto &server: list) {
            configNames.push_back(server.first);
        }
    }
}

std::shared_ptr<SyncConfig> ReadOperations::getLocalConfig(const string &configName, bool mustExist)
{
    string peer, context;
    SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configName),
                                  peer, context);

    auto syncConfig = std::make_shared<SyncConfig>(configName);

    /** if config was not set temporarily */
    if (!setFilters(*syncConfig)) {
        // the default configuration can always be opened for reading,
        // everything else must exist
        if ((context != "default" || peer != "") &&
            mustExist &&
            !syncConfig->exists()) {
            SE_THROW_EXCEPTION(NoSuchConfig, "No configuration '" + configName + "' found");
        }
    }
    return syncConfig;
}

void ReadOperations::getConfig(bool getTemplate,
                               Config_t &config)
{
    getNamedConfig(m_configName, getTemplate, config);
}

void ReadOperations::getNamedConfig(const std::string &configName,
                                    bool getTemplate,
                                    Config_t &config)
{
    map<string, string> localConfigs;
    std::shared_ptr<SyncConfig> dbusConfig;
    SyncConfig *syncConfig;
    string syncURL;
    /** get server template */
    if(getTemplate) {
        string peer, context;

        std::shared_ptr<SyncConfig::TemplateDescription> peerTemplate =
            m_server.getPeerTempl(configName);
        if(peerTemplate) {
            SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(peerTemplate->m_templateId),
                    peer, context);
            dbusConfig = SyncConfig::createPeerTemplate(peerTemplate->m_path);
            // if we have cached template information, add match information for it
            localConfigs.insert(pair<string, string>("description", peerTemplate->m_description));

            stringstream score;
            score << peerTemplate->m_rank;
            localConfigs.insert(pair<string, string>("score", score.str()));
            // Actually this fingerprint is transferred by getConfigs, which refers to device name
            localConfigs.insert(pair<string, string>("deviceName", peerTemplate->m_deviceName));
            // This is the reliable device info obtained from the bluetooth
            // device id profile (DIP) or emtpy if DIP not supported.
            if (!peerTemplate->m_hardwareName.empty()) {
                localConfigs.insert(pair<string, string>("hardwareName", peerTemplate->m_hardwareName));
            }
            // This is the fingerprint of the template
            localConfigs.insert(pair<string, string>("fingerPrint", peerTemplate->m_matchedModel));
            // This is the template name presented to UI (or device class)
            if (!peerTemplate->m_templateName.empty()) {
                localConfigs.insert(pair<string,string>("templateName", peerTemplate->m_templateName));
            }

            // if the peer is client, then replace syncURL with bluetooth
            // MAC address
            syncURL = "obex-bt://";
            syncURL += peerTemplate->m_deviceId;
        } else {
            SyncConfig::splitConfigString(SyncConfig::normalizeConfigString(configName),
                    peer, context);
            dbusConfig = SyncConfig::createPeerTemplate(peer);
        }

        if(!dbusConfig.get()) {
            SE_THROW_EXCEPTION(NoSuchConfig, "No template '" + configName + "' found");
        }

        // use the shared properties from the right context as filter
        // so that the returned template preserves existing properties
        std::shared_ptr<SyncConfig> shared = getLocalConfig(string("@") + context, false);

        ConfigProps props;
        shared->getProperties()->readProperties(props);
        dbusConfig->setConfigFilter(true, "", props);
        for (const std::string &source: shared->getSyncSources()) {
            SyncSourceNodes nodes = shared->getSyncSourceNodes(source, "");
            props.clear();
            nodes.getProperties()->readProperties(props);
            // Special case "type" property: the value in the context
            // is not preserved. Every new peer must ensure that
            // its own value is compatible (= same backend) with
            // the other peers.
            props.erase("type");
            dbusConfig->setConfigFilter(false, source, props);
        }
        syncConfig = dbusConfig.get();
    } else {
        dbusConfig = getLocalConfig(configName);
        DBusUserInterface ui(dbusConfig->getKeyring());
        //try to check password and read password from gnome keyring if possible
        PasswordConfigProperty::checkPasswords(ui, *dbusConfig,
                                               // Keep usernames as they are, but retrieve passwords for the D-Bus client.
                                               PasswordConfigProperty::CHECK_PASSWORD_ALL & ~PasswordConfigProperty::CHECK_PASSWORD_RESOLVE_USERNAME,
                                               dbusConfig->getSyncSources());
        syncConfig = dbusConfig.get();
    }

    /** get sync properties and their values */
    ConfigPropertyRegistry &syncRegistry = SyncConfig::getRegistry();
    for (const ConfigProperty *prop: syncRegistry) {
        InitStateString value = prop->getProperty(*syncConfig->getProperties());
        if (boost::iequals(prop->getMainName(), "syncURL") && !syncURL.empty() ) {
            localConfigs.insert(pair<string, string>(prop->getMainName(), syncURL));
        } else if (value.wasSet()) {
            localConfigs.insert(pair<string, string>(prop->getMainName(), value));
        }
    }

    // Set ConsumerReady for existing SyncEvolution < 1.2 configs
    // if not set explicitly,
    // because in older releases all existing configurations where
    // shown. SyncEvolution 1.2 is more strict and assumes that
    // ConsumerReady must be set explicitly. The sync-ui always has
    // set the flag for configs created or modified with it, but the
    // command line did not. Matches similar code in the Cmdline.cpp
    // migration code.
    //
    // This does not apply to templates which always have ConsumerReady
    // set explicitly (to on or off) or not set (same as off).
    if (!getTemplate &&
        syncConfig->getConfigVersion(CONFIG_LEVEL_PEER, CONFIG_CUR_VERSION) == 0 /* SyncEvolution < 1.2 */) {
        localConfigs.insert(make_pair("ConsumerReady", "1"));
    }

    // insert 'configName' of the chosen config (configName is not normalized)
    localConfigs.insert(pair<string, string>("configName", syncConfig->getConfigName()));

    config.insert(pair<string,map<string, string> >("", localConfigs));

    /* get configurations from sources */
    list<string> sources = syncConfig->getSyncSources();
    for (const string &name: sources) {
        localConfigs.clear();
        SyncSourceNodes sourceNodes = syncConfig->getSyncSourceNodes(name);
        ConfigPropertyRegistry &sourceRegistry = SyncSourceConfig::getRegistry();
        for (const ConfigProperty *prop: sourceRegistry) {
            InitStateString value = prop->getProperty(*sourceNodes.getProperties());
            if (value.wasSet()) {
                localConfigs.insert(pair<string, string>(prop->getMainName(), value));
            }
        }
        config.insert(pair<string, map<string, string> >( "source/" + name, localConfigs));
    }
}

void ReadOperations::getReports(uint32_t start, uint32_t count,
                                Reports_t &reports)
{
    SyncContext client(m_configName, false);
    std::vector<string> dirs;
    client.getSessions(dirs);

    uint32_t index = 0;
    // newest report firstly
    for( int i = dirs.size() - 1; i >= 0; --i) {
        /** if start plus count is bigger than actual size, then return actual - size reports */
        if(index >= start && index - start < count) {
            const string &dir = dirs[i];
            std::map<string, string> aReport;
            // insert a 'dir' as an ID for the current report
            aReport.insert(pair<string, string>("dir", dir));
            SyncReport report;
            // peerName is also extracted from the dir
            string peerName = client.readSessionInfo(dir,report);
            auto config = std::make_shared<SyncConfig>(m_configName);
            string storedPeerName = config->getPeerName();
            //if can't find peer name, use the peer name from the log dir
            if(!storedPeerName.empty()) {
                peerName = storedPeerName;
            }

            /** serialize report to ConfigProps and then copy them to reports */
            IniHashConfigNode node("/dev/null","",true);
            node << report;
            ConfigProps props;
            node.readProperties(props);

            for (const auto &entry: props) {
                aReport.insert(entry);
            }
            // a new key-value pair <"peer", [peer name]> is transferred
            aReport.insert(pair<string, string>("peer", peerName));
            reports.push_back(aReport);
        }
        index++;
    }
}

void ReadOperations::checkSource(const std::string &sourceName)
{
    auto config = std::make_shared<SyncConfig>(m_configName);
    setFilters(*config);

    list<std::string> sourceNames = config->getSyncSources();
    list<std::string>::iterator it;
    for(it = sourceNames.begin(); it != sourceNames.end(); ++it) {
        if(*it == sourceName) {
            break;
        }
    }
    if(it == sourceNames.end()) {
        SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' has no '" + sourceName + "' datastore");
    }
    bool checked = false;
    try {
        // this can already throw exceptions when the config is invalid
        SyncSourceParams params(sourceName, config->getSyncSourceNodes(sourceName), config);
        unique_ptr<SyncSource> syncSource(SyncSource::createSource(params, false, config.get()));

        if (syncSource.get()) {
            syncSource->open();
            // success!
            checked = true;
        }
    } catch (...) {
        Exception::handle();
    }

    if (!checked) {
        SE_THROW_EXCEPTION(SourceUnusable, "The datastore '" + sourceName + "' is not usable");
    }
}
void ReadOperations::getDatabases(const string &sourceName, SourceDatabases_t &databases)
{
    auto config = std::make_shared<SyncConfig>(m_configName);
    setFilters(*config);

    SyncSourceParams params(sourceName, config->getSyncSourceNodes(sourceName), config);
    const SourceRegistry &registry(SyncSource::getSourceRegistry());
    for (const RegisterSyncSource *sourceInfo: registry) {
        unique_ptr<SyncSource> source(sourceInfo->m_create(params));
        if (!source.get()) {
            continue;
        } else if (source->isInactive()) {
            SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' backend of datastore '" + sourceName + "' is not supported");
        } else {
            databases = source->getDatabases();
            return;
        }
    }

    SE_THROW_EXCEPTION(NoSuchSource, "'" + m_configName + "' has no '" + sourceName + "' datastore");
}

SE_END_CXX
