#pragma once

#include "options.h"
#include "thread_opts.h"

#include <saas/util/preprocessor.h>
#include <library/cpp/logger/global/common.h>

#include <library/cpp/http/server/options.h>
#include <library/cpp/yconf/conf.h>
#include <library/cpp/json/json_value.h>

#include <util/datetime/base.h>
#include <util/generic/ptr.h>
#include <util/generic/singleton.h>
#include <util/generic/string.h>
#include <util/generic/yexception.h>
#include <util/system/defaults.h>

#include <utility>

class TAnyDirective: public TYandexConfig::Directives {
public:
    TAnyDirective() : Directives(false) {}
};

class TAnyYandexConfig: public TYandexConfig {
public:
    bool OnBeginSection(Section& s) override {
        if (!s.Cookie) {
            s.Cookie = new TAnyDirective;
            s.Owner = true;
        }
        return true;
    }
};

const TYandexConfig::Section& GetSubSection(
    const TYandexConfig::Section& section,
    const TString& name);

template <class TSection>
class TSectionParser {
private:
    TSimpleSharedPtr<TSection> Section;
    struct TParsingValidator {
        TParsingValidator(bool parsed, TSection* section)
        {
            if (!parsed) {
                TString errors;
                section->PrintErrors(errors);
                ythrow yexception() << errors;
            }
        }
    };

    TParsingValidator Validator;

protected:
    typedef TSectionParser<TSection> TParser;
    const TYandexConfig::Section* ServerSection;

public:
    TSectionParser(const char* config, const TString& serverName)
        : Section(new TSection)
        , Validator(Section->ParseMemory(config), Section.Get())
        , ServerSection(&GetSubSection(*Section->GetRootSection(), serverName))
    {
    }
};

namespace NThreading {
    class TThreadingConfig;
}

class TDaemonConfig {
public:
    class THttpOptions : public THttpServerOptions {
    public:
        THttpOptions();
        THttpOptions(const THttpServerOptions& other);
        virtual ~THttpOptions() {}
        TString ToString(const TString& sectionName) const;
        virtual void Init(const TYandexConfig::Directives& directives, bool verifyThreads = true);
    protected:
        virtual void ToStringImpl(TStringStream& so) const;

    protected:
        TString BindAddress;
    };

    struct TConnection {
        TString Host;
        ui16 Port = 0;
        TDuration ConnectionTimeout;
        TDuration InteractionTimeout;
        void ToStringFields(TStringStream& ss) const;
    };

    class TControllerConfig : public THttpOptions {
    public:
        struct TDMOptions : public TConnection {
            bool Enabled = false;
            TString UriPrefix;
            TString CType;
            TString ServiceType;
            TString Service;
            TString Slot;
            TDuration Timeout = TDuration::Seconds(30);
            ui32 Attemptions = 3;

            NJson::TJsonValue Serialize() const;
            void Deserialize(const NJson::TJsonValue& json);
            void Init(const TYandexConfig::Section& section);
            TString ToString() const;
        };
    public:
        bool StartServer;
        bool Enabled;
        bool AutoStop;
        bool ConfigsControl;
        bool ReinitLogsOnRereadConfigs;
        bool EnableNProfile;
        TString ConfigsRoot;
        TString StateRoot;
        TString Log;
        TString VersionsFileName;

    public:
        TDMOptions DMOptions;
        TControllerConfig();
        NThreading::TThreadingConfig Threading;

        virtual void Init(const TYandexConfig::Directives& directives, bool verifyThreads = true) override;
        void Init(const TYandexConfig::Section& section, bool verifyThreads = true);
        TString GetStatusFileName() const;

    protected:
        virtual void ToStringImpl(TStringStream& so) const override;
    };

public:
    TDaemonConfig(const char* config, bool doInitLogs);

    void InitLogs();
    TString ToString(const char* sectionName) const;

    const TMap<TString, TString>& GetSpecialMessageProcessors() const {
        return SpecialProcessorsMap;
    }

    inline bool StartAsDaemon() const {
        return !!PidFile;
    }

    template <class T>
    void StartLogging(const TString& logType, TMaybe<int> logLevel = TMaybe<int>()) const {
        StartLoggingImpl<T>(logType, logLevel ? *logLevel : LogLevel);
    }

    template <class T>
    void StartLoggingUnfiltered(const TString& logType) const {
        StartLoggingImpl<T>(logType, LOG_MAX_PRIORITY);
    }

    const TControllerConfig& GetController() const {
        return Controller;
    }

