/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <atomic>
#include <cstdint>
#include <list>
#include <memory>
#include <string>
#include <unordered_map>

#include <folly/Range.h>
#include <folly/Synchronized.h>
#include <folly/logging/LogLevel.h>

namespace folly {

class LoggerDB;
class LogHandler;
class LogMessage;

/**
 * LogCategory stores all of the logging configuration for a specific
 * log category.
 *
 * This class is separate from Logger to allow multiple Logger objects to all
 * refer to the same log category.  Logger can be thought of as a small wrapper
 * class that behaves like a pointer to a LogCategory object.
 */
class LogCategory {
 public:
  /**
   * Create the root LogCategory.
   *
   * This should generally only be invoked by LoggerDB.
   */
  explicit LogCategory(LoggerDB* db);

  /**
   * Create a new LogCategory.
   *
   * This should only be invoked by LoggerDB, while holding the main LoggerDB
   * lock.
   *
   * The name argument should already be in canonical form.
   *
   * This constructor automatically adds this new LogCategory to the parent
   * category's firstChild_ linked-list.
   */
  LogCategory(folly::StringPiece name, LogCategory* parent);

  /**
   * Get the name of this log category.
   */
  const std::string& getName() const { return name_; }

  /**
   * Get the level for this log category.
   */
  LogLevel getLevel() const {
    return static_cast<LogLevel>(
        level_.load(std::memory_order_acquire) & ~FLAG_INHERIT);
  }

  /**
   * Get the log level and inheritance flag.
   */
  std::pair<LogLevel, bool> getLevelInfo() const {
    auto value = level_.load(std::memory_order_acquire);
    return {
        static_cast<LogLevel>(value & ~FLAG_INHERIT),
        bool(value & FLAG_INHERIT)};
  }

  /**
   * Get the effective level for this log category.
   *
   * This is the minimum log level of this category and all of its parents.
   * Log messages below this level will be ignored, while messages at or
   * above this level need to be processed by this category or one of its
   * parents.
   */
  LogLevel getEffectiveLevel() const {
    return effectiveLevel_.load(std::memory_order_acquire);
  }

  /**
   * Get the effective log level using std::memory_order_relaxed.
   *
   * This is primarily used for log message checks.  Most other callers should
   * use getEffectiveLevel() above to be more conservative with regards to
   * memory ordering.
   */
  LogLevel getEffectiveLevelRelaxed() const {
    return effectiveLevel_.load(std::memory_order_relaxed);
  }

  /**
   * Check whether this Logger or any of its parent Loggers would do anything
   * with a log message at the given level.
   */
  bool logCheck(LogLevel level) const {
    // We load the effective level using std::memory_order_relaxed.
    //
    // We want to make log checks as lightweight as possible.  It's fine if we
    // don't immediately respond to changes made to the log level from other
    // threads.  We can wait until some other operation triggers a memory
    // barrier before we honor the new log level setting.  No other memory
    // accesses depend on the log level value.  Callers should not rely on all
    // other threads to immediately stop logging as soon as they decrease the
    // log level for a given category.
    return effectiveLevel_.load(std::memory_order_relaxed) <= level;
  }

  /**
   * Set the log level for this LogCategory.
   *
   * Messages logged to a specific log category will be ignored unless the
   * message log level is greater than the LogCategory's effective log level.
   *
   * If inherit is true, LogCategory's effective log level is the minimum of
   * its level and its parent category's effective log level.  If inherit is
   * false, the LogCategory's effective log level is simply its log level.
   * (Setting inherit to false is necessary if you want a child LogCategory to
   * use a less verbose level than its parent categories.)
   */
  void setLevel(LogLevel level, bool inherit = true);

  /**
   * Set which messages processed by this category will be propagated up to the
   * parent category
   *
   * The default is `LogLevel::MIN_LEVEL` meaning that all messages will be
   * passed to the parent. You can set this to any higher log level to prevent
   * some messages being passed to the parent. `LogLevel::MAX_LEVEL` is a good
   * choice if you've attached a Handler to this category and you don't want
   * any of these logs to also appear in the parent's Handler.
   */
  void setPropagateLevelMessagesToParent(LogLevel level);

  /**
   * Get which messages processed by this category will be processed by the
   * parent category
   */
  LogLevel getPropagateLevelMessagesToParentRelaxed() const;

  /**
   * Get the LoggerDB that this LogCategory belongs to.
   *
   * This is almost always the main LoggerDB singleton returned by
   * LoggerDB::get().  The logging unit tests are the main location that
   * creates alternative LoggerDB objects.
   */
  LoggerDB* getDB() const { return db_; }

  /**
   * Attach a LogHandler to this category.
   */
  void addHandler(std::shared_ptr<LogHandler> handler);

  /**
   * Remove all LogHandlers from this category.
   */
  void clearHandlers();

  /**
   * Get the list of LogHandlers attached to this category.
   */
  std::vector<std::shared_ptr<LogHandler>> getHandlers() const;

  /**
   * Replace the list of LogHandlers with a completely new list.
   */
  void replaceHandlers(std::vector<std::shared_ptr<LogHandler>> handlers);

