#include "allocator.h"
#include <library/cpp/charset/wide.h>


namespace NRTYCluster {

    TSlotsAllocator::TSlotsAllocator()
    {}

    TSlotsAllocator::TSlotsAllocator(const TVector<TString>& prefDCs)
        : PrefDCs(prefDCs)
    {}

    void TSlotsAllocator::RotatePrefDCs() {
        if (PrefDCs.size() > 1) {
            Rotate(PrefDCs.begin(), PrefDCs.begin() + 1, PrefDCs.end());
        }
    }

    bool TSlotsAllocator::DeserializeFromString(const TString& value) {
        NJson::TJsonValue valueJson;
        if (!NJson::ReadJsonFastTree(value, &valueJson))
            return false;
        return DeserializeFromJson(valueJson);
    }

    bool TSlotsAllocator::DeserializeFromJson(const NJson::TJsonValue& value) {
        const NJson::TJsonValue::TArray& prefDcsJson = value["pref_dcs"].GetArray();
        const NJson::TJsonValue::TArray& customSlotsJson = value["custom_slots"].GetArray();
        if (!value["pref_dc_only"].GetBoolean(&PrefferedDCOnly))
            PrefferedDCOnly = false;
        if (PrefferedDCOnly && prefDcsJson.empty() && customSlotsJson.empty()) {
            ERROR_LOG << "set pref_dcs or custom_slots" << Endl;
            return false;
        }
        for (auto&& i : prefDcsJson) {
            TString dcName = i.GetStringRobust();
            if (!IsUtf(dcName))
                dcName = WideToUTF8(CharToWide(dcName, csYandex));
            PrefDCs.push_back(dcName);
        }
        if (!value["multi_dc"].GetBoolean(&MultiDC))
            MultiDC = false;
        if (!value["one_slot_on_server"].GetBoolean(&OneServerOnSlot))
            OneServerOnSlot = false;
        if (!value["allow_same_host_replica"].GetBoolean(&AllowSameHostReplica))
            AllowSameHostReplica = false;
        for (const auto& slot: value["custom_slots"].GetArray()) {
            TSlotData sd;
            if (!TSlotData::Parse(slot.GetStringRobust(), sd)) {
                ERROR_LOG << "set pref_dcs or custom_slots" << Endl;
                return false;
            }
            CustomSlots.insert(sd);
        }

        return true;
    }

    NJson::TJsonValue TSlotsAllocator::SerializeToJson() const {
        NJson::TJsonValue result;
        NJson::TJsonValue& prefDcsJson = result.InsertValue("pref_dcs", NJson::JSON_ARRAY);
        for (auto&& i : PrefDCs) {
            prefDcsJson.AppendValue(i);
        }
        result["pref_dc_only"] = PrefferedDCOnly;
        result["multi_dc"] = MultiDC;
        result["one_slot_on_server"] = OneServerOnSlot;
        result["allow_same_host_replica"] = AllowSameHostReplica;
        for (const auto& slot : CustomSlots)
            result["custom_slots"].AppendValue(slot.FullSlotName());

        return result;
    }

    ui32 TSlotsAllocator::GetDCPrefMark(const TString& dcName) const {
        for (ui32 dcSel = 0; dcSel < PrefDCs.size(); dcSel++) {
            if (PrefDCs[dcSel] == dcName) {
                return 1000 - dcSel;
            }
        }
        return 0;
    }

    bool TSlotsAllocator::IsAllowedDC(const TString& dc) const {
        if (PrefferedDCOnly) {
            return GetDCPrefMark(dc);
        }
        return true;
    }

    TPreferredDCListFilter::TPreferredDCListFilter(const TVector<TString>& prefDCs) {
        PrefDCs.insert(prefDCs.begin(), prefDCs.end());
    }