    TControllerConfig& GetController() {
        return Controller;
    }

    inline const TString& GetPidFileName() const {
        return PidFile;
    }

    inline const TString& GetMetricsPrefix() const {
        return MetricsPrefix;
    }

    inline const TString& GetMetricsStorage() const {
        return MetricsStorage;
    }

    inline size_t GetMetricsMaxAge() const {
        return MetricsMaxAge;
    }

    inline const TString& GetLoggerType() const {
        return LoggerType;
    }

    inline void SetLoggerType(const TString& log) {
        LoggerType = log;
    }

    inline const TString& GetLoggerIName() const {
        return LoggerIName;
    }

    inline void SetLoggerIName(const TString& iname) {
        LoggerIName = iname;
    }

    inline const TString& GetStdOut() const {
        return StdOut;
    }

    inline void SetStdOut(const TString& log) {
        StdOut = log;
    }

    inline const TString& GetStdErr() const {
        return StdErr;
    }

    inline void SetStdErr(const TString& log) {
        StdErr = log;
    }

    inline int GetLogLevel() const {
        return LogLevel;
    }

    inline void SetLogLevel(int level) {
        LogLevel = level;
    }

    inline bool GetLogRotation() const {
        return LogRotation;
    }

    inline bool IsStatusControlEnabled() const {
        return EnableStatusControl;
    }

    inline bool ShouldLockExecutablePages() const {
        return LockExecutablePages;
    }

    void ReopenLog() const;

public:
    typedef std::pair<TString, ui16> TNetworkAddress;
    static TString DefaultEmptyConfig;

    // All daemons will parse these parameters from config
    static TNetworkAddress ParseAddress(
        const TYandexConfig::Directives& directives,
        const TString& prefix = "", const TString& defaultHost = "");
    static THttpServerOptions ParseHttpServerOptions(
        const TYandexConfig::Directives& directives,
        const TString& prefix = "", const TString& defaultHost = "");
    static TConnection ParseConnection(
        const TYandexConfig::Directives& directives,
        const TString& prefix = "",
        TDuration connectionTimetout = TDuration::MilliSeconds(30),
        TDuration interactionTimeout = TDuration::MilliSeconds(100));

private:
    TString PidFile;
    TString MetricsPrefix;
    size_t MetricsMaxAge;
    TString MetricsStorage;
    TString StdOut;
    TString StdErr;
    TString LoggerType;
    TString LoggerIName; //instance name for log aggregators
    int LogLevel;
    size_t LogQueueSize = 100000;
    bool LogRotation;
    bool LockExecutablePages = true;
    bool EnableStatusControl;
    TString SpecialProcessors;
    TMap<TString, TString> SpecialProcessorsMap;
    TControllerConfig Controller;

private:
    void ReplaceDescriptor(const TString& name, FHANDLE fd) const;
    template <class TLoggerType>
    void StartLoggingImpl(TString logType, const int logLevel) const {
        if (logLevel < 0 || logLevel > (int)LOG_MAX_PRIORITY)
            ythrow yexception() << "Incorrect priority";
        if (LogRotation && TFsPath(logType).Exists()) {
            TString newPath = Sprintf("%s_%s_%lu", logType.data(), NLoggingImpl::GetLocalTimeSSimple().data(), Now().MicroSeconds());
            TFsPath(logType).RenameTo(newPath);
        }
        if (StartAsDaemon() && (logType == "console" || logType == "cout" || logType == "cerr")) {
            logType = "null";
        }
        TLoggerOperator<TLoggerType>::Set(new TLoggerType(logType, (ELogPriority)logLevel, LogQueueSize));
    }
};

using TDaemonConfigPtr = TAtomicSharedPtr<TDaemonConfig>;

struct TServerConfigConstructorParams {
    TServerConfigConstructorParams(const char* text,
                                   const char* path = nullptr,
                                   TConfigPatcher* preprocessor = nullptr)
        : Text(text)
        , Daemon(new TDaemonConfig(text, false))
        , Path(path)
        , Preprocessor(preprocessor)
    {}
    TServerConfigConstructorParams(TDaemonOptions& options, const TString& text = Default<TString>())
        : Text(text ? text : options.RunPreprocessor())
        , Daemon(new TDaemonConfig(text.data(), false))
        , Path(options.GetConfigFileName())
        , Preprocessor(&options.GetPreprocessor())
    {}

    TString Text;
    TDaemonConfigPtr Daemon;
    TString Path;
    TConfigPatcher* Preprocessor;
};
