// Copyright 2023 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
#ifndef BASE_INCLUDE_LOG_LOGGING_H_
#define BASE_INCLUDE_LOG_LOGGING_H_

#include <cstdint>
#include <memory>
#include <sstream>
#include <string>

#include "base/include/base_export.h"
#include "base/include/log/alog_wrapper.h"
#include "base/include/log/log_stream.h"
#include "base/include/path_utils.h"

namespace lynx {
namespace base {
namespace logging {

class LogMessage;

using PlatformLogCallBack = void (*)(LogMessage* msg, const char* tag);
using InitAlogCallBack = alog_write_func_ptr (*)();

BASE_EXPORT void InitLynxLogging(InitAlogCallBack initAlogCallback,
                                 PlatformLogCallBack PlatformLogCallBack,
                                 bool isPrintAllLogToAllChannels);

// Updates native filtering only. Platform setters use this as their leaf call.
BASE_EXPORT void SetMinLogLevel(int level);
// Updates platform and native filtering through the platform setter.
// Harmony requires logger initialization and its owning ArkTS thread.
// Calls that violate the Harmony preconditions are logged and ignored.
BASE_EXPORT void SetPlatformMinLogLevel(int level);

namespace detail {
// Internal INFO sublevels. NOTE: 0 is reserved for the original INFO value;
// future INFO sublevels must be nonzero to avoid that ambiguity.
enum InfoLogLevel {
  INFO_LEVEL_MONITOR = 5,
  INFO_LEVEL_OBSERVE = 10,
  INFO_LEVEL_INFO = 15
};
// Shared storage for the inline getter. Update through SetMinLogLevel().
BASE_EXPORT extern int32_t g_min_log_level;
BASE_EXPORT extern int32_t g_info_log_level;
}  // namespace detail

inline int GetMinLogLevel() { return detail::g_min_log_level; }
inline int GetInfoLogLevel() { return detail::g_info_log_level; }

// Converts a configurable threshold name (including MONITOR/OBSERVE).
// Returns -1 for unknown names; FATAL is not a configurable threshold.
BASE_EXPORT int ParseLogLevel(const std::string& name);
BASE_EXPORT const char* GetMinLogLevelName();
// Lowest LOG macro level retained by the native logging build. INFO includes
// MONITOR/OBSERVE, so its lowest retained level is reported as MONITOR.
BASE_EXPORT const char* GetCompileMinLogLevelName();

BASE_EXPORT void PrintLogToLynxLogging(int level, const char* tag,
                                       const char* message);

bool HasInitedLynxLogWriteFunction();
void EnableLogOutputByPlatform();
void DisableLogOutputByPlatform();

#define LYNX_LOG_LEVEL_VERBOSE 0
#define LYNX_LOG_LEVEL_DEBUG 1
#define LYNX_LOG_LEVEL_INFO 2
#define LYNX_LOG_LEVEL_WARNING 3
#define LYNX_LOG_LEVEL_ERROR 4
#define LYNX_LOG_LEVEL_FATAL 5
#define LYNX_LOG_LEVEL_NUM 6

typedef int LogSeverity;
const LogSeverity LOG_VERBOSE = LYNX_LOG_LEVEL_VERBOSE;
const LogSeverity LOG_DEBUG = LYNX_LOG_LEVEL_DEBUG;
const LogSeverity LOG_INFO = LYNX_LOG_LEVEL_INFO;
const LogSeverity LOG_WARNING = LYNX_LOG_LEVEL_WARNING;
const LogSeverity LOG_ERROR = LYNX_LOG_LEVEL_ERROR;
// The error in windi.h in the windows environment will pollute the LOG_0 here,
// and a LOG_0 needs to be defined directly
#if defined(_WIN32) && !defined(LOG_0)
#define LOG_0 LOG_ERROR
#endif
const LogSeverity LOG_FATAL = LYNX_LOG_LEVEL_FATAL;
const LogSeverity LOG_NUM_SEVERITIES = LYNX_LOG_LEVEL_NUM;

namespace detail {
// Internal configuration values only, not output severities or client APIs.
// Low 16 bits retain INFO; high 16 bits select its nonzero sublevel.
constexpr int LOG_MONITOR = (INFO_LEVEL_MONITOR << 16) | LOG_INFO;
constexpr int LOG_OBSERVE = (INFO_LEVEL_OBSERVE << 16) | LOG_INFO;
}  // namespace detail

// !!! Need to be aligned with platform layer. !!!
// !!! in file <LogSource.java> !!!
typedef int LogSource;
const LogSource LOG_SOURCE_NATIVE = 0;
const LogSource LOG_SOURCE_JS = 1;
const LogSource LOG_SOURCE_JS_EXT =
    2;  // used for console.alog & console.report

// channel type from lynx internal and external developer
using LogChannel = int32_t;
constexpr LogChannel LOG_CHANNEL_LYNX_INTERNAL = 0;
constexpr LogChannel LOG_CHANNEL_LYNX_EXTERNAL = 1;

// A lightweight class that consumes and discards all stream inputs.
class NullLogStream {
 public:
  NullLogStream() {}
  template <typename T>
  NullLogStream& operator<<(T) {
    return *this;
  }
};

// This class is used to explicitly ignore values in the conditional
// logging macros.  This avoids compiler warnings like "value computed
// is not used" and "statement has no effect".
class LogMessageVoidify {
 public:
  LogMessageVoidify() {}
  // This has to be an operator with a precedence lower than << but
  // higher than ?:
  void operator&(LogStream&) {}
  void operator&(const NullLogStream&) {}
};

#ifdef __FILE_NAME__
#define __LOG_FILE_NAME__ __FILE_NAME__
#else
#define __LOG_FILE_NAME__ \
  lynx::base::PathUtils::GetLastPath(__FILE__, sizeof(__FILE__) - 1)
#endif

#define LOG_IS_ON(severity)                 \
  ((lynx::base::logging::LOG_##severity) >= \
   lynx::base::logging::GetMinLogLevel())

#define LOG_STREAM(severity)                                           \
  lynx::base::logging::LogMessage(__LOG_FILE_NAME__, __LINE__,         \
                                  lynx::base::logging::LOG_##severity) \
      .stream()

#define LOG_STREAM_INFO \
  lynx::base::logging::LogMessageI(__LOG_FILE_NAME__, __LINE__).stream()

#define LOG_STREAM_WARNING \
  lynx::base::logging::LogMessageW(__LOG_FILE_NAME__, __LINE__).stream()

#define LOG_STREAM_ERROR \
  lynx::base::logging::LogMessageE(__LOG_FILE_NAME__, __LINE__).stream()

#define JS_LOG_STREAM(severity, source, runtime_id, channel_type)       \
  lynx::base::logging::LogMessage(                                      \
      __LOG_FILE_NAME__, __LINE__, lynx::base::logging::LOG_##severity, \
      lynx::base::logging::LOG_SOURCE_JS, runtime_id, channel_type)     \
      .stream()

#define JS_ALOG_STREAM(severity, source, runtime_id, channel_type)      \
  lynx::base::logging::LogMessage(                                      \
      __LOG_FILE_NAME__, __LINE__, lynx::base::logging::LOG_##severity, \
      lynx::base::logging::LOG_SOURCE_JS_EXT, runtime_id, channel_type) \
      .stream()

#define LAZY_STREAM(stream, condition) \
  !(condition) ? (void)0 : lynx::base::logging::LogMessageVoidify() & (stream)

// Add a new FML-style LOG macro
#define BASE_LOG(severity) \
  LAZY_STREAM(LOG_STREAM(severity), LOG_IS_ON(severity))

// Use this macro to suppress warning if the variable in log is not used.
#define UNUSED_LOG_VARIABLE __attribute__((unused))

#ifndef LYNX_MIN_LOG_LEVEL
#ifdef NDEBUG
#define LYNX_MIN_LOG_LEVEL LYNX_LOG_LEVEL_INFO
#else
#define LYNX_MIN_LOG_LEVEL LYNX_LOG_LEVEL_VERBOSE
#endif
#endif

// TODO(zhixuan): Currently, the usage of log macros is like "LOGI("abc" <<
// variable)", which is mixed of stream pattern and format string pattern.
// Change the logging fashion entirely to format string pattern in future.
#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_VERBOSE
#define LOGV(msg) \
  { LAZY_STREAM(LOG_STREAM(VERBOSE), LOG_IS_ON(VERBOSE)) << msg; }
#else
#define LOGV(msg)
#endif

#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_DEBUG
#define LOGD(msg) \
  { LAZY_STREAM(LOG_STREAM(DEBUG), LOG_IS_ON(DEBUG)) << msg; }
#else
#define LOGD(msg)
#endif

// TODO: Preserve M/O identity if output consumers need it in the future.
#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_INFO
// Include compile-time availability so callers can skip preparing arguments.
#define LOGO_IS_ON()                                            \
  (LOG_IS_ON(INFO) && lynx::base::logging::GetInfoLogLevel() <= \
                          lynx::base::logging::detail::INFO_LEVEL_OBSERVE)
#define LOGM(msg)                                          \
  do {                                                     \
    if (lynx::base::logging::GetInfoLogLevel() <=          \
        lynx::base::logging::detail::INFO_LEVEL_MONITOR) { \
      LOGI(msg);                                           \
    }                                                      \
  } while (0)
#define LOGO(msg)                                      \
  do {                                                 \
    LAZY_STREAM(LOG_STREAM_INFO, LOGO_IS_ON()) << msg; \
  } while (0)
#else
#define LOGO_IS_ON() false
#define LOGM(msg)
#define LOGO(msg)
#endif

#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_INFO
#define LOGI(msg) \
  { LAZY_STREAM(LOG_STREAM_INFO, LOG_IS_ON(INFO)) << msg; }
#else
#define LOGI(msg)
#endif

#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_WARNING
#define LOGW(msg) \
  { LAZY_STREAM(LOG_STREAM_WARNING, LOG_IS_ON(WARNING)) << msg; }
#else
#define LOGW(msg)
#endif

#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_ERROR
#define LOGE(msg) \
  { LAZY_STREAM(LOG_STREAM_ERROR, LOG_IS_ON(ERROR)) << msg; }
#else
#define LOGE(msg)
#endif

#if LYNX_MIN_LOG_LEVEL <= LYNX_LOG_LEVEL_FATAL
#define LOGF(msg) \
  { LAZY_STREAM(LOG_STREAM(FATAL), LOG_IS_ON(FATAL)) << msg; }
#else
#define LOGF(msg)
#endif

#define JSLOG(severity, runtime_id, channel_type) \
  LAZY_STREAM(JS_LOG_STREAM(severity, JS, runtime_id, channel_type), true)

#define JSALOG(severity, runtime_id, channel_type) \
  LAZY_STREAM(JS_ALOG_STREAM(severity, JS, runtime_id, channel_type), true)

#define CHECK(condition)                       \
  LAZY_STREAM(LOG_STREAM(FATAL), !(condition)) \
      << "Check failed: " #condition ". "

// now support DCHECK(...) << ...
#ifndef DCHECK
// for debug, if check failed, log fatal and abort
#ifndef NDEBUG
#define DCHECK(condition) CHECK(condition)
#else
// In release builds, DCHECK is a no-op that still consumes the stream.
#define DCHECK(condition)                                          \
  true || (condition) ? (void)0                                    \
                      : lynx::base::logging::LogMessageVoidify() & \
                            lynx::base::logging::NullLogStream()
#endif
#endif

#define DCHECK_EQ(v1, v2) DCHECK((v1) == (v2))

#if defined(__clang__) || defined(__GNUC__)
#define LYNX_BUILTIN_UNREACHABLE() __builtin_unreachable()
#elif defined(_MSC_VER)
#define LYNX_BUILTIN_UNREACHABLE() __assume(false)
#else
#define LYNX_BUILTIN_UNREACHABLE() abort()
#endif

#undef NOTREACHED
#define NOTREACHED()            \
  do {                          \
    LOGF("Abort here!!!");      \
    LYNX_BUILTIN_UNREACHABLE(); \
  } while (0);

// A macro to provide a stream for logging a function that is not implemented.
// In debug builds, it will log an ERROR. In release builds, it's a no-op.
#ifndef NDEBUG
#define UNIMPLEMENTED() \
  BASE_LOG(ERROR) << "Not implemented in " << __FUNCTION__ << "() ";
#else
#define UNIMPLEMENTED() (void)0;
#endif

class BASE_EXPORT LogMessage {
 public:
  LogMessage(const char* file, int line, LogSeverity severity, LogSource source,
             int64_t rt_id, LogChannel channel_type);
  LogMessage(const char* file, int line, LogSeverity severity);
  ~LogMessage();
  LogStream& stream() { return stream_; }
  LogSeverity severity() { return severity_; }
  LogSource source() { return source_; }
  size_t messageStart() { return message_start_; }
  int64_t runtimeId() { return runtime_id_; }
  LogChannel ChannelType() { return channel_type_; }

 private:
  void Init(const char* file, int line);

  LogSeverity severity_;
  LogStream stream_;
  size_t message_start_;

  const char* file_;
  const int line_;
  LogSource source_;
  int64_t runtime_id_;
  LogChannel channel_type_;
  LogMessage(const LogMessage&) = delete;
  LogMessage& operator=(const LogMessage&) = delete;
};

/**
 Specialized for different log levels to omit severity argument
 at callsite for binary size optimization.
 */
class BASE_EXPORT LogMessageI : public LogMessage {
 public:
  LogMessageI(const char* file, int line) : LogMessage(file, line, LOG_INFO) {}
};

class BASE_EXPORT LogMessageW : public LogMessage {
 public:
  LogMessageW(const char* file, int line)
      : LogMessage(file, line, LOG_WARNING) {}
};

class BASE_EXPORT LogMessageE : public LogMessage {
 public:
  LogMessageE(const char* file, int line) : LogMessage(file, line, LOG_ERROR) {}
};

}  // namespace logging
}  // namespace base
}  // namespace lynx

#endif  // BASE_INCLUDE_LOG_LOGGING_H_