#include "extbuilder_module.h"
#include "extbuilder_config.h"
#include "dbg_helper.h"
#include <saas/rtyserver_jupi/library/extbuilder/module.h>
#include <library/cpp/mediator/global_notifications/system_status.h>
#include <util/datetime/base.h>
#include <util/system/file.h>
#include <util/system/mutex.h>
#include <util/generic/cast.h>

namespace NFusion {
    class TExtBuilderLocalClient;

    //TODO(yrum, 20190316): refactor this helper class into a generic template, move to ^/saas/util or ^/util
    class IExtBuilderClientsCollection : public TThrRefBase {
    public:
        using TPtr = TIntrusivePtr<IExtBuilderClientsCollection>;

    public:
        virtual ~IExtBuilderClientsCollection() = default;

        virtual void OnDelete(const TExtBuilderLocalClient* instance) = 0;
    };

    // TExtBuilderLocalClient is just a simple decorator to separate Jupiter code from RTY
    class TExtBuilderLocalClient: public NRTYServer::IExtBuilderClient {
    public:
        using TPtr = TIntrusivePtr<TExtBuilderLocalClient>;

    private:
        TExtBuilderExecutor Bundle;
        const TExtBuilderConfig& Config;
        IExtBuilderTrace::TPtr Helper;
        IExtBuilderClientsCollection::TPtr Owner;

    public:
        TExtBuilderLocalClient(const TExtBuilderConfig& config, IExtBuilderTrace::TPtr helper)
            : Config(config)
            , Helper(helper)
        {
            const TAssertPolicy& assertPol = Config.CreateAssertPolicy();
            Bundle.SetAssertPolicy(assertPol);
            Bundle.SetTrace(Helper);
        }

        void SetOwner(const IExtBuilderClientsCollection::TPtr& owner) {
            Y_ASSERT(owner);
            Owner = owner;
        }

        ~TExtBuilderLocalClient() {
            if (Owner) {
                Owner->OnDelete(this);
            }
        }

        //TODO(yrum): implement BeginGetStatus()/EndGetStatus() async API, to update merger_tasks_info in time (Run() should be async too)

        void Run(NRtDoc::TBuilderTaskResult& result, NRtDoc::TBuilderTask& task) override {
            if (Config.GetModelsPath()) {
                task.SetModelsDir(Config.GetModelsPath());
            }
            task.SetTimerLogPath(Config.GetTimerLogPath());
            task.MutableConfig()->SetBundleVersion(Config.GetBundleVersion());
            task.MutableConfig()->SetForceKeepPreparates(Config.GetForceKeepPreparates());
            if (Config.GetDbgFlags().contains("force_rebuild") && Config.GetForceKeepPreparates()) {
                task.MutableConfig()->SetForceRebuild(true);
            }
            if (Config.GetDbgFlags().contains("dbg_info")) {
                task.MutableConfig()->SetDebugInfo(true);
            }

            Bundle.Merge(result, task);

            if (Config.GetDbgFlags().contains("dbg_mark_merged_as_incomplete") && task.HasOutput()) {
                TFile jupiIndex(TFsPath(task.GetOutput().GetTrgDir()) / "jupi_prep_index", WrOnly | OpenAlways);
            }

            if (result.GetErrorCode() != 0 && Config.GetDbgFlags().contains("dbg_hang_on_error")) {
                Sleep(TDuration::Minutes(60));
                AbortFromCorruptedIndex("JUPI merge failed");
            }
        }

    public:
        //TODO(yrum, 20190316): add to IExtBuilderClient?
        virtual void AsyncStop() {
            Bundle.AsyncStop();
        }
    };

    //TODO(yrum, 20190316): refactor this helper class into a generic template, move to ^/saas/util or ^/util
    class TExtBuilderClientsCollection: public IExtBuilderClientsCollection {
    private:
        static TMutex DeleteMutex;
        TVector<TExtBuilderLocalClient*> Clients;
    public:
        void Add(const TIntrusivePtr<TExtBuilderLocalClient>& instance) {
            TGuard<TMutex> g(&DeleteMutex);
            Clients.push_back(instance.Get()); // no .Ref()
            instance->SetOwner(this);
        }

