#pragma once

#include <saas/indexerproxy/configs/dispatch_config.h>

#include <saas/library/daemon_base/metrics/metrics.h>
#include <saas/util/queue.h>
#include <saas/util/events_rate_calcer.h>

#include <util/generic/noncopyable.h>
#include <util/generic/ptr.h>
#include <util/system/event.h>
#include <util/thread/pool.h>

#include <atomic>

class TBlob;
class TDispatcherConfig;
class TTaskExecutor;
struct TProxyServiceConfig;

class TDeferredMessagesQueue : public TNonCopyable {
public:
    struct TQueueHeader {
        TString Service;
        TInterval<NSearchMapParser::TShardIndex> Shards;

        bool operator!=(const TQueueHeader& o) const {
            return Service != o.Service || Shards != o.Shards;
        }
    };

    class IQueue {
    private:
        TAtomic ActorsCounter = 0;
    public:
        class IMessage {
        public:
            typedef TAtomicSharedPtr<IMessage> TPtr;
            virtual ~IMessage() {}
            virtual const NRTYServer::TMessage& Data() const = 0;
            virtual const TBlob& RawData() const = 0;
            virtual void Ack() = 0;
        };

        typedef TAtomicSharedPtr<IQueue> TPtr;

    public:
        virtual ~IQueue() {}
        virtual IMessage::TPtr Get() = 0;
        virtual TDeferredMessagesQueue::TQueueHeader GetHeader() const = 0;
        virtual void Store(const NRTYServer::TMessage& data) = 0;
        virtual bool CheckMessageForStore(const NRTYServer::TMessage& message, TString& reply) const = 0;
        virtual void Purge() = 0;
        virtual void CollectGarbage() = 0;
        virtual ui32 Size() const = 0;
        virtual ui64 SizeInBytes() const = 0;
        virtual void Stop() = 0;

        TAtomic& MutableActorsCounter() {
            return ActorsCounter;
        }

        ui64 GetActorsCount() const {
            return AtomicGet(ActorsCounter);
        }

    public:
        typedef NObjectFactory::TParametrizedObjectFactory<IQueue, TDeferredMQConfig::TType, const TDispatcherConfig&, const TString&, const TQueueHeader&> TFactory;
    };

    class TServiceData : public TAtomicRefCount<TServiceData> {
    public:
        typedef TIntrusivePtr<TServiceData> TPtr;
        typedef TMap<TString, IQueue::TPtr> TQueues;

        TServiceData(const TString& name, const TProxyServiceConfig& config)
            : Name(name)
            , Config(config)
        {}

        ui64 GetSizeInBytes() const;
        void CheckSizeInBytesLimit() const;
        void AddQueue(const TString& queueName, IQueue::TPtr queue) {
            Queues[queueName] = queue;
        }

        void PurgeQueues() {
            for (auto&& q : Queues) {
                q.second->Purge();
            }
        }

        const TQueues& GetQueues() const {
            return Queues;
        }

        const TProxyServiceConfig& GetConfig() const {
            return Config;
        }

    private:
        TString Name;
        const TProxyServiceConfig& Config;
        TQueues Queues;
    };

    struct TBackendData {
        TServiceData::TPtr Service;
        TEventRate<> TrySend;
        TEventRate<> SendFailed;
        IQueue::TPtr Queue;
    };
public:
    TDeferredMessagesQueue(const TDispatcherConfig& config);
    ~TDeferredMessagesQueue();
    void Store(const TString& clientForSaveName, const NRTYServer::TMessage& message, bool asyncFlag);
    ui64 GetSizeInBytes() const;
    void PurgeQueue(const TString& name);
    void PurgeService(const TString& service);
    void GetQueuesInfo(NJson::TJsonValue& result) const;

    void Start(TTaskExecutor& executor);
    void Stop();

    void StartStorage();
    void StopStorage();

    ui64 GetAllRequestersCount() const;

    const TDispatcherConfig& GetConfig() const {
        return Config;
    }

private:
    struct TProcessor : public IObjectInQueue {
    public:
        TProcessor(TDeferredMessagesQueue& queue, const TDuration& period, const TString& name);
        virtual void Process(void* ThreadSpecificResource) override;

    protected:
        //true - not wait
        virtual bool DoProcess(const TString& queueName, IQueue::TPtr oneBackendQueue) = 0;

    protected:
        TDeferredMessagesQueue& Queue;

    private:
        TDuration Period;
        TString Name;
    };

    struct TGarbageCollector : public TProcessor {
    public:
        TGarbageCollector(TDeferredMessagesQueue& queue);
        virtual bool DoProcess(const TString& queueName, IQueue::TPtr oneBackendQueue) override;
    };

    struct TSender : public TProcessor {
        TTaskExecutor& Executor;

    public:
        TSender(TDeferredMessagesQueue& queue, TTaskExecutor& executor, const TString& name);
        virtual bool DoProcess(const TString& queueName, IQueue::TPtr oneBackendQueue) override;
    };

    typedef TMap<TString, TBackendData> TQueues;
    typedef TMap<TString, TServiceData::TPtr> TSerivces;
    void AddQueue(const TString& queueName, const TString& serviceName, const NSearchMapParser::TShardsInterval& interval, TServiceData::TPtr serviceData);

private:
    const TDispatcherConfig& Config;
    TQueues Queues;
    TSerivces Services;
    TSimpleThreadPool ThreadPool;
    std::atomic<bool> Active;
    TManualEvent StoppedEvent;
    TOptionalRTYMtpQueue StoreQueue;
    TMutex MutexStartStop;
    TAutoGlobal<TOrangeMetric> MetricQueueSize;
};
