#pragma once

#include <library/cpp/watchdog/lib/handle.h>
#include <library/cpp/watchdog/lib/interface.h>
#include <util/generic/map.h>
#include <util/generic/set.h>
#include <util/generic/list.h>
#include <util/generic/vector.h>
#include <util/generic/ptr.h>
#include <util/generic/maybe.h>
#include <util/stream/output.h>

class TMutex;
class TStringOutput;

//
// Subscriber interface
//
class IWatchdogOptionSubscriber {
public:
    virtual void OnWatchdogOption(const TString& key, const TString& value) = 0;
    virtual ~IWatchdogOptionSubscriber();
};

//
// Subscription source interface
//
class IWatchdogOptions {
public:
    //RAII type for automatic unsubscription (keep it while updates are needed; unref it to unsubscribe)
    struct TSubscriberInfo final : public TAtomicRefCount<TSubscriberInfo, IWatchdogOptions> {
    private:
        IWatchdogOptions* Owner = nullptr;
        IWatchdogOptionSubscriber* Subscriber = nullptr;
        TSet<TString> Options;
        friend class TWatchdogOptionsCollection;
    public:
        inline IWatchdogOptions* GetOwner() const {
            return Owner;
        }
        inline IWatchdogOptionSubscriber* GetSubscriber() const {
            return Subscriber;
        }
        inline const TSet<TString>& GetSubscription() const {
            return Options;
        }
    };

    using TSubscription = TIntrusivePtr<TSubscriberInfo>;

public:
    virtual ~IWatchdogOptions();

    //Subscribe to changes
    virtual TSubscription Subscribe(IWatchdogOptionSubscriber* subscriber, const TSet<TString>& options) = 0;

    inline TSubscription Subscribe(IWatchdogOptionSubscriber* subscriber, const TString& option) {
        return Subscribe(subscriber, TSet<TString>{option});
    }

    inline TSubscription Subscribe(IWatchdogOptionSubscriber* subscriber) {
        return Subscribe(subscriber, TSet<TString>());
    }

protected:
    //Unsubscribe from changes
    virtual void DoDestroy(TSubscriberInfo* t) = 0;

public:
    static void Destroy(TSubscriberInfo* t) noexcept;
};

//
// External updater interface, used from controller commands
//
class IWatchdogOptionsUpdater {
public:
    virtual void Override(const TString& key, const TMaybe<TString>& value) = 0;
    virtual void NotifyNow() = 0;
    virtual ~IWatchdogOptionsUpdater();
};

//
// The subscription source
//
class TWatchdogOptionsCollection : public IWatchdogOptions, protected IWatchdogOptionsUpdater {
public:
    using TData = TMap<TString,TString>;
public:
    TWatchdogOptionsCollection();
    virtual ~TWatchdogOptionsCollection();

    //Set new data
    virtual void DoUpdate(const TString& key, const TString& value);
    virtual void DoUpdate(const TData& data);

    //Subscribe to changes
    virtual TSubscription Subscribe(IWatchdogOptionSubscriber* subscriber, const TSet<TString>& options) override;

    //Debug output
    virtual void DumpData(TStringOutput& o, const TString& sep = TString()) const;

    virtual TData GetData() const;

protected:
    virtual void DoNotify(IWatchdogOptionSubscriber& t, const TString& key, const TString& value);
    void DoNotify(const TString& key, const TString& value);
    virtual void DoDestroy(TSubscriberInfo* t) override;
    void Release();
    static void MergeMaps(TData& data, const TData& patch);

protected:
    // IWatchdogOptionsUpdater
    void Override(const TString& key, const TMaybe<TString>& value) override;
    void NotifyNow() override;

    inline IWatchdogOptionsUpdater& AsUpdater() {
        return *this;
    }

protected:
    TData Data;
    TData Overrides;
    TList<TSubscriberInfo*> Subscribers;
    THolder<TMutex> Mutex;
    bool Updating;
};

using TWatchdogSubscription = TWatchdogOptionsCollection::TSubscription;

//
// ITS watchdog implementation
//
class TWatchdogOptionsHandle: public IWatchDogHandleFreq, public TWatchdogOptionsCollection {
public:
    TWatchdogOptionsHandle(const TString& filename, TDuration period);
    TWatchdogOptionsHandle(const TVector<TString>& filenames, TDuration period);
    virtual ~TWatchdogOptionsHandle();
    TDuration GetPeriod() const;
    void Enable();
    void Disable();
    void DoCheck(TInstant timeNow) override;
    using TWatchdogOptionsCollection::AsUpdater;

public:
    using TPtr = TIntrusivePtr<TWatchdogOptionsHandle>;
    static TPtr CreateShared(const TString& filename, TDuration period);
    static TPtr CreateShared(const TVector<TString>& filenames, TDuration period);

protected:
    TVector<TString> FileNames;
    TDuration Period;
    THolder<IWatchDog> WatchDogHandle;
    bool Active;
};

using TWatchdogOptionsHandlePtr = TWatchdogOptionsHandle::TPtr;
