#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/compound_token.h>
#include <maps/wikimap/mapspro/libs/common/include/yandex/maps/wiki/common/string_utils.h>

#include <maps/libs/common/include/exception.h>

#include <boost/lexical_cast.hpp>
#include <set>

namespace maps::wiki::common {

namespace {

const std::string TOKEN_DELIMITER = ".";
const std::string ALIAS_DELIMITER = ":";

const std::set<std::string> NON_CORE_ALIASES = {
    TOKEN_DB_ALIAS_SOCIAL,
    TOKEN_DB_ALIAS_TRUNK,
    TOKEN_DB_ALIAS_STABLE,
    TOKEN_DB_ALIAS_TRUNK_LABELS,
    TOKEN_DB_ALIAS_STABLE_LABELS
};

bool isNumber(const std::string& str)
{
    try {
        if (!str.empty()) {
            boost::lexical_cast<int64_t>(str);
            return true;
        }
    } catch (...) {
        // suppress
    }
    return false;
}

} // namespace

void CompoundToken::append(const std::string& dbAlias, const std::string& token)
{
    REQUIRE(
        !order_.empty() || dbAlias == TOKEN_DB_ALIAS_CORE,
        "wrong first alias: " << dbAlias);

    if (alias2token_.insert({dbAlias, token}).second) {
        order_.push_back(dbAlias);
    }
}

std::string CompoundToken::str() const
{
    std::string result;
    for (const auto& alias : order_) {
        if (!result.empty()) {
            result += TOKEN_DELIMITER;
        }
        result += alias2token_.at(alias);
        result += ALIAS_DELIMITER;
        result += alias;
    }
    return result;
}

std::string CompoundToken::subToken(
    const std::string& compoundToken,
    const std::string& dbAlias)
{
    if (compoundToken.empty()) {
        return {};
    }

    // <timestamp1>:<id1>:<alias1>[... .<timestampN>:<idN>:<aliasN>]
    auto tokens = split(compoundToken, TOKEN_DELIMITER);
    if (tokens.empty()) {
        return {};
    }

    const auto aliasPostfix = ALIAS_DELIMITER + dbAlias;

    for (const auto& token : tokens) {
        if (token.find(aliasPostfix) != std::string::npos) {
            return token;
        }
    }
    return {};
}

bool CompoundToken::isValid(const std::string& compoundToken)
{
    if (compoundToken.empty()) {
        return true;
    }

    auto tokens = split(compoundToken, TOKEN_DELIMITER);
    if (tokens.empty()) {
        return false;
    }

    std::set<std::string> visitedAliases;

    bool first = true;
    for (const auto& token : tokens) {
        auto parts = split(token, ALIAS_DELIMITER);

        auto partsCount = parts.size();
        if (partsCount == 2) {
            // <timestamp1>:<id1>
            return tokens.size() == 1 && isNumber(parts[0]) && isNumber(parts[1]);
        }

        // <timestamp1>:<id1>:core[.<timestampK>:<idK>:<aliasK> ...]
        if (partsCount != 3) {
            return false;
        }

        const auto& alias = parts[2];

        bool isAliasValid = first
            ? alias == TOKEN_DB_ALIAS_CORE
            : NON_CORE_ALIASES.count(alias) > 0;
        if (!isAliasValid) {
            return false;
        }
        first = false;

        if (!visitedAliases.emplace(alias).second) {
            return false;
        }

        if (!isNumber(parts[0]) || !isNumber(parts[1])) {
            return false;
        }
    }
    return true;
}

} // namespace maps::wiki::common