    TMap<TString, NRTYCluster::TDatacenter> TPreferredDCListFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        TMap<TString, NRTYCluster::TDatacenter> result;
        for (auto&& dc : dcs) {
            if (!PrefDCs.contains(dc.first)) {
                DEBUG_LOG << "TPreferredDCListFilter filter " << dc.first << Endl;
                continue;
            }

            result[dc.first] = dc.second;
        }
        return result;
    }

    TDCResourcesFilter::TDCResourcesFilter(ui64 slotsCount, bool oneSlotOnServer)
        : SlotsCount(slotsCount)
        , OneSlotOnServer(oneSlotOnServer)
    {}

    TMap<TString, NRTYCluster::TDatacenter> TDCResourcesFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        TMap<TString, NRTYCluster::TDatacenter> result;
        for (auto&& dc : dcs) {
            if (OneSlotOnServer && dc.second.GetServers().size() < SlotsCount) {
                DEBUG_LOG << "TDCResourcesFilter filter " << dc.first << Endl;
                continue;
            }
            if (!OneSlotOnServer && dc.second.GetSlots().size() < SlotsCount) {
                DEBUG_LOG << "TDCResourcesFilter filter " << dc.first << Endl;
                continue;
            }

            result[dc.first] = dc.second;
        }
        return result;
    }

    TMap<TString, NRTYCluster::TDatacenter> TOneSlotFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        TMap<TString, NRTYCluster::TDatacenter> result;
        for (auto&& dc : dcs) {
            for (auto&& server : dc.second.GetServers()) {
                CHECK_WITH_LOG(!server.second.GetSlots().empty());
                result[dc.first].RegisterSlot(server.second.GetSlots().begin()->second);
            }
        }
        return result;
    }

    class TVectorComparable {
    private:
        TVector<ui64> VectComp;
    public:
        TVectorComparable(const TVector<ui64>& vect) {
            VectComp = vect;
        }

        bool operator <(const TVectorComparable& v1) const {
            CHECK_WITH_LOG(v1.VectComp.ysize() == VectComp.ysize());
            for (ui32 i = 0; i < v1.VectComp.size(); ++i) {
                if (VectComp[i] == v1.VectComp[i])
                    continue;
                return VectComp[i] > v1.VectComp[i];
            }
            return false;
        }
    };

    TVector<ui64> TCommonRankerFilter::GetRank(const TSlotData& sd, const TServer& /*server*/, const TString& dcName, const TDatacenter& dc) const {
        TVector<ui64> result;

        result.push_back(Allocator.GetDCPrefMark(dcName));
        result.push_back(dc.GetServers().size());
        result.push_back(FnvHash<ui64>(dcName.data(), dcName.size()));
        result.push_back(1000 - CurrentServiceSlotsByServer[sd.ShortHost()]);
        result.push_back(1000 - UsedSlotsByServer[sd.ShortHost()]);
        result.push_back(100000 - sd.Port);
        TString host = sd.ShortHost();
        result.push_back(FnvHash<ui64>(host.data(), host.size()));
        return result;
    }

    TMap<TString, NRTYCluster::TDatacenter> TCommonRankerFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        TMap<TString, NRTYCluster::TDatacenter> result;
        TSet<TString> selectedSlots;
        for (ui32 i = 0; i < SlotsCount; ++i) {
            TMap<TVectorComparable, TVector<TSlotData>> sortedSlots;
            for (auto&& dc : dcs) {
                for (auto&& server : dc.second.GetServers()) {
                    for (auto&& slot : server.second.GetSlots()) {
                        if (!selectedSlots.contains(slot.second.ShortSlotName())) {
                            TVectorComparable rank(GetRank(slot.second, server.second, dc.first, dc.second));
                            sortedSlots[rank].push_back(slot.second);
                        }
                    }
                }
            }

            if (sortedSlots.size() < 15) {
                DEBUG_LOG << "--------" << Endl;
                for (auto&& slot : sortedSlots) {
                    TStringStream ss;
                    for (auto&& slotName : slot.second) {
                        ss << slotName.FullSlotName() << ",";
                    }
                    DEBUG_LOG << ss.Str() << Endl;
                }
                DEBUG_LOG << "--------" << Endl;
            }

            if (!sortedSlots.size())
                return TMap<TString, NRTYCluster::TDatacenter>();

            TSlotData slot = *(sortedSlots.begin()->second.begin());
            selectedSlots.insert(slot.ShortSlotName());
            result[slot.GetDC()].RegisterSlot(slot);
            UsedSlotsByServer[slot.ShortHost()]++;
            CurrentServiceSlotsByServer[slot.ShortHost()]++;
        }
        return result;
    }

    TCustomSlotsFilter::TCustomSlotsFilter(const TSet<TSlotData>& customSlots)
        : CustomSlots(customSlots)
    {}

    TMap<TString, NRTYCluster::TDatacenter> TCustomSlotsFilter::Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const {
        TMap<TString, NRTYCluster::TDatacenter> result;
        for (const auto& dc : dcs)
            for (const auto& server : dc.second.GetServers())
                for (const auto& slot: server.second.GetSlots())
                    if (CustomSlots.contains(slot.second))
                        result[dc.first].RegisterSlot(slot.second);
        return result;
    }

}
