#pragma once

#include <saas/library/daemon_base/config/daemon_config.h>
#include <library/cpp/object_factory/object_factory.h>
#include <library/cpp/logger/global/global.h>

#include <library/cpp/cgiparam/cgiparam.h>
#include <util/generic/cast.h>
#include <util/generic/noncopyable.h>
#include <util/generic/maybe.h>

class IServerConfig;

class IAbstractModuleConfig {
public:
    typedef TAtomicSharedPtr<IAbstractModuleConfig> TPtr;
    virtual ~IAbstractModuleConfig() {}
    virtual bool Init(const IServerConfig& serverConfig, const TYandexConfig::Section* componentsSection, const TString& componentName) = 0;
    virtual bool Check() const = 0;
    virtual void ToString(IOutputStream& so) const = 0;
    virtual bool ConfigurationExists() const = 0;
};

class IDaemonModuleConfig: public IAbstractModuleConfig {
public:
    typedef NObjectFactory::TObjectFactory<IDaemonModuleConfig, TString> TFactory;
};

class IServerConfig: public TNonCopyable {
public:
    virtual ~IServerConfig() {}
    virtual TSet<TString> GetModulesSet() const = 0;
    virtual TVector<TString> GetModulesVector() const { // override if you need a non-alphabetic init order
        auto s = GetModulesSet();
        return TVector<TString>(s.cbegin(), s.cend());
    }
    virtual const TDaemonConfig& GetDaemonConfig() const = 0;
    template<class T>
    const T& GetMeAs() const {
        return *VerifyDynamicCast<const T*>(this);
    }
    template <class T>
    T& GetMeAs() {
        return *VerifyDynamicCast<T*>(this);
    }
    template <class T>
    T* SafeGetMeAs() {
        return dynamic_cast<T*>(this);
    }
    template <class T>
    const T* SafeGetMeAs() const {
        return dynamic_cast<T*>(this);
    }
    template <class T>
    const T* GetModuleConfig(const TStringBuf name) const {
        return VerifyDynamicCast<const T*>(GetModuleConfigImpl(name));
    }

protected:
    virtual const IAbstractModuleConfig* GetModuleConfigImpl(const TStringBuf /*name*/) const {
        return nullptr;
    }
};

template<class TBaseClass>
class TPluginConfig: public TBaseClass {
private:
    bool ConstructedWithConfiguration = false;
public:

    virtual bool ConfigurationExists() const override {
        return ConstructedWithConfiguration;
    }

    virtual bool Check() const override {
        return DoCheck();
    }

    virtual bool Init(const IServerConfig& serverConfig, const TYandexConfig::Section* componentsSection, const TString& componentName) override {
        Owner = &serverConfig;
        ComponentName = componentName;
        if (componentsSection) {
            const TYandexConfig::TSectionsMap children = componentsSection->GetAllChildren();
            const TYandexConfig::TSectionsMap::const_iterator iter = children.find(ComponentName);
            if (iter != children.end()) {
                ConstructedWithConfiguration = true;
                DoInit(*iter->second);
                return true;
            }
        }
        return false;
    }

    const IServerConfig& GetOwner() const {
        CHECK_WITH_LOG(Owner);
        return *Owner;
    }

    virtual void ToString(IOutputStream& so) const override {
        if (ConstructedWithConfiguration) {
            so << "<" << ComponentName << ">" << Endl;
            DoToString(so);
            so << "</" << ComponentName << ">" << Endl;
        }
    }

    TString ComponentName;
protected:
    virtual bool DoCheck() const = 0;
    virtual void DoInit(const TYandexConfig::Section& componentSection) = 0;
    virtual void DoToString(IOutputStream& so) const = 0;
    const IServerConfig* Owner;
};

template <class IConfig>
class TPluginConfigsBase {
public:
    typedef THashMap<TString, typename IConfig::TPtr> TConfigHashMap;

public:
    virtual ~TPluginConfigsBase() {}

