/** @file

  DbgCtl class header file.

  @section license License

  Licensed to the Apache Software Foundation (ASF) under one
  or more contributor license agreements.  See the NOTICE file
  distributed with this work for additional information
  regarding copyright ownership.  The ASF licenses this file
  to you 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 "tsutil/SourceLocation.h"
#include "tsutil/ts_diag_levels.h"
#include "swoc/BufferWriter.h"
#include <atomic>
#include <utility>
#include <functional>

#include <ts/apidefs.h> // For TS_PRINTFLIKE

class DiagsConfigState;

enum DiagsShowLocation { SHOW_LOCATION_NONE = 0, SHOW_LOCATION_DEBUG, SHOW_LOCATION_ALL };

class DebugInterface
{
public:
  virtual ~DebugInterface()                            = default;
  virtual bool debug_tag_activated(const char *) const = 0;
  virtual bool get_override() const                    = 0;
  virtual void print_va(const char *debug_tag, DiagsLevel diags_level, const SourceLocation *loc, const char *format_string,
                        va_list ap) const              = 0;

  static DebugInterface *get_instance();
  static void            set_instance(DebugInterface *);

  // Generate the default diagnostics format string for the given parameters.
  // @return The offset in the format string of the timestamp (in case the caller doesn't want to include that)
  static size_t generate_format_string(swoc::LocalBufferWriter<1024> &format_writer, const char *debug_tag, DiagsLevel diags_level,
                                       const SourceLocation *loc, DiagsShowLocation show_location, const char *format_string);
  static const char *level_name(DiagsLevel dl);
};

class DbgCtl
{
public:
  // Tag is a debug tag.  Debug output associated with this control will be output when debug output
  // is enabled globally, and the tag matches the configured debug tag regular expression.
  //
  DbgCtl(char const *tag) : _ptr{_new_reference(tag)} {}

  // As instance with no tag will always be off.
  //
  DbgCtl() : _ptr{&_No_tag_dummy()} {}

  ~DbgCtl()
  {
    if (_ptr != &_No_tag_dummy()) {
      _rm_reference();
    }
  }

  // No copying. Only moving from a tagged to a tagless instance allowed.
  //
  DbgCtl(DbgCtl const &)            = delete;
  DbgCtl &operator=(DbgCtl const &) = delete;
  DbgCtl(DbgCtl &&);
  DbgCtl &operator=(DbgCtl &&);

  // A shorthand.
  //
  void
  set(char const *tag)
  {
    *this = DbgCtl{tag};
  }

  bool
  tag_on() const
  {
    return _ptr->second;
  }

  char const *
  tag() const
  {
    return _ptr->first;
  }

  bool
  on() const
  {
    auto m{_config_mode.load(std::memory_order_relaxed)};
    if (!m) {
      return false;
    }
    if (!_ptr->second) {
      return false;
    }
    if (m & 1) {
      return true;
    }
    return (2 == m) && (_override_global_on());
  }

  static bool
  global_on()
  {
    auto m{_config_mode.load(std::memory_order_relaxed)};
    if (!m) {
      return false;
    }
    if (m & 1) {
      return true;
    }
    return (2 == m) && (_override_global_on());
  }

  // Call this when the compiled regex to enable tags may have changed.
  //
  static void update(const std::function<bool(const char *)> &f);

  // For use in DbgPrint() only.
  //
  static void print(char const *tag, char const *file, char const *function, int line, char const *fmt_str, ...)
    TS_PRINTFLIKE(5, 6);

private:
  using _TagData = std::pair<char const *const, bool>;

  _TagData const *_ptr;

  static const _TagData &
  _No_tag_dummy()
  {
    static DbgCtl::_TagData const ntd{nullptr, false};
    return ntd;
  }

  static const _TagData *_new_reference(char const *tag);

  static void _rm_reference();

  class _RegistryAccessor;

  static std::atomic<int> _config_mode;

  static bool _override_global_on();

  friend class DiagsConfigState;
};

// Always generates output when called.
//
#define DbgPrint(CTL, ...) (DbgCtl::print((CTL).tag(), __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__))

#define Dbg(CTL, ...)               \
  do {                              \
    if ((CTL).on()) {               \
      DbgPrint((CTL), __VA_ARGS__); \
    }                               \
  } while (false)

// Same as Dbg above, but this allows a positive override of the DbgCtl, if FLAG is true.
//
#define SpecificDbg(FLAG, CTL, ...)   \
  do {                                \
    if (DbgCtl::global_on()) {        \
      if ((FLAG) || ((CTL).on())) {   \
        DbgPrint((CTL), __VA_ARGS__); \
      }                               \
    }                                 \
  } while (false)