  /**
   * Update the LogHandlers attached to this LogCategory by replacing
   * currently attached handlers with new LogHandler objects.
   *
   * The handlerMap argument is a map of (old_handler -> new_handler)
   * If any of the LogHandlers currently attached to this category are found in
   * the handlerMap, replace them with the new handler indicated in the map.
   *
   * This is used when the LogHandler configuration is changed requiring one or
   * more LogHandler objects to be replaced with new ones.
   */
  void updateHandlers(const std::unordered_map<
                      std::shared_ptr<LogHandler>,
                      std::shared_ptr<LogHandler>>& handlerMap);

  /* Internal methods for use by other parts of the logging library code */

  /**
   * Admit a message into the LogCategory hierarchy to be logged.
   *
   * The caller is responsible for having already performed log level
   * admittance checks.
   *
   * This method generally should be invoked only through the logging macros,
   * rather than calling this directly.
   */
  void admitMessage(const LogMessage& message) const;

  /**
   * Note: setLevelLocked() may only be called while holding the
   * LoggerDB loggersByName_ lock.  It is safe to call this while holding the
   * loggersByName_ lock in read-mode; holding it exclusively is not required.
   *
   * This method should only be invoked by LoggerDB.
   */
  void setLevelLocked(LogLevel level, bool inherit);

  /**
   * Register a std::atomic<LogLevel> value used by XLOG*() macros to check the
   * effective level for this category.
   *
   * The LogCategory will keep this value updated whenever its effective log
   * level changes.
   *
   * This function should only be invoked by LoggerDB, and the LoggerDB lock
   * must be held when calling it.
   */
  void registerXlogLevel(std::atomic<LogLevel>* levelPtr);

 private:
  enum : uint32_t { FLAG_INHERIT = 0x80000000 };

  // FLAG_INHERIT is the stored in the uppermost bit of the LogLevel field.
  // assert that it does not conflict with valid LogLevel values.
  static_assert(
      static_cast<uint32_t>(LogLevel::MAX_LEVEL) < FLAG_INHERIT,
      "The FLAG_INHERIT bit must not be set in any valid LogLevel value");

  // Forbidden copy constructor and assignment operator
  LogCategory(LogCategory const&) = delete;
  LogCategory& operator=(LogCategory const&) = delete;
  // Disallow moving LogCategory objects as well.
  // LogCategory objects store pointers to their parent and siblings,
  // so we cannot allow moving categories to other locations.
  LogCategory(LogCategory&&) = delete;
  LogCategory& operator=(LogCategory&&) = delete;

  static void processMessageWalker(
      const LogCategory* category, const LogMessage& message);
  void processMessage(const LogMessage& message) const;
  void updateEffectiveLevel(LogLevel newEffectiveLevel);
  void parentLevelUpdated(LogLevel parentEffectiveLevel);

  /**
   * Which log messages processed at this category should propagate to the
   * parent category. The usual case is `LogLevel::MIN_LEVEL` which means all
   * messages will be propagated. `LogLevel::MAX_LEVEL` generally means that
   * this category and its children are directed to different destinations
   * and the user does not want the messages duplicated.
   */
  std::atomic<LogLevel> propagateLevelMessagesToParent_{LogLevel::MIN_LEVEL};

  /**
   * The minimum log level of this category and all of its parents.
   */
  std::atomic<LogLevel> effectiveLevel_{LogLevel::MAX_LEVEL};

  /**
   * The current log level for this category.
   *
   * The most significant bit is used to indicate if this logger should
   * inherit its parent's effective log level.
   */
  std::atomic<uint32_t> level_{0};

  /**
   * Our parent LogCategory in the category hierarchy.
   *
   * For instance, if our log name is "foo.bar.abc", our parent category
   * is "foo.bar".
   */
  LogCategory* const parent_{nullptr};

  /**
   * Our log category name.
   */
  const std::string name_;

  /**
   * The list of LogHandlers attached to this category.
   */
  folly::Synchronized<std::vector<std::shared_ptr<LogHandler>>> handlers_;

  /**
   * A pointer to the LoggerDB that we belong to.
   *
   * This is almost always the main LoggerDB singleton.  Unit tests are the
   * main place where we use other LoggerDB objects besides the singleton.
   */
  LoggerDB* const db_{nullptr};

  /**
   * Pointers to children and sibling loggers.
   * These pointers should only ever be accessed while holding the
   * LoggerDB::loggersByName_ lock.  (These are only modified when creating new
   * loggers, which occurs with the main LoggerDB lock held.)
   */
  LogCategory* firstChild_{nullptr};
  LogCategory* nextSibling_{nullptr};

  /**
   * A list of LogLevel values used by XLOG*() statements for this LogCategory.
   * The XLOG*() statements will check these values.  We ensure they are kept
   * up-to-date each time the effective log level changes for this category.
   *
   * This list may only be accessed while holding the main LoggerDB lock.
   */
  std::vector<std::atomic<LogLevel>*> xlogLevels_;
};
} // namespace folly
