#pragma once

#include "stream.h"
#include "signals.h"

#include <saas/rtyserver/synchronizer/library/resource.h>
#include <saas/rtyserver/common/common_messages.h>

#include <library/cpp/json/writer/json_value.h>
#include <util/generic/maybe.h>

namespace NFusion {
    class TSyncSignals;
    struct TSyncOptions;

    class TSync {
    public:
        enum class ESyncStatus {
            UNNECESSARY,
            STARTED,
            GETTING_RESOURCES,
            APPLY_RESOURCES,
            FINISHED,
            FAILED
        };
    private:
        class TSyncStatus {
        public:
            TSyncStatus();
            void Change(const ESyncStatus& status);
            NJson::TJsonValue GetStatus() const;
        private:
            bool Required;
            ESyncStatus Status;
        };

    public:
        TSync(const TSyncOptions& options, const TRTYServerConfig& globalConfig)
            : Options(options)
            , GlobalConfig(globalConfig)
        {
        }
        NJson::TJsonValue GetSyncStatus() const {
            return Status.GetStatus();
        }
    protected:
        void OnStreamStart(const TBaseStreamConfig& stream, TDuration delay);
        void OnSyncFailure();
        bool CriticalDelay(TDuration delay) const;
        bool IsConfigForSync() const;
        bool SyncRequired() const;
        bool DisableSearchWhileFetching() const;
        bool SyncResources();
        bool SyncResource(const NRTYServer::TRemoteResource& resource);
        bool GetResources(const TBaseStreamConfig& stream,  TDuration delay);

    private:
        const TSyncOptions& Options;
        const TRTYServerConfig& GlobalConfig;

        TVector<NRTYServer::TRemoteResource> Resources;
        TSyncStatus Status;
    };

    namespace {
        class TMessageProcessor : public IMessageProcessor {
        public:
            TMessageProcessor(const TString & streamName, const NFusion::TSync *sync)
                : Sync(sync)
                , StreamName(streamName)
            {
                RegisterGlobalMessageProcessor(this);
            }
            ~TMessageProcessor() {
                UnregisterGlobalMessageProcessor(this);
            }
        protected:
            virtual TString Name() const override {
                return JoinMetricName(StreamName, "FetcherStream");
            }
            virtual bool Process(IMessage* message_) override {

                if (auto message = message_->As<TMessageGetDocfetcherStatus>()) {
                    message->DocfetcherPresent = true;
                    message->Status["Sync"] = Sync->GetSyncStatus();
                }
                return true;
            }
        private:
            const NFusion::TSync *Sync;
            const TString StreamName;
        };
    }

    template <class TStream>
    class TSyncStream
        : public TStream
        , public TSync
    {
    public:
        template <class... TArgs>
        TSyncStream(const TSyncOptions& options, const TRTYServerConfig& globalConfig, TArgs&... args)
            : TStream(args...)
            , TSync(options, globalConfig)
            , Signals(MakeHolder<TSyncSignals>())
        {
        }

    protected:
        virtual void OnStreamStart() override {
            const TDuration delay = TStream::GetDelay();
            if (TSync::CriticalDelay(delay) && TSync::IsConfigForSync()) {
                Signals->UpdateSyncStatus(true);
                TMessageProcessor messageProcessor(TStream::GetStreamName(), this);
                if (TSync::DisableSearchWhileFetching()) {
                    TStream::DisableSearch();
                }
                TSync::OnStreamStart(TStream::BaseConfig, delay);
                Signals->UpdateSyncStatus(false);
                TStream::OnStreamStart();
                TStream::EnableSearch();
            } else {
                TStream::OnStreamStart();
            }
        }
    private:
        THolder<TSyncSignals> Signals;
    };
}
