#include "json.h"

#include <saas/util/json/json.h>

#include <library/cpp/json/json_reader.h>

#include <util/generic/algorithm.h>
#include <util/stream/file.h>
#include <util/stream/output.h>

using namespace NJson;

namespace {
    template <class T>
    inline void InsertToJsonRequired(TJsonValue& json, const TString& name, const T& value)
    {
        if (!value)
            throw yexception() << "missing required field " << name;
        json.InsertValue(name, value);
    }
    template <class T>
    inline void InsertToJson(TJsonValue& json, const TString& name, const T& value)
    {
        if (value)
            json.InsertValue(name, value);
    }
    template <class T>
    inline void InsertToJson(TJsonValue& json, const TString& name, const T& value, const T& ignoredValue)
    {
        if (value != ignoredValue)
            json.InsertValue(name, value);
    }
    bool CompareHosts(const NSearchMapParser::TSearchMapHost& i, const NSearchMapParser::TSearchMapHost& j) {
        return i.Shards < j.Shards;
    }

    const TString SearchPortLable     = "search_port";
    const TString IndexerPortLable    = "indexer_port";
    const TString GroupLable          = "group";
    const TString IndexAttachesLable  = "index_attaches";
    const TString RequireAuthLable    = "require_auth";
    const TString SearchProtocolLable = "search_protocol";
    const TString ShardByLable        = "shard_by";
    const TString IndexingTargetLable = "dispatch_target";
    const TString HostLable           = "host";
    const TString ShardMinLable       = "shard_min";
    const TString ShardMaxLable       = "shard_max";
    const TString RankLable           = "rank";
    const TString ReplicasLable       = "replicas";
    const TString StreamLable         = "stream";
    const TString CustomConfigLable   = "config";
    const TString DisableSearchLable  = "disable_search";
    const TString DisableIndexingLable= "disable_indexing";
    const TString IndexReplyIgnoringLable = "reply_index_ignoring";
    const TString DisableSearchFiltrationLable="disable_search_filtration";
    const TString DisableFetchLable   = "disable_fetch";
    const TString IsServiceDiscoveryLable = "is_sd";
    const TString HostsLable          = "hosts";
    const TString UpdateFreqLable     = "update_freq";
    const TString VersionLable        = "version";
    const TString PropertiesLable     = "properties";
    const TString ServicesLable       = "services";
    const TString DistribitorsLable   = "distributors";
    const TString FeaturesLable       = "features";
    const TString MetaServicesLable   = "meta";
    const TString MetaComponentsLable = "components";
    const TString SearchPriorityLable = "priority";
    const TString ServiceNameLable    = "service";
    const TString PerDcSearchLable    = "per_dc_search";
    const TString ShardsLable         = "shards";
    const TString LocationsWeights    = "locations_weights";
    const TString ServersLable        = "servers";
    const TString IdentLable          = "ident";
    const TString NumTopicsLable      = "num_topics";
    const TString SkipPingLable       = "skip_ping";
}

#define REQUIRED(name, value) InsertToJsonRequired(result, name, value)
#define REQUIRED_NULLABLE(name, value) result.InsertValue(name, value)
#define OPTIONAL(name, value) if (!VerboseSerialization) InsertToJson(result, name, value); else result.InsertValue(name, value)
#define CHECKED(name, value, ignore) if (!VerboseSerialization) InsertToJson(result, name, value, ignore); else result.InsertValue(name, value)
#define CHECKED_ENUM(name, value, ignore) if (!VerboseSerialization) InsertToJson(result, name, ToString(value), ToString(ignore)); else result.InsertValue(name, ToString(value))

