#pragma once

#include "filters.h"
#include "slot_data.h"
#include "datacenter.h"

#include <util/generic/string.h>
#include <util/string/cast.h>
#include <util/generic/map.h>
#include <util/string/vector.h>
#include <util/system/mutex.h>
#include <util/generic/set.h>
#include <util/digest/fnv.h>
#include <library/cpp/cgiparam/cgiparam.h>
#include <util/charset/utf8.h>
#include <util/charset/wide.h>
#include <util/system/rwlock.h>
#include <library/cpp/json/writer/json_value.h>

namespace NRTYCluster {

    class TCTypeCluster;

    class IClusterScanCallback {
    public:
        virtual ~IClusterScanCallback() {}
        virtual bool OnCType(const TString& name, const TCTypeCluster& cTypeCluster) = 0;
        virtual bool OnDC(const TString& name, const TDatacenter& dC) = 0;
        virtual void OnSlot(const TString& name, const TSlotData& slotData) = 0;
    };

    class TCTypeCluster {
    private:
        TMap<TString, TDatacenter> DCs;
        TVector<TString> DCNames;
        TMap<TString, TSlotData> Slots;
        TRWMutex Mutex;
    public:

        typedef TAtomicSharedPtr<TCTypeCluster> TPtr;

        TCTypeCluster() {

        }

        TCTypeCluster(const TCTypeCluster& ctypeCluster) {
            operator=(ctypeCluster);
        }

        TCTypeCluster& operator=(const TCTypeCluster& ctypeCluster) {
            DCs = ctypeCluster.DCs;
            DCNames = ctypeCluster.DCNames;
            Slots = ctypeCluster.Slots;
            return *this;
        }

        const TVector<TString>& GetDCNames() const {
            return DCNames;
        }

        const TDatacenter* GetDC(const TString& dcName) const {
            TReadGuard rg(Mutex);
            TMap<TString, TDatacenter>::const_iterator i = DCs.find(dcName);
            if (i != DCs.end())
                return &i->second;
            return nullptr;
        }

        TCTypeCluster::TPtr FilterSlots(const ISlotsFilter& filter) const;

        bool RegisterSlot(const TSlotData& slot) {
            TWriteGuard wg(Mutex);
            if (Slots.find(slot.ShortSlotName()) == Slots.end()) {
                Slots[slot.ShortSlotName()] = slot;
                if (DCs.find(slot.GetDC()) == DCs.end())
                    DCNames.push_back(slot.GetDC());
                return DCs[slot.GetDC()].RegisterSlot(slot);
            } else
                return false;
        }

        bool IsExists(const TSlotData& slot) const {
            TReadGuard rg(Mutex);
            return Slots.find(slot.ShortSlotName()) != Slots.end();
        }

        void ScanAll(IClusterScanCallback& callback) const {
            TReadGuard rg(Mutex);
            for (TMap<TString, TDatacenter>::const_iterator i = DCs.begin(); i != DCs.end(); ++i) {
                if (callback.OnDC(i->first, i->second))
                    i->second.ScanAll(callback);
            }
        }

        const TMap<TString, TSlotData>& GetSlots() const {
            TReadGuard rg(Mutex);
            return Slots;
        }

        bool Deserialize(const NJson::TJsonValue& json) {
            if (!json.IsMap())
                return false;

            DCs.clear();
            for (auto&& i : json.GetMapSafe()) {
                if (!DCs[i.first].Deserialize(i.second)) {
                    return false;
                }
                auto&& slots = DCs[i.first].GetSlots();
                Slots.insert(slots.begin(), slots.end());
            }
            return true;
        }

        NJson::TJsonValue Serialize() const {
            NJson::TJsonValue result(NJson::JSON_MAP);
            for (auto&& i : DCs) {
                result[i.first] = i.second.Serialize();
            }
            return result;
        }
    };

    class TCluster {
    private:
        TMap<TString, TCTypeCluster> CTypeClusters;
        TMap<TString, TSlotData> Slots;
        TRWMutex Mutex;
    public:

        using TMapType = TMap<TString, TCluster>;

        TCluster() {

        }

        TCluster(const TCluster& cluster) {
            operator=(cluster);
        }

        TCluster& operator=(const TCluster& cluster) {
            CTypeClusters = cluster.CTypeClusters;
            Slots = cluster.Slots;
            return *this;
        }

        void Clear() {
            TWriteGuard wg(Mutex);
            CTypeClusters.clear();
            Slots.clear();

        }

        bool IsExists(const TSlotData& slot) const {
            TReadGuard rg(Mutex);
            return Slots.find(slot.ShortSlotName()) != Slots.end();
        }

        bool GetCTypeCluster(const TString& ctype, TCTypeCluster& result) const {
            TReadGuard rg(Mutex);
            TMap<TString, TCTypeCluster>::const_iterator i = CTypeClusters.find(ctype);
            if (i != CTypeClusters.end()) {
                result = i->second;
                return true;
            }
            return false;
        }

        const TMap<TString, TCTypeCluster>& GetCTypeCluster() const {
            return CTypeClusters;
        }

        const TMap<TString, TSlotData>& GetSlots() const {
            return Slots;
        }

        bool RegisterSlot(const TString& ctype, const TSlotData& slot) {
            TWriteGuard wg(Mutex);
            if (Slots.find(slot.ShortSlotName()) == Slots.end()) {
                Slots[slot.ShortSlotName()] = slot;
                return CTypeClusters[ctype].RegisterSlot(slot);
            } else
                return false;
        }

        bool RegisterCType(const TString& ctype) {
            TWriteGuard wg(Mutex);
            CTypeClusters[ctype];
            return true;
        }

        void ScanAll(IClusterScanCallback& callback) const {
            TReadGuard rg(Mutex);
            for (TMap<TString, TCTypeCluster>::const_iterator i = CTypeClusters.begin(); i != CTypeClusters.end(); ++i) {
                if (callback.OnCType(i->first, i->second))
                    i->second.ScanAll(callback);
            }
        }

        bool Deserialize(const NJson::TJsonValue& json) {
            if (!json.IsMap())
                return false;

            CTypeClusters.clear();
            for (auto&& i : json.GetMapSafe()) {
                if (!CTypeClusters[i.first].Deserialize(i.second)) {
                    return false;
                }
                auto&& slots = CTypeClusters[i.first].GetSlots();
                Slots.insert(slots.begin(), slots.end());
            }
            return true;
        }

        NJson::TJsonValue Serialize() const {
            NJson::TJsonValue result(NJson::JSON_MAP);
            for (auto&& i : CTypeClusters) {
                result[i.first] = i.second.Serialize();
            }
            return result;
        }
    };

}