        TVector<TExtBuilderLocalClient::TPtr> GetClients() const {
            TVector<TExtBuilderLocalClient::TPtr> result;

            TGuard<TMutex> g(&DeleteMutex);
            for (TExtBuilderLocalClient* c : Clients) {
                auto ptr = TExtBuilderLocalClient::TPtr(c);
                if (ptr.RefCount() > 1) {
                    result.emplace_back(std::move(ptr));
                } else {
                    // ptr.RefCount() == 1 would mean that we have an unlikely race condition.
                    // The object is being destroyed right now, but its destructor (and ours OnDelete()) has not been called yet.
                    // The casual ptr.Drop() would result in calling the destructor twice.
                    // We have to inch our way out.
                    c->Ref();
                    ptr.Drop();
                    c->DecRef(); // DecRef(), unlike UnRef(), does not call the destructor
                }
            }
            return result;
        }

        void AsyncStop() {
            auto clients = GetClients();
            for (const auto& ptr : clients) {
                ptr->AsyncStop();
            }
        }

    public:
        void OnDelete(const TExtBuilderLocalClient* instance) override {
            TGuard<TMutex> g(&DeleteMutex);
            Clients.erase(std::remove(Clients.begin(), Clients.end(), instance), Clients.end());
        }
    };

    TMutex TExtBuilderClientsCollection::DeleteMutex;

    class TLocalConn: public NRTYServer::IExtBuilderConnection {
    private:
        const TExtBuilderConfig& Config;
        IExtBuilderTrace::TPtr Diag;
        TIntrusivePtr<TExtBuilderClientsCollection> Clients;

    public:
        TLocalConn(const TExtBuilderConfig& cfg, IExtBuilderTrace::TPtr diagHelper)
            : Config(cfg)
            , Diag(diagHelper)
            , Clients(MakeIntrusive<TExtBuilderClientsCollection>())
        {
        }

    public:
        NRTYServer::IExtBuilderClient::TPtr CreateClient() override {
            TExtBuilderLocalClient::TPtr client = MakeIntrusive<TExtBuilderLocalClient>(Config, Diag);
            Clients->Add(client);
            return client;
        }

        void AsyncStop() {
            Clients->AsyncStop();
        }
    };


    TExtBuilderModule::TExtBuilderModule(const IServerConfig& serverConfig)
        : ExtConfig(*serverConfig.GetModuleConfig<TExtBuilderConfig>(NameStatic()))
        , DiagHelper(MakeIntrusive<TExtBuilderDebugHelper>())
        , AppObject(MakeIntrusive<TExtBuilderApp>(ExtConfig, DiagHelper))
    {
        NRTYServer::TExtBuilderEngine::Enable();
        AppObject->Start();
        //RegisterGlobalMessageProcessor(this);
    }

    TExtBuilderModule::~TExtBuilderModule() {
        NRTYServer::TExtBuilderEngine::Disable();
        //UnregisterGlobalMessageProcessor(this);
    }

    bool TExtBuilderModule::Start(const TStartContext& /*context*/) {
        NRTYServer::TExtBuilderEngine::RegisterConnection(new TLocalConn(ExtConfig, DiagHelper));
        NOTICE_LOG << "ExtBuilder: connection is active" << Endl;
        return true;
    }

    bool TExtBuilderModule::Stop(const TStopContext& context) {
        NRTYServer::IExtBuilderConnection::TPtr connection = NRTYServer::TExtBuilderEngine::GetConnection();
        if (context.GetRigidLevel() >= 1) {
            if (connection) {
                TLocalConn* conn = CheckedCast<TLocalConn*>(connection.Get());
                conn->AsyncStop();
            }
        }
        NRTYServer::TExtBuilderEngine::Disable();
        connection.Drop(); // (unlikely but) throws
        AppObject->Stop();
        NOTICE_LOG << "ExtBuilder: stopped" << Endl;
        return true;
    }

    bool TExtBuilderModule::Process(NMessenger::IMessage* /*message*/) {
        //TODO(yrum): implement info commands
        return false;
    }
}