namespace NSearchMapParser {
    TSearchMapHostParser::TSearchMapHostParser(const NJson::TJsonValue& host, const TParsingSettings& settings)
        : TSearchMapCommonParser<TSearchMapHostParser>(settings)
        , Host(*this, HostLable, Default<TString>())
        , ShardMin(*this, ShardMinLable, TInterval<TShardIndex>(0, settings.ShardsNumber), 0)
        , ShardMax(*this, ShardMaxLable, TInterval<TShardIndex>(0, settings.ShardsNumber), settings.ShardsNumber)
        , Rank(*this, RankLable, Master)
        , SearchPort(*this, SearchPortLable, 0)
        , IndexerPort(*this, IndexerPortLable, [this]() {
            const ui16 searchPort = SearchPort;
            return searchPort ? searchPort + DefaultIndexerPortShift : 0;
        })
        , Group(*this, GroupLable, "")
        , DisableSearch(*this, DisableSearchLable, false)
        , DisableIndexing(*this, DisableIndexingLable, false)
        , DisableSearchFiltration(*this, DisableSearchFiltrationLable, false)
        , DisableFetch(*this, DisableFetchLable, false)
        , IsSd(*this, IsServiceDiscoveryLable, false)
    {
        ParseParameters(host);
    }

    TSearchMapReplicaParser::TSearchMapReplicaParser(const TString& alias, const NJson::TJsonValue& replica, const TParsingSettings& settings)
        : TSearchMapCommonParser<TSearchMapReplicaParser>(settings)
        , Alias(alias)
        , CustomConfig(*this, CustomConfigLable, Default<TString>())
        , Features(*this, FeaturesLable, Default<TString>())
        , UpdateFrequency(*this, UpdateFreqLable, 0)
        , DisableSearch(*this, DisableSearchLable, false)
        , DisableIndexing(*this, DisableIndexingLable, false)
    {
        if (replica.IsArray()) {
            ParseJsonArray(replica, Hosts);
        } else if (replica.IsMap()) {
            ParseParameters(replica);
            ParseJsonArray(replica[HostsLable], Hosts);
        } else if (!settings.IgnoreParsingErrors){
            ythrow yexception() << "can't parse replica: " << replica.GetStringRobust();
        }
    }

    TSearchMapMetaComponentParser::TSearchMapMetaComponentParser(const NJson::TJsonValue& component, const TParsingSettings& settings)
        : TSearchMapCommonParser<TSearchMapMetaComponentParser>(settings)
        , ServiceName(*this, ServiceNameLable)
        , SearchPriority(*this, SearchPriorityLable)
        , DisableSearch(*this, DisableSearchLable, false)
        , DisableIndexing(*this, DisableIndexingLable, false)
        , IndexReplyIgnoring(*this, IndexReplyIgnoringLable, false)
    {
        ParseParameters(component);
    }