    template <class T>
    const T* Get(const TStringBuf pluginName) const {
        typename TConfigHashMap::const_iterator i = ConfigHashMap.find(pluginName);
        return i == ConfigHashMap.end() ? nullptr : VerifyDynamicCast<const T*>(i->second.Get());
    }
    template <class T>
    const T& GetNotNull(const TStringBuf pluginName) const {
        const T* CurObject = std::get<T>(pluginName);
        CHECK_WITH_LOG(CurObject) << "Null pointer for plugin '" << pluginName << "'";
        return *CurObject;
    }
    bool Check() const {
        for (typename TConfigHashMap::const_iterator i = ConfigHashMap.begin(); i != ConfigHashMap.end(); ++i)
            if (i->second->ConfigurationExists() && !i->second->Check())
                return false;
        return true;
    }

    void ToString(IOutputStream* so) const {
        *so << "<" << Name << ">" << Endl;
        for (typename TConfigHashMap::const_iterator i = ConfigHashMap.begin(); i != ConfigHashMap.end(); ++i)
            i->second->ToString(*so);
        *so << "</" << Name << ">" << Endl;
    }

    void Init(const IServerConfig& serverConfig, const TYandexConfig::TSectionsMap& sections) {
        ConfigHashMap.clear();
        TYandexConfig::TSectionsMap::const_iterator iter = sections.find(Name);
        const TYandexConfig::Section* ccSection = iter == sections.end() ? nullptr : iter->second;
        InitFromSection(serverConfig, ccSection);
    }

    TSet<TString> GetModules() const {
        TSet<TString> result;
        for (auto i : ConfigHashMap) {
            result.insert(i.first);
        }
        return result;
    }

protected:
    TPluginConfigsBase(const TString& name)
        : Name(name)
    {}

    virtual void InitFromSection(const IServerConfig& serverConfig, const TYandexConfig::Section* ccSection) = 0;

protected:
    TConfigHashMap ConfigHashMap;
    const TString Name;
};

template <class IConfig>
class TPluginConfigs : public TPluginConfigsBase<IConfig> {
public:
    typedef TPluginConfigsBase<IConfig> TBase;
    TPluginConfigs(const IServerConfig& serverConfig, const TString& name)
        : TBase(name)
    {
        InitFromSection(serverConfig, nullptr);
    }

protected:
    virtual void InitFromSection(const IServerConfig& serverConfig, const TYandexConfig::Section* ccSection) override {
        TSet<TString> componentConfigsNames;
        Singleton<typename IConfig::TFactory>()->GetKeys(componentConfigsNames);
        for (TSet<TString>::const_iterator i = componentConfigsNames.begin(); i != componentConfigsNames.end(); ++i) {
            INFO_LOG << TBase::Name << " section reading... " << *i << Endl;
            typename IConfig::TPtr cc(IConfig::TFactory::Construct(*i));
            VERIFY_WITH_LOG(!!cc, "cannot construct config for %s", i->c_str());
            cc->Init(serverConfig, ccSection, *i);
            INFO_LOG << TBase::Name << " section saved  " << *i << Endl;
            TBase::ConfigHashMap[*i] = cc;
            INFO_LOG << TBase::Name << " section reading... " << *i << " OK" << Endl;
        }
    }
};

template <class IConfig>
class TTypedPluginConfigs: public TPluginConfigsBase<IConfig> {
public:
    typedef TPluginConfigsBase<IConfig> TBase;
    TTypedPluginConfigs(const TString& name)
        : TBase(name)
    {}

protected:
    virtual void InitFromSection(const IServerConfig& serverConfig, const TYandexConfig::Section* ccSection) override {
        if (!ccSection)
            return;

        TYandexConfig::TSectionsMap children = ccSection->GetAllChildren();
        for (const auto& child : children) {
            INFO_LOG << TBase::Name << " section reading... " << child.first << Endl;
            TString type;
            if (!child.second->GetDirectives().GetValue("Type", type))
                ythrow yexception() << "there is no Type for " << child.first;

            typename IConfig::TPtr cc(IConfig::TFactory::Construct(type, type));
            VERIFY_WITH_LOG(!!cc, "cannot construct config for %s", type.data());
            cc->Init(serverConfig, ccSection, child.first);
            INFO_LOG << TBase::Name << " section saved  " << child.first << Endl;
            TBase::ConfigHashMap[child.first] = cc;
            INFO_LOG << TBase::Name << " section reading... " << child.first << " OK" << Endl;
        }
    }
};

