#pragma once

#include <saas/util/cluster/filters.h>
#include <saas/util/cluster/server.h>
#include <saas/util/cluster/datacenter.h>

#include <util/generic/string.h>
#include <util/generic/vector.h>
#include <util/generic/hash_set.h>
#include <library/cpp/cgiparam/cgiparam.h>
#include <util/charset/utf8.h>
#include <util/charset/wide.h>
#include <util/string/vector.h>
#include <library/cpp/json/writer/json_value.h>
#include <library/cpp/json/json_reader.h>
#include <saas/library/searchmap/slots_pool.h>

namespace NRTYCluster {

    struct TResources {
        ui64 CPUi = 3200;
        ui64 DiskGB = 4 * 1024;
        ui64 MemoryGB = 24;
    };

    struct TSlotsAllocator {
        bool MultiDC = false;
        bool PrefferedDCOnly = false;
        bool OneServerOnSlot = false;
        bool AllowSameHostReplica = false;
        TVector<TString> PrefDCs;
        TSet<TSlotData> CustomSlots;

        TSlotsAllocator();
        TSlotsAllocator(const TVector<TString>& prefDCs);

        bool DeserializeFromString(const TString& value);
        bool DeserializeFromJson(const NJson::TJsonValue& value);
        NJson::TJsonValue SerializeToJson() const;

        ui32 GetDCPrefMark(const TString& dcName) const;
        bool IsAllowedDC(const TString& dc) const;
        void RotatePrefDCs();
    };

    // For allocator.PrefferedDCOnly
    class TPreferredDCListFilter : public ISlotsFilter {
    public:
        TPreferredDCListFilter(const TVector<TString>& prefDCs);
        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override;

    private:
        TSet<TString> PrefDCs;
    };

    // For !allocator.MultiDc
    class TDCResourcesFilter : public ISlotsFilter {
    public:
        TDCResourcesFilter(ui64 slotsCount, bool oneSlotOnServer);
        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override;

    private:
        ui64 SlotsCount;
        bool OneSlotOnServer;
    };

    class TOneSlotFilter : public NRTYCluster::ISlotsFilter {
    public:
        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override;
    };

    class TCommonRankerFilter : public ISlotsFilter {
    public:
        TCommonRankerFilter(TMap<TString, ui32>& usedSlotsByServer, TMap<TString, ui32>& currentServiceSlotsByServer, const TSlotsAllocator& allocator, ui64 slotsCount)
            : Allocator(allocator)
            , SlotsCount(slotsCount)
            , UsedSlotsByServer(usedSlotsByServer)
            , CurrentServiceSlotsByServer(currentServiceSlotsByServer)
        {
        }

    private:
        TVector<ui64> GetRank(const TSlotData& sd, const TServer& server, const TString& dcName, const TDatacenter& /*dc*/) const;
        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override;

    private:
        const TSlotsAllocator& Allocator;
        ui64 SlotsCount;
        TMap<TString, ui32>& UsedSlotsByServer;
        TMap<TString, ui32>& CurrentServiceSlotsByServer;
    };

    class TPoolFilter: public NRTYCluster::ISlotsFilter {
    private:
        TSet<TString> UsedSlots;
        const bool CheckExclude;
    public:

        TPoolFilter(bool checkExclude)
            : CheckExclude(checkExclude)
        {

        }

        TPoolFilter(const NSearchMapParser::IUsedSlotsPool& slots, bool checkExclude)
            : CheckExclude(checkExclude)
        {
            TVector<NSearchMapParser::TSearchMapHost> hosts = slots.GetUsedSlots();
            for (auto&& i : hosts) {
                UsedSlots.insert(i.GetShortSlotName());
            }
        }

        bool IsAcceptableSlot(const NRTYCluster::TSlotData& sd) const {
            return (CheckExclude == !UsedSlots.contains(sd.ShortSlotName()));
        }

        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override {
            TMap<TString, NRTYCluster::TDatacenter> result;
            for (auto&& dc : dcs) {
                for (auto&& slot : dc.second.GetSlots()) {
                    if (IsAcceptableSlot(slot.second)) {
                        result[dc.first].RegisterSlot(slot.second);
                    }
                }
            }
            return result;
        }

        void AddInPool(const TString& newSlot) {
            UsedSlots.insert(newSlot);
        }
    };

    class TUsedServersFilter: public NRTYCluster::ISlotsFilter {
    public:
        TUsedServersFilter(NSearchMapParser::IUsedSlotsPool& slots) {
            TVector<NSearchMapParser::TSearchMapHost> hosts = slots.GetUsedSlots();
            for (auto&& i : hosts) {
                UsedServers.insert(i.GetShortHostName());
            }
        }

        TUsedServersFilter() {
        }

        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override {
            TMap<TString, NRTYCluster::TDatacenter> result;
            for (auto&& dc : dcs) {
                for (auto&& server : dc.second.GetServers()) {
                    if (!UsedServers.contains(server.first)) {
                        for (auto&& slot : server.second.GetSlots())
                            result[dc.first].RegisterSlot(slot.second);
                    } else
                        DEBUG_LOG << "TUsedServersFilter filter " << server.first << Endl;
                }
            }
            return result;
        }

        void AddInPool(const TString& server) {
            UsedServers.insert(server);
        }

    private:
        TSet<TString> UsedServers;
    };

    class TCustomSlotsFilter : public NRTYCluster::ISlotsFilter {
    public:
        TCustomSlotsFilter(const TSet<TSlotData>& customSlots);
        virtual TMap<TString, NRTYCluster::TDatacenter> Apply(const TMap<TString, NRTYCluster::TDatacenter>& dcs) const override;

    private:
        const TSet<TSlotData>& CustomSlots;
    };
}