    TSearchMapCommonServiceParser::TSearchMapCommonServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings)
        : TSearchMapCommonParser<TSearchMapCommonServiceParser>(settings)
        , Name(name)
        , SearchPort(*this, SearchPortLable, 0)
        , IndexerPort(*this, IndexerPortLable, [this]() {
            const ui16 searchPort = SearchPort;
            return searchPort ? searchPort + DefaultIndexerPortShift : 0;
        })
        , IndexAttaches(*this, IndexAttachesLable, false)
        , RequireAuth(*this, RequireAuthLable, false)
        , SearchProtocol(*this, SearchProtocolLable, "proto")
        , ShardBy(*this, ShardByLable, ::ToString(NSaas::KeyPrefix))
        , IndexingTarget(*this, IndexingTargetLable, Backends)
        , Servers(*this, ServersLable, Default<TString>(), DistribitorsLable)
        , Stream(*this, StreamLable, Default<TString>(), IdentLable)
        , PerDcSearch(*this, PerDcSearchLable, false)
        , Shards(*this, ShardsLable, NSaas::ShardsCount::Legacy)
        , NumTopics(*this, NumTopicsLable, 0)
        , SkipPing(*this, SkipPingLable, false)
    {
        ParseParameters(service);
        for (const auto& from : service[LocationsWeights].GetMap()) {
            for (const auto& to : from.second.GetMap()) {
                WeightsByAlias[from.first][to.first] = to.second.GetUInteger();
            }
        }
    }

    TSearchMapServiceParser::TSearchMapServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings)
        : TSearchMapCommonServiceParser(name, service, settings)
    {
        if (!service.Has(ReplicasLable) && !ParsingSettings.IgnoreParsingErrors)
            throw yexception() << "service " << name << " has no replicas";
        ParsingSettings.ShardsNumber = NSaas::GetShards(Shards);
        ParseJsonMap(service[ReplicasLable], Replicas);
    }

    TSearchMapMetaServiceParser::TSearchMapMetaServiceParser(const TString& name, const NJson::TJsonValue& service, const TParsingSettings& settings)
        : TSearchMapCommonServiceParser(name, service, settings)
    {
        if (!service.Has(MetaComponentsLable) && !ParsingSettings.IgnoreParsingErrors)
            throw yexception() << "meta service " << name << " has no components";
        ParsingSettings.ShardsNumber = NSaas::GetShards(Shards);
        ParseJsonArray(service[MetaComponentsLable], Components);
    }

    TSearchMapPropertiesParser::TSearchMapPropertiesParser(const NJson::TJsonValue& properties, const TParsingSettings& parsingSettings)
        : TSearchMapCommonParser<TSearchMapPropertiesParser>(parsingSettings)
        , Version(*this, VersionLable, 0)
    {
        ParseParameters(properties);
    }

    NSearchMapParser::TSearchMapProperties TSearchMapPropertiesParser::Get() const {
        TSearchMapProperties result;
        result.Version = Version;
        return result;
    }

    TSearchMapHost TSearchMapHostParser::Get() const {
        TSearchMapHost host(Host, TInterval<TShardIndex>(ShardMin, ShardMax), Rank, SearchPort, IndexerPort);
        host.DisableIndexing = DisableIndexing;
        host.DisableSearch = DisableSearch;
        host.DisableSearchFiltration = DisableSearchFiltration;
        host.DisableFetch = DisableFetch;
        host.Group = Group;
        host.IsSd = IsSd;
        return host;
    }

    TSearchMapReplica TSearchMapReplicaParser::Get() const {
        TSearchMapReplica replica;
        replica.Alias = Alias;
        replica.Features = Features;
        replica.DisableSearch = DisableSearch;
        replica.DisableIndexing = DisableIndexing;
        replica.UpdateFrequency = TDuration::Seconds((ui32)UpdateFrequency);

        for (TSubParsers<TSearchMapHostParser>::const_iterator i = Hosts.begin(); i != Hosts.end(); ++i)
            replica.Hosts.push_back((**i).Get());
        Sort(replica.Hosts.begin(), replica.Hosts.end(), CompareHosts);

        return replica;
    }

    NSearchMapParser::TMetaComponent TSearchMapMetaComponentParser::Get() const {
        TMetaComponent component;
        component.ServiceName = ServiceName;
        component.SearchPriority = SearchPriority;
        component.SearchDisabled = DisableSearch;
        component.IndexDisabled = DisableIndexing;
        component.IndexReplyIgnoring = IndexReplyIgnoring;
        return component;
    }

    void TSearchMapCommonServiceParser::FillCommonService(TServiceSpecificOptions& service) const {
        auto shardingContext = NSaas::TShardsDispatcher::TContext::FromString(ShardBy);
        shardingContext.Shards = Shards;

        service.RequireAuth = RequireAuth;
        service.SearchProtocol = SearchProtocol;
        service.ShardsDispatcher.Reset(new NSaas::TShardsDispatcher(shardingContext));
        service.IndexingTarget = IndexingTarget;
        service.SetServers(Servers);
        service.Stream = Stream;
        service.NumTopics = NumTopics;
        service.PerDcSearch = PerDcSearch;
        service.SkipPing = SkipPing;
        service.WeightsByAliasFrom = WeightsByAlias;
    }

    TSearchMapService TSearchMapServiceParser::Get() const {
        TSearchMapService service;
        service.Name = Name;
        service.SearchPort = SearchPort;
        service.IndexerPort = IndexerPort;
        FillCommonService(service);
        for (TSubParsers<TSearchMapReplicaParser>::const_iterator i = Replicas.begin(); i != Replicas.end(); ++i)
            service.Replicas.push_back((**i).Get());
        return service;
    }

    NSearchMapParser::TMetaService TSearchMapMetaServiceParser::Get() const {
        TMetaService service;
        service.Name = Name;
        FillCommonService(service);
        for (auto&& component : Components)
            service.Components.push_back(component->Get());
        return service;
    }

    void TJsonSearchMapParser::ParseSearchMap(const TString& searchMap) {
        if (!searchMap) {
            InternalSearchMap.clear();
        } else {
            TStringInput si(searchMap);
            ParseSearchMap(si);
        }
    }

    void TJsonSearchMapParser::ParseSearchMap(IInputStream& stream) {
        TJsonValue searchMap;
        if (!ReadJsonTree(&stream, &searchMap))
            throw yexception() << "cannot parse json tree";

        ParseSearchMap(searchMap);
    }

    void TJsonSearchMapParser::OpenSearchMap(IInputStream& stream) {
        Y_ENSURE(Services.empty() && MetaServices.empty(), "already opened");
        ParseSearchMap(stream);
    }

    void TJsonSearchMapParser::ParseSearchMap(const NJson::TJsonValue& searchMap) {
        const bool hasServices = searchMap.Has(ServicesLable);
        const bool hasMetaServices = searchMap.Has(MetaServicesLable);

        auto filterCond = [&filter = ParsingSettings.FilterServices](const TString& sectionName) {
            return filter.empty() || filter.contains(sectionName);
        };

        if (hasServices || hasMetaServices) {
            if (hasServices)
                ParseJsonMapIf(searchMap[ServicesLable], Services, filterCond);
            if (hasMetaServices)
                ParseJsonMapIf(searchMap[MetaServicesLable], MetaServices, filterCond);
            Properties = TSearchMapPropertiesParser(searchMap[PropertiesLable], ParsingSettings).Get();
        } else {
            ParseJsonMapIf(searchMap, Services, filterCond);
            Properties.Version = 0;
        }

        for (TSubParsers<TSearchMapServiceParser>::const_iterator serviceIter = Services.begin(); serviceIter != Services.end(); ++serviceIter)
            InternalSearchMap.push_back((**serviceIter).Get());

        for (auto&& metaService : MetaServices)
            InternalMetaSearchMap.push_back(metaService->Get());

        UpdateVersion();
    }

    TJsonSearchMapParser::TJsonSearchMapParser(const TParsingSettings& settings /* = DefaultParsingSettings */)
        : TSearchMapCommonParser<TJsonSearchMapParser>(settings)
        , VerboseSerialization(false)
    {}

    TJsonSearchMapParser::TJsonSearchMapParser(const TSearchMap& searchMap)
        : TSearchMapCommonParser<TJsonSearchMapParser>(DefaultParsingSettings)
        , VerboseSerialization(false)
        , InternalSearchMap(searchMap.GetInternalSearchMap())
        , InternalMetaSearchMap(searchMap.GetMetaServices())
    {}

    TJsonSearchMapParser::TJsonSearchMapParser(const TString& fileName)
        : TSearchMapCommonParser<TJsonSearchMapParser>(DefaultParsingSettings)
        , VerboseSerialization(false)
    {
        try {
            TIFStream searchMap(fileName);
            ParseSearchMap(searchMap);
        } catch (const yexception& exc) {
            throw yexception() << "Failed to parse file: " << fileName.Quote() << ": " << exc.what();
        }
    }

    TJsonSearchMapParser::TJsonSearchMapParser(IInputStream& stream)
        : TSearchMapCommonParser<TJsonSearchMapParser>(DefaultParsingSettings)
        , VerboseSerialization(false)
    {
        ParseSearchMap(stream);
    }

    TJsonSearchMapParser::TJsonSearchMapParser(const NJson::TJsonValue& value)
        : TSearchMapCommonParser<TJsonSearchMapParser>(DefaultParsingSettings)
        , VerboseSerialization(false)
    {
        ParseSearchMap(value);
    }

    TSearchMap TJsonSearchMapParser::GetSearchMap(bool checkIndex, bool checkSearch) const {
        TSearchMap result(InternalSearchMap, InternalMetaSearchMap);
        result.Compile(checkIndex, checkSearch);
        return result;
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeHost(const TSearchMapHost& host, bool VerboseSerialization)
    {
        NJson::TJsonValue result;
        REQUIRED(HostLable, host.Name);
        REQUIRED_NULLABLE(ShardMaxLable, host.Shards.GetMax());
        OPTIONAL(ShardMinLable, host.Shards.GetMin());
        OPTIONAL(GroupLable, host.Group);
        CHECKED_ENUM(RankLable, host.Rank, Master);
        REQUIRED(IndexerPortLable, host.IndexerPort);
        REQUIRED(SearchPortLable, host.SearchPort);
        CHECKED(DisableSearchLable, host.DisableSearch, false);
        CHECKED(DisableIndexingLable, host.DisableIndexing, false);
        CHECKED(DisableSearchFiltrationLable, host.DisableSearchFiltration, false);
        CHECKED(DisableFetchLable, host.DisableFetch, false);
        CHECKED(IsServiceDiscoveryLable, host.IsSd, false);
        return result;
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeReplica(const NSearchMapParser::TSearchMapReplica& replica, bool VerboseSerialization) {
        TJsonValue hosts(NJson::JSON_ARRAY);
        for (TVector<TSearchMapHost>::const_iterator i = replica.Hosts.begin(); i != replica.Hosts.end(); ++i) {
            hosts.AppendValue(SerializeHost(*i, VerboseSerialization));
        }
        for (TVector<TSearchMapHost>::const_iterator i = replica.EndPointSets.begin(); i != replica.EndPointSets.end(); ++i) {
            hosts.AppendValue(SerializeHost(*i, VerboseSerialization));
        }

        if (replica.IsDefault()) {
            return hosts;
        } else {
            TJsonValue result(NJson::JSON_MAP);
            CHECKED(DisableSearchLable, replica.DisableSearch, false);
            CHECKED(DisableIndexingLable, replica.DisableIndexing, false);
            CHECKED(FeaturesLable, replica.Features, Default<TString>());
            OPTIONAL(UpdateFreqLable, replica.UpdateFrequency.Seconds());
            result.InsertValue(HostsLable, hosts);
            return result;
        }
    }

    void TJsonSearchMapParser::SerializeCommonService(NJson::TJsonValue& result, const TServiceSpecificOptions& service, bool VerboseSerialization /*= false*/) {
        CHECKED(SearchProtocolLable, service.SearchProtocol, TString("proto"));
        CHECKED(ShardByLable, service.ShardsDispatcher->GetContext().ToString(), Default<TString>());
        CHECKED_ENUM(ShardsLable, service.ShardsDispatcher->GetContext().Shards, NSaas::ShardsCount::Legacy);
        OPTIONAL(RequireAuthLable, service.RequireAuth);
        CHECKED_ENUM(IndexingTargetLable, service.IndexingTarget, Backends);
        CHECKED(DistribitorsLable, service.GetDistributors(), Default<TString>());
        CHECKED(StreamLable, service.Stream, Default<TString>());
        OPTIONAL(NumTopicsLable, service.NumTopics);
        OPTIONAL(PerDcSearchLable, service.PerDcSearch);
        OPTIONAL(SkipPingLable, service.SkipPing);
        for (const auto& from : service.WeightsByAliasFrom) {
            for (const auto& to : from.second) {
                result[LocationsWeights][from.first][to.first] = to.second;
            }
        }
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeService(const NSearchMapParser::TSearchMapService& service, bool VerboseSerialization) {
        TJsonValue result(NJson::JSON_MAP);
        SerializeCommonService(result, service, VerboseSerialization);
        TJsonValue replicas(NJson::JSON_MAP);
        for (TVector<TSearchMapReplica>::const_iterator i = service.Replicas.begin(); i != service.Replicas.end(); ++i) {
            TVector<TString> hosts; //todo: unused var?
            for (TVector<TSearchMapHost>::const_iterator j = i->Hosts.begin(); j != i->Hosts.end(); ++j)
                hosts.push_back(j->Name);
            replicas.InsertValue(i->Alias, SerializeReplica(*i, VerboseSerialization));
        }
        result.InsertValue(ReplicasLable, replicas);
        return result;
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeMetaService(const TMetaService& service, bool VerboseSerialization) {
        TJsonValue result(NJson::JSON_MAP);
        TJsonValue components(NJson::JSON_ARRAY);
        SerializeCommonService(result, service, VerboseSerialization);
        for (auto&& component: service.Components) {
            components.AppendValue(SerializeMetaComponent(component, VerboseSerialization));
        }
        result.InsertValue(MetaComponentsLable, components);
        return result;
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeMetaComponent(const TMetaComponent& component, bool VerboseSerialization /*= false*/) {
        NJson::TJsonValue result;
        REQUIRED(ServiceNameLable, component.ServiceName);
        CHECKED(SearchPriorityLable, component.SearchPriority, (ui32)-1);
        CHECKED(DisableSearchLable, component.SearchDisabled, false);
        CHECKED(DisableIndexingLable, component.IndexDisabled, false);
        CHECKED(IndexReplyIgnoringLable, component.IndexReplyIgnoring, false);
        return result;
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeProperties(const TSearchMapProperties& properties, bool /*VerboseSerialization*/) {
        TJsonValue result(NJson::JSON_MAP);
        REQUIRED(VersionLable, properties.Version);
        return result;
    }

    TString TJsonSearchMapParser::SerializeToString() const {
        NJson::TJsonValue resultJson = TJsonSearchMapParser::SerializeToJson();
        return NJson::WriteJson(resultJson);
    }

    NJson::TJsonValue TJsonSearchMapParser::SerializeToJson() const {
        TJsonValue searchMap(NJson::JSON_MAP);

        TJsonValue services(NJson::JSON_MAP);
        for (auto& service : InternalSearchMap) {
            services.InsertValue(service.Name, SerializeService(service, VerboseSerialization));
        }
        searchMap.InsertValue(ServicesLable, services);

        TJsonValue metaServices(NJson::JSON_MAP);
        for (auto& metaService : InternalMetaSearchMap) {
            metaServices.InsertValue(metaService.Name, SerializeMetaService(metaService, VerboseSerialization));
        }
        searchMap.InsertValue(MetaServicesLable, metaServices);

        searchMap.InsertValue(PropertiesLable, SerializeProperties(Properties, VerboseSerialization));
        return searchMap;
    }

    bool TJsonSearchMapParser::VerifySearchMap(TString& error, bool checkIndex, bool checkSearch) const {
        try {
            GetSearchMap(checkIndex, checkSearch);
            return true;
        } catch (const yexception& e) {
            error = e.what();
            return false;
        }
    }

    void TJsonSearchMapParser::ExpandSearchMap(TShardIndex shardsNumber) {
        if (shardsNumber < ParsingSettings.ShardsNumber)
            ythrow yexception() << "requested shards number is less than the current number";

        ParsingSettings.ShardsNumber = shardsNumber;
    }

    void TJsonSearchMapParser::RescaleSearchMap(TShardIndex shardsNumber) {
        if (!shardsNumber)
            ythrow yexception() << "incorrect argument";

        const double scaleFactor = (shardsNumber + 1.0) / (ParsingSettings.ShardsNumber + 1.0);

        for (auto& service : InternalSearchMap)
        for (auto& replica : service.Replicas)
        {
            Sort(replica.Hosts.begin(), replica.Hosts.end(), CompareHosts);
            for (ui64 i = 0; i < replica.Hosts.size() - 1; ++i) {
                TInterval<TShardIndex>& curShards = replica.Hosts[i].Shards;
                TInterval<TShardIndex>& nextShards = replica.Hosts[i + 1].Shards;
                if (curShards.GetLength() * scaleFactor < 1)
                    ythrow yexception() << "interval " << replica.Hosts[i].Shards.ToString() << " of service "
                                        << service.Name << " is too small";

                const int gap = nextShards.GetMin() - curShards.GetMax();
                if (gap != 1 && Max(abs(gap) - 1, 0) * scaleFactor < 1)
                    ythrow yexception() << "gap " << nextShards.GetMin() << " - " << curShards.GetMax()
                                        << " of service " << service.Name << " is too small or incorrect";

                const TShardIndex border = floor(curShards.GetMax() * scaleFactor);
                curShards.SetMax(border);
                if (gap == 1)
                    nextShards.SetMin(border + 1);
                else
                    nextShards.SetMin(floor(nextShards.GetMin() * scaleFactor));
            }

            // fix front
            const TShardIndex front = replica.Hosts.front().Shards.GetMin();
            if (front != 0) {
                if (front * scaleFactor < 1)
                    ythrow yexception() << "too small gap in front";

                replica.Hosts.front().Shards.SetMin(floor(scaleFactor * front));
            }

            // fix tail
            const TShardIndex tail = replica.Hosts.back().Shards.GetMax();
            TShardIndex newTail = shardsNumber;
            if (tail != ParsingSettings.ShardsNumber) {
                if ((ParsingSettings.ShardsNumber - tail) * scaleFactor < 1)
                    ythrow yexception() << "too small gap at tail";

                newTail = ceil(scaleFactor * tail);
            }
            replica.Hosts.back().Shards.SetMax(newTail);
        }

        ParsingSettings.ShardsNumber = shardsNumber;
    }

    void TJsonSearchMapParser::UpdateVersion() {
        if (Properties.Version < 1) {
            MergeReplicas();
            MergePorts();
        }
        Properties.Version = CurrentJsonFormatVersion;
    }

    void TJsonSearchMapParser::MergeReplicas() {
        for (auto& service : InternalSearchMap) {
            TMap<TString, TSearchMapReplica> newReplicas;
            for (auto& oldReplica : service.Replicas) {
                const TString newName = "default";
                const bool merging = newReplicas.contains(newName);
                TSearchMapReplica& targetReplica = newReplicas[newName];

                if (!merging) {
                    targetReplica.Alias = newName;
                    targetReplica.DisableIndexing = oldReplica.DisableIndexing;
                    targetReplica.DisableSearch = oldReplica.DisableSearch;
                    targetReplica.UpdateFrequency = oldReplica.UpdateFrequency;
                }

                if (merging && targetReplica.DisableIndexing && !oldReplica.DisableIndexing) {
                    targetReplica.DisableIndexing = false;
                    for (TVector<TSearchMapHost>::iterator host = targetReplica.Hosts.begin(); host != targetReplica.Hosts.end(); ++host)
                        host->DisableIndexing = true;
                }
                if (merging && targetReplica.DisableSearch && !oldReplica.DisableSearch) {
                    targetReplica.DisableSearch = false;
                    for (TVector<TSearchMapHost>::iterator host = targetReplica.Hosts.begin(); host != targetReplica.Hosts.end(); ++host)
                        host->DisableSearch = true;
                }

                for (TVector<TSearchMapHost>::const_iterator host = oldReplica.Hosts.begin(); host != oldReplica.Hosts.end(); ++host) {
                    TSearchMapHost oldHost = *host;

                    if (merging && !targetReplica.DisableSearch && (oldHost.DisableSearch || oldReplica.DisableSearch))
                        oldHost.DisableSearch = true;
                    if (merging && !targetReplica.DisableIndexing && (oldHost.DisableIndexing || oldReplica.DisableIndexing))
                        oldHost.DisableIndexing = true;

                    targetReplica.Hosts.push_back(oldHost);
                }
            }
            service.Replicas.clear();
            for (TMap<TString, TSearchMapReplica>::const_iterator r = newReplicas.begin(); r != newReplicas.end(); ++r) {
                service.Replicas.push_back(r->second);
            }
        }
    }

    void TJsonSearchMapParser::MergePorts() {
        for (auto& service : InternalSearchMap)
        for (TVector<TSearchMapReplica>::iterator replica = service.Replicas.begin(); replica != service.Replicas.end(); ++replica)
        for (TVector<TSearchMapHost>::iterator host = replica->Hosts.begin(); host != replica->Hosts.end(); ++host)
        {
            if (!host->IndexerPort)
                host->IndexerPort = service.IndexerPort;
            if (!host->SearchPort)
                host->SearchPort = service.SearchPort;
        }
    }

}

TSearchMapParserFactory::TRegistrator<NSearchMapParser::TJsonSearchMapParser> RegistratorJson("json");
