#pragma once

#include <util/datetime/base.h>

#include <util/folder/path.h>
#include <util/generic/singleton.h>
#include <util/generic/string.h>
#include <util/generic/ptr.h>
#include <util/generic/yexception.h>
#include <util/string/printf.h>
#include <util/system/src_location.h>

#include <library/cpp/logger/log.h>

namespace NLoggingImpl {
    const size_t SingletonPriority = 500;
}

template <class T>
T* CreateDefaultLogger() {
    return nullptr;
}

namespace NLoggingImpl { 
    template<class T, class TTraits> 
    class TOperatorBase { 
        struct TPtr { 
            TPtr() 
                : Instance(TTraits::CreateDefault()) 
            { 
            } 
 
            THolder<T> Instance; 
        }; 
 
    public: 
        inline static bool Usage() { 
            return SingletonWithPriority<TPtr, SingletonPriority>()->Instance.Get();
        }

        inline static T* Get() { 
            return SingletonWithPriority<TPtr, SingletonPriority>()->Instance.Get();
        } 
 
        inline static void Set(T* v) { 
            SingletonWithPriority<TPtr, SingletonPriority>()->Instance.Reset(v);
        } 
    };

    template<class T> 
    struct TLoggerTraits { 
        static T* CreateDefault() { 
            return CreateDefaultLogger<T>(); 
        } 
    }; 
} 

template <class T> 
class TLoggerOperator : public NLoggingImpl::TOperatorBase<T, NLoggingImpl::TLoggerTraits<T>>  { 
public: 
    inline static TLog& Log() {
        Y_ASSERT(TLoggerOperator::Usage()); 
        return *TLoggerOperator::Get(); 
    }
};

namespace NLoggingImpl {

    TString GetLocalTimeSSimple();

    // Returns correct log type to use
    TString PrepareToOpenLog(TString logType, int logLevel, bool rotation, bool startAsDaemon);
 
    template <class TLoggerType> 
    void InitLogImpl(TString logType, const int logLevel, const bool rotation, const bool startAsDaemon) { 
        TLoggerOperator<TLoggerType>::Set(new TLoggerType(PrepareToOpenLog(logType, logLevel, rotation, startAsDaemon), (ELogPriority)logLevel));
    } 
}

struct TLogRecordContext {
    constexpr TLogRecordContext(const TSourceLocation& sourceLocation, TStringBuf customMessage, ELogPriority priority)
        : SourceLocation(sourceLocation)
        , CustomMessage(customMessage)
        , Priority(priority)
    {}

    TSourceLocation SourceLocation;
    TStringBuf CustomMessage;
    ELogPriority Priority;
};

template <class... R>
struct TLogRecordPreprocessor;

template <>
struct TLogRecordPreprocessor<> {
    inline static bool CheckLoggingContext(TLog& /*log*/, const TLogRecordContext& /*context*/) {
        return true;
    }

    inline static TSimpleSharedPtr<TLogElement> StartRecord(TLog& log, const TLogRecordContext& context, TSimpleSharedPtr<TLogElement> earlier) {
        if (earlier)
            return earlier;
        TSimpleSharedPtr<TLogElement> result(new TLogElement(&log));
        *result << context.Priority;
        return result;
    }
};

template <class H, class... R>
struct TLogRecordPreprocessor<H, R...> {
    inline static bool CheckLoggingContext(TLog& log, const TLogRecordContext& context) {
        return H::CheckLoggingContext(log, context) && TLogRecordPreprocessor<R...>::CheckLoggingContext(log, context);
    }

    inline static TSimpleSharedPtr<TLogElement> StartRecord(TLog& log, const TLogRecordContext& context, TSimpleSharedPtr<TLogElement> earlier) {
        TSimpleSharedPtr<TLogElement> first = H::StartRecord(log, context, earlier);
        return TLogRecordPreprocessor<R...>::StartRecord(log, context, first);
    }
};

struct TLogFilter {
    static bool CheckLoggingContext(TLog& log, const TLogRecordContext& context);
    static TSimpleSharedPtr<TLogElement> StartRecord(TLog& log, const TLogRecordContext& context, TSimpleSharedPtr<TLogElement> earlier);
};

class TNullLog;

template <class TPreprocessor>
TSimpleSharedPtr<TLogElement> GetLoggerForce(TLog& log, const TLogRecordContext& context) {
    if (TSimpleSharedPtr<TLogElement> result = TPreprocessor::StartRecord(log, context, nullptr))
        return result;
    return new TLogElement(&TLoggerOperator<TNullLog>::Log());
}

namespace NPrivateGlobalLogger {
    struct TEatStream {
        Y_FORCE_INLINE bool operator|(const IOutputStream&) const {
            return true;
        }
    };
}

#define LOGGER_GENERIC_LOG_CHECKED(logger, preprocessor, level, message) (*GetLoggerForce<preprocessor>(logger, TLogRecordContext(__LOCATION__, message, level)))
#define LOGGER_CHECKED_GENERIC_LOG(logger, preprocessor, level, message) \
    (preprocessor::CheckLoggingContext(logger, TLogRecordContext(__LOCATION__, message, level))) && NPrivateGlobalLogger::TEatStream() | (*(preprocessor::StartRecord(logger, TLogRecordContext(__LOCATION__, message, level), nullptr)))

#define SINGLETON_GENERIC_LOG_CHECKED(type, preprocessor, level, message) LOGGER_GENERIC_LOG_CHECKED(TLoggerOperator<type>::Log(), preprocessor, level, message)
#define SINGLETON_CHECKED_GENERIC_LOG(type, preprocessor, level, message) LOGGER_CHECKED_GENERIC_LOG(TLoggerOperator<type>::Log(), preprocessor, level, message)