#include "urlhash.h"
#include "kps.h"

#include <saas/protos/rtyserver.pb.h>

#include <library/cpp/logger/global/global.h>
#include <cmath>


namespace NSaas {
    static bool CheckInterval(const TInterval<NSearchMapParser::TShardIndex>& shardingInterval, const TInterval<NSearchMapParser::TShardIndex>& interval) {
        return interval.Intersection(shardingInterval);
    }

    NSearchMapParser::TShardIndex TKpsShardingRule::GetComplexShard(TKeyPrefix prefix, const TStringBuf& url) const {
        return GetComplexShard(prefix, TUrlShardingRule::GetUrlShard(url, Context.GetShardsMax()));
    }

    NSearchMapParser::TShardIndex TKpsShardingRule::GetComplexShard(TKeyPrefix prefix, const NSearchMapParser::TShardIndex urlShard) const {
        return Min(GetPrefixShard(prefix, Context) + urlShard % (1 << Context.KpsShift), Context.GetShardsMax() - 1);
    }

    TKpsShardingRule::TKpsShardingRule(const TShardsDispatcher::TContext& context)
        : IShardingRule(context)
    {}

    NSearchMapParser::TShardIndex TKpsShardingRule::GetShard(const TStringBuf& url, TKeyPrefix prefix) const {
        if (url.empty()) {
            Y_ASSERT(Context.KpsShift == 0);
            return GetPrefixShard(prefix, Context);
        }

        return GetComplexShard(prefix, url);
    }

    bool TKpsShardingRule::CheckSearchInterval(TStringBuf /*url*/, TKeyPrefix kps, const TInterval<NSearchMapParser::TShardIndex>& interval) const {
        const auto shardingInterval = GetShardingInterval(GetPrefixShard(kps, Context));
        return NSaas::CheckInterval(shardingInterval, interval);
    }

    void TKpsShardingRule::EnumerateIntervals(TStringBuf /*url*/, TKeyPrefix kps, const TShardIntervals& sortedIntervals,
                                              IShardIntervalCallback& callback) const {
        auto shardInterval = GetShardingInterval(GetPrefixShard(kps, Context));
        NSearchMapParser::TShardsInterval leftBorder(shardInterval.GetMin(), shardInterval.GetMin());
        auto first = sortedIntervals.begin();
        auto last = sortedIntervals.end();
        auto it = std::lower_bound(first, last, leftBorder);
        for (; it != last && it->Intersection(shardInterval); ++it) {
            callback.OnShardInterval(std::distance(first, it));
        }
    }

    bool TKpsShardingRule::CheckMessage(const NRTYServer::TMessage& message, TString& error) const {
        if (!message.GetDocument().HasKeyPrefix()) {
            error = "Document doesn't have keyprefix";
            return false;
        }
        if (Context.KpsShift != 0 && !message.GetDocument().HasUrl()) {
            error = "Document doesn't have url";
            return false;
        }
        return true;
    }

    TInterval<NSearchMapParser::TShardIndex> TKpsShardingRule::GetShardingInterval(NSearchMapParser::TShardIndex shard) const {
        NSaas::TKeyPrefix shardMin = (shard >> Context.KpsShift) << Context.KpsShift;
        return TInterval<NSearchMapParser::TShardIndex>(shardMin, shardMin + (1 << Context.KpsShift) - 1);
    }

    const double MULTIPLIER = 0.5 * (sqrt((double)5) - 1);

    NSearchMapParser::TShardIndex TKpsShardingRule::GetPrefixShard(TKeyPrefix prefix, const TShardsDispatcher::TContext& context) {
        if (!context.KpsShift)
            return prefix % context.GetShardsMax();
        //http://studopedia.ru/2_80095_funktsii-heshirovaniya.html
        ui32 kpsBits = 16 - context.KpsShift;
        double hashpart = MULTIPLIER * prefix;
        ui32 hash = (1 << kpsBits) * (hashpart - ui64(hashpart));
        return hash << context.KpsShift;
    }

    TShardsDispatcher::IShardingRule::TFactory::TRegistrator<TKpsShardingRule> TKpsShardingRule::Registrator(KeyPrefix);
}