class IWatchdogOptions;

class IDaemonModule {
public:
    struct TStartContext {
    private:
        IWatchdogOptions* WatchdogOpts;
    public:
        TStartContext(IWatchdogOptions* watchdogOpts=nullptr)
            : WatchdogOpts(watchdogOpts)
        {}

        inline bool HasWatchdogOpts() const {
            return !!WatchdogOpts;
        }

        inline IWatchdogOptions& GetWatchdogOpts() const {
            CHECK_WITH_LOG(HasWatchdogOpts());
            return *WatchdogOpts;
        }
    };

    struct TStopContext {
    private:
        ui32 RigidLevel;
        TMaybe<ui32> DFState;
    public:
        ui32 GetRigidLevel() const {
            return RigidLevel;
        }

        const TMaybe<ui32>& GetDFState() const {
            return DFState;
        }

        TStopContext(ui32 rigidLevel, const TCgiParameters* cgiParams) {
            RigidLevel = rigidLevel;
            if (cgiParams && cgiParams->Has("dfstarttime")) {
                ui32 value;
                if (TryFromString<ui32>(cgiParams->Get("dfstarttime"), value)) {
                    DFState = value;
                }
            }
        }
    };

    typedef NObjectFactory::TParametrizedObjectFactory<IDaemonModule, TString, const TDaemonConfig&> TFactory;
    virtual ~IDaemonModule() {}
    virtual bool Start(const TStartContext& startContext) = 0;
    virtual bool Stop(const TStopContext& stopContext) = 0;
    virtual TString Name() const = 0;
};

class TDaemonModules {
private:
    const IServerConfig& Config;
    TVector<TAtomicSharedPtr<IDaemonModule> > Modules;
public:
    typedef NObjectFactory::TParametrizedObjectFactory<IDaemonModule, TString, const IServerConfig&> TFactory;

    TDaemonModules(const IServerConfig& config)
        : Config(config)
    {
        const auto objectNames = Config.GetModulesVector();
        for (auto i : objectNames) {
            IDaemonModule* product = TFactory::Construct(i, Config);
            if (!product)
                product = IDaemonModule::TFactory::Construct(i, Config.GetDaemonConfig());
            VERIFY_WITH_LOG(!!product, "Incorrect module name: %s", i.data());
            Modules.push_back(product);
        }
    }

    virtual ~TDaemonModules() {
        for (i32 module = Modules.size() - 1; module >= 0; --module) {
            Modules[module].Drop();
        }
    }

    bool Start(const IDaemonModule::TStartContext& startContext) {
        for (i32 module = 0; module < Modules.ysize(); ++module) {
            DEBUG_LOG << "Module " << Modules[module]->Name() << " starting" << Endl;
            VERIFY_WITH_LOG(Modules[module]->Start(startContext), "incorrect start method result for %s module", Modules[module]->Name().data());
            DEBUG_LOG << "Module " << Modules[module]->Name() << " started" << Endl;
        }
        return true;
    }

    bool Stop(const IDaemonModule::TStopContext& stopContext) {
        for (i32 module = Modules.size() - 1; module >= 0; --module) {
            DEBUG_LOG << "Module " << Modules[module]->Name() << " stopping" << Endl;
            VERIFY_WITH_LOG(Modules[module]->Stop(stopContext), "incorrect stop method result for %s module", Modules[module]->Name().data());
            DEBUG_LOG << "Module " << Modules[module]->Name() << " stopped" << Endl;
        }
        return true;
    }

    TString Name() const {
        return "modules_container";
    }
};
