#include "dbwrapper.h"
#include <maps/libs/common/include/exception.h>
#include <yandex/maps/wiki/common/compound_token.h>
#include <maps/libs/pgpool/include/pool_configuration.h>
#include <maps/libs/pgpool/include/xml_config.h>
#include <maps/libs/log8/include/log8.h>

#include <boost/algorithm/string.hpp>

namespace p3 = maps::pgpool3;

namespace maps {
namespace wiki {
namespace renderer {

namespace {

const std::string DELIMITER = ",";
const std::string CONFIG_ALIAS = "alias";
const std::string CONFIG_SCOPE = "scope";

std::map<std::string, Scope>
makeValidScopesMap()
{
    std::map<std::string, Scope> result;
    for (auto scope : {Scope::Objects, Scope::Labels, Scope::All}) {
        result.emplace(toString(scope), scope);
    }
    return result;
}

const auto VALID_SCOPES_MAP = makeValidScopesMap();

std::set<Scope>
parseScope(const std::string& scopeStr)
{
    std::vector<std::string> splittedScopes;
    boost::split(splittedScopes, scopeStr, boost::is_any_of(DELIMITER));

    std::set<Scope> scopes;
    for (const auto& str : splittedScopes) {
        if (str.empty()) {
            continue;
        }
        auto it = VALID_SCOPES_MAP.find(str);
        REQUIRE(it != VALID_SCOPES_MAP.end(),
                "Invalid scope: " << str << " in " << scopeStr);
        auto scope = it->second;
        REQUIRE(scopes.emplace(scope).second,
                "Dublicated scope: " << str << " in " << scopeStr);
    }

    if (scopes.empty()) {
        scopes.emplace(Scope::All);
    } else if (scopes.size() > 1) {
        REQUIRE(scopes.size() == 2 && !scopes.count(Scope::All),
                "Invalid scope: " << scopeStr);
    }
    return scopes;
}

} // namespace

DbWrapper::DbWrapper(const xml3::Nodes& nodes, const std::string& layerName)
{
    auto size = nodes.size();
    REQUIRE(size, "can not find a database section");

    //TODO: make parallel pool initializaion

    for (size_t i = 0; i < size; ++i) {
        const auto& dbNode = nodes[i];
        auto alias = dbNode.attr<std::string>(CONFIG_ALIAS, "");
        auto scopeStr = dbNode.attr<std::string>(CONFIG_SCOPE, "");

        for (auto scope : parseScope(scopeStr)) {
            auto layerFullName = alias.empty()
                ? layerName
                : layerName + DELIMITER + alias;
            layerFullName += DELIMITER + toString(scope);

            auto holder = std::make_unique<pgp3utils::DynamicPoolHolder>(
                dbNode,
                "", // poolId
                "pgpool3." + layerFullName);

            auto& scope2data = alias2data_[alias];
            auto res = scope2data.emplace(scope, std::move(holder));
            REQUIRE(res.second, "duplicated db section for " << layerFullName);
        }
    }
}

DbWrapper::~DbWrapper()
{
}

bool
DbWrapper::isSplitDb() const
{
    if (alias2data_.size() > 1) {
        return true;
    }

    ASSERT(alias2data_.size() == 1);
    const auto& alias = alias2data_.begin()->first;
    return !alias.empty();
}

bool
DbWrapper::isBranchAllowed(TBranchId branchId) const
{
    if (!isSplitDb()) {
        return true;
    }

    auto alias = common::CompoundToken::alias(branchId);
    return alias2data_.count(alias) > 0;
}

std::set<Scope>
DbWrapper::allowedScopes(TBranchId branchId) const
{
    std::set<Scope> result;
    if (isSplitDb()) {
        auto alias = common::CompoundToken::alias(branchId);
        auto it = alias2data_.find(alias);
        if (it != alias2data_.end()) {
            for (const auto& [scope, holder] : it->second) {
                result.emplace(scope);
            }
        }
    } else {
        const auto& scope2data = alias2data_.begin()->second;
        for (const auto& [scope, holder] : scope2data) {
            result.emplace(scope);
        }
    }
    return result;
}

p3::ConnectionHandle DbWrapper::getConnection(
    const std::string& token, const TBranchId branchId, Scope scope)
{
    if (!isSplitDb()) {
        auto& scope2data = alias2data_.begin()->second;
        auto it = scope2data.find(scope);
        REQUIRE(it != scope2data.end(),
                "can not find db section with scope: " << scope);
        auto& data = it->second;
        return data->pool().getSlaveConnection(token);
    }

    auto alias = common::CompoundToken::alias(branchId);

    auto itA = alias2data_.find(alias);
    REQUIRE(itA != alias2data_.end(),
            "can not find db section with alias: " << alias);

    auto& scope2data = itA->second;
    auto it = scope2data.find(scope);
    REQUIRE(it != scope2data.end(),
            "can not find db section with alias: " << alias <<
            " scope: " << scope);

    if (scope == Scope::Labels) {
        alias = common::CompoundToken::aliasLabels(branchId);
    }
    auto subtoken = common::CompoundToken::subToken(token, alias);
    auto& data = it->second;
    return data->pool().getSlaveConnection(subtoken);
}

} // namespace renderer
} // namespace wiki
} // namespace maps
