/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
#define MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_

// To enable BWE logging, run this command from trunk/ :
// build/gyp_chromium --depth=. webrtc/modules/modules.gyp
//   -Denable_bwe_test_logging=1
#ifndef BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
#define BWE_TEST_LOGGING_COMPILE_TIME_ENABLE 0
#endif  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE

// BWE logging allows you to insert dynamically named log/plot points in the
// call tree. E.g. the function:
//  void f1() {
//    BWE_TEST_LOGGING_TIME(clock_->TimeInMilliseconds());
//    BWE_TEST_LOGGING_CONTEXT("stream");
//    for (uint32_t i=0; i<4; ++i) {
//      BWE_TEST_LOGGING_ENABLE(i & 1);
//      BWE_TEST_LOGGING_CONTEXT(i);
//      BWE_TEST_LOGGING_LOG1("weight", "%f tonnes", weights_[i]);
//      for (float j=0.0f; j<1.0; j+=0.4f) {
//        BWE_TEST_LOGGING_PLOT(0, "bps", -1, j);
//      }
//    }
//  }
//
// Might produce the output:
//   stream_00000001_weight 13.000000 tonnes
//   PLOT  stream_00000001_bps  1.000000  0.000000
//   PLOT  stream_00000001_bps  1.000000  0.400000
//   PLOT  stream_00000001_bps  1.000000  0.800000
//   stream_00000003_weight 39.000000 tonnes
//   PLOT  stream_00000003_bps  1.000000  0.000000
//   PLOT  stream_00000003_bps  1.000000  0.400000
//   PLOT  stream_00000003_bps  1.000000  0.800000
//
// Log *contexts* are names concatenated with '_' between them, with the name
// of the logged/plotted string/value last. Plot *time* is inherited down the
// tree. A branch is enabled by default but can be *disabled* to reduce output.
// The difference between the RTC_LOG and PLOT macros is that PLOT prefixes the
// line so it can be easily filtered, plus it outputs the current time.

#if !(BWE_TEST_LOGGING_COMPILE_TIME_ENABLE)

// Set a thread-global base logging context. This name will be prepended to all
// hierarchical contexts.
// `name` is a char*, std::string or uint32_t to name the context.
#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name)

// Thread-globally allow/disallow logging.
// `enable` is expected to be a bool.
#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled)

// Insert a (hierarchical) logging context.
// `name` is a char*, std::string or uint32_t to name the context.
#define BWE_TEST_LOGGING_CONTEXT(name)

// Allow/disallow logging down the call tree from this point. Logging must be
// enabled all the way to the root of the call tree to take place.
// `enable` is expected to be a bool.
#define BWE_TEST_LOGGING_ENABLE(enabled)

// Set current time (only affects PLOT output). Down the call tree, the latest
// time set always takes precedence.
// `time` is an int64_t time in ms, or -1 to inherit time from previous context.
#define BWE_TEST_LOGGING_TIME(time)

// Print to stdout, e.g.:
//   Context1_Context2_Name  printf-formated-string
// `name` is a char*, std::string or uint32_t to name the log line.
// `format` is a printf format string.
// |_1...| are arguments for printf.
#define BWE_TEST_LOGGING_LOG1(name, format, _1)
#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2)
#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3)
#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4)
#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5)

// Print to stdout in tab-separated format suitable for plotting, e.g.:
//   PLOT figure Context1_Context2_Name  time  value
// `figure` is a figure id. Different figures are plotted in different windows.
// `name` is a char*, std::string or uint32_t to name the plotted value.
// `time` is an int64_t time in ms, or -1 to inherit time from previous context.
// `value` is a double precision float to be plotted.
// `ssrc` identifies the source of a stream
// `alg_name` is an optional argument, a string
#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name)
#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc)
#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
                                                 ssrc, alg_name)

// Print to stdout in tab-separated format suitable for plotting, e.g.:
//   BAR figure Context1_Context2_Name  x_left  width  value
// `figure` is a figure id. Different figures are plotted in different windows.
// `name` is a char*, std::string or uint32_t to name the plotted value.
// `value` is a double precision float to be plotted.
// `ylow` and `yhigh` are double precision float for the error line.
// `title` is a string and refers to the error label.
// `ymax` is a double precision float for the limit horizontal line.
// `limit_title` is a string and refers to the limit label.
#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id)
#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, \
                                  error_title, flow_id)
#define BWE_TEST_LOGGING_LIMITERRORBAR( \
    figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id)

#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id)

// `num_flows` is an integer refering to the number of RMCAT flows in the
// scenario.
// Define `x_label` and `y_label` for plots.
#define BWE_TEST_LOGGING_LABEL(figure, x_label, y_label, num_flows)

#else  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE

#include <map>
#include <memory>
#include <stack>
#include <string>

#include "rtc_base/synchronization/mutex.h"

#define BWE_TEST_LOGGING_GLOBAL_CONTEXT(name)                             \
  do {                                                                    \
    webrtc::testing::bwe::Logging::GetInstance()->SetGlobalContext(name); \
  } while (0)

#define BWE_TEST_LOGGING_GLOBAL_ENABLE(enabled)                             \
  do {                                                                      \
    webrtc::testing::bwe::Logging::GetInstance()->SetGlobalEnable(enabled); \
  } while (0)

#define __BWE_TEST_LOGGING_CONTEXT_NAME(ctx, line) ctx##line
#define __BWE_TEST_LOGGING_CONTEXT_DECLARE(ctx, line, name, time, enabled) \
  webrtc::testing::bwe::Logging::Context __BWE_TEST_LOGGING_CONTEXT_NAME(  \
      ctx, line)(name, time, enabled)

#define BWE_TEST_LOGGING_CONTEXT(name) \
  __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, name, -1, true)
#define BWE_TEST_LOGGING_ENABLE(enabled)                           \
  __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", -1, \
                                     static_cast<bool>(enabled))
#define BWE_TEST_LOGGING_TIME(time)                            \
  __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, "", \
                                     static_cast<int64_t>(time), true)

#define BWE_TEST_LOGGING_LOG1(name, format, _1)                    \
  do {                                                             \
    BWE_TEST_LOGGING_CONTEXT(name);                                \
    webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1); \
  } while (0)
#define BWE_TEST_LOGGING_LOG2(name, format, _1, _2)                    \
  do {                                                                 \
    BWE_TEST_LOGGING_CONTEXT(name);                                    \
    webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2); \
  } while (0)
#define BWE_TEST_LOGGING_LOG3(name, format, _1, _2, _3)                    \
  do {                                                                     \
    BWE_TEST_LOGGING_CONTEXT(name);                                        \
    webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3); \
  } while (0)
#define BWE_TEST_LOGGING_LOG4(name, format, _1, _2, _3, _4)                    \
  do {                                                                         \
    BWE_TEST_LOGGING_CONTEXT(name);                                            \
    webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, _4); \
  } while (0)
#define BWE_TEST_LOGGING_LOG5(name, format, _1, _2, _3, _4, _5)               \
  do {                                                                        \
    BWE_TEST_LOGGING_CONTEXT(name);                                           \
    webrtc::testing::bwe::Logging::GetInstance()->Log(format, _1, _2, _3, _4, \
                                                      _5);                    \
  } while (0)

#define BWE_TEST_LOGGING_PLOT(figure, name, time, value)                     \
  do {                                                                       \
    __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name,           \
                                       static_cast<int64_t>(time), true);    \
    webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value); \
  } while (0)

#define BWE_TEST_LOGGING_PLOT_WITH_NAME(figure, name, time, value, alg_name) \
  do {                                                                       \
    __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name,           \
                                       static_cast<int64_t>(time), true);    \
    webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value,  \
                                                       alg_name);            \
  } while (0)

#define BWE_TEST_LOGGING_PLOT_WITH_SSRC(figure, name, time, value, ssrc)    \
  do {                                                                      \
    __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name,          \
                                       static_cast<int64_t>(time), true);   \
    webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
                                                       ssrc);               \
  } while (0)

#define BWE_TEST_LOGGING_PLOT_WITH_NAME_AND_SSRC(figure, name, time, value, \
                                                 ssrc, alg_name)            \
  do {                                                                      \
    __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __PLOT__, name,          \
                                       static_cast<int64_t>(time), true);   \
    webrtc::testing::bwe::Logging::GetInstance()->Plot(figure, name, value, \
                                                       ssrc, alg_name);     \
  } while (0)

#define BWE_TEST_LOGGING_BAR(figure, name, value, flow_id)                     \
  do {                                                                         \
    BWE_TEST_LOGGING_CONTEXT(name);                                            \
    webrtc::testing::bwe::Logging::GetInstance()->PlotBar(figure, name, value, \
                                                          flow_id);            \
  } while (0)

#define BWE_TEST_LOGGING_BASELINEBAR(figure, name, value, flow_id) \
  do {                                                             \
    BWE_TEST_LOGGING_CONTEXT(name);                                \
    webrtc::testing::bwe::Logging::GetInstance()->PlotBaselineBar( \
        figure, name, value, flow_id);                             \
  } while (0)

#define BWE_TEST_LOGGING_ERRORBAR(figure, name, value, ylow, yhigh, title, \
                                  flow_id)                                 \
  do {                                                                     \
    BWE_TEST_LOGGING_CONTEXT(name);                                        \
    webrtc::testing::bwe::Logging::GetInstance()->PlotErrorBar(            \
        figure, name, value, ylow, yhigh, title, flow_id);                 \
  } while (0)

#define BWE_TEST_LOGGING_LIMITERRORBAR(                                        \
    figure, name, value, ylow, yhigh, error_title, ymax, limit_title, flow_id) \
  do {                                                                         \
    BWE_TEST_LOGGING_CONTEXT(name);                                            \
    webrtc::testing::bwe::Logging::GetInstance()->PlotLimitErrorBar(           \
        figure, name, value, ylow, yhigh, error_title, ymax, limit_title,      \
        flow_id);                                                              \
  } while (0)

#define BWE_TEST_LOGGING_LABEL(figure, title, y_label, num_flows) \
  do {                                                            \
    BWE_TEST_LOGGING_CONTEXT(title);                              \
    webrtc::testing::bwe::Logging::GetInstance()->PlotLabel(      \
        figure, title, y_label, num_flows);                       \
  } while (0)

namespace webrtc {
namespace testing {
namespace bwe {

class Logging {
 public:
  class Context {
   public:
    Context(uint32_t name, int64_t timestamp_ms, bool enabled);
    Context(const std::string& name, int64_t timestamp_ms, bool enabled);
    Context(const char* name, int64_t timestamp_ms, bool enabled);

    Context() = delete;
    Context(const Context&) = delete;
    Context& operator=(const Context&) = delete;
    ~Context();
  };

  static Logging* GetInstance();

  void SetGlobalContext(uint32_t name);
  void SetGlobalContext(const std::string& name);
  void SetGlobalContext(const char* name);
  void SetGlobalEnable(bool enabled);

#if defined(__GNUC__)
  // Note: Implicit `this` argument counts as the first argument.
  __attribute__((__format__(__printf__, 2, 3)))
#endif
  void
  Log(const char format[], ...);
  void Plot(int figure, const std::string& name, double value);
  void Plot(int figure,
            const std::string& name,
            double value,
            const std::string& alg_name);
  void Plot(int figure, const std::string& name, double value, uint32_t ssrc);
  void Plot(int figure,
            const std::string& name,
            double value,
            uint32_t ssrc,
            const std::string& alg_name);
  void PlotBar(int figure, const std::string& name, double value, int flow_id);
  void PlotBaselineBar(int figure,
                       const std::string& name,
                       double value,
                       int flow_id);
  void PlotErrorBar(int figure,
                    const std::string& name,
                    double value,
                    double ylow,
                    double yhigh,
                    const std::string& error_title,
                    int flow_id);

  void PlotLimitErrorBar(int figure,
                         const std::string& name,
                         double value,
                         double ylow,
                         double yhigh,
                         const std::string& error_title,
                         double ymax,
                         const std::string& limit_title,
                         int flow_id);
  void PlotLabel(int figure,
                 const std::string& title,
                 const std::string& y_label,
                 int num_flows);

 private:
  struct State {
    State();
    State(const std::string& new_tag, int64_t timestamp_ms, bool enabled);
    void MergePrevious(const State& previous);

    std::string tag;
    int64_t timestamp_ms;
    bool enabled;
  };
  struct ThreadState {
    ThreadState();
    ~ThreadState();
    State global_state;
    std::stack<State> stack;
  };
  typedef std::map<uint32_t, ThreadState> ThreadMap;

  Logging();
  ~Logging();

  Logging(const Logging&) = delete;
  Logging& operator=(const Logging&) = delete;

  void PushState(const std::string& append_to_tag,
                 int64_t timestamp_ms,
                 bool enabled);
  void PopState();

  Mutex mutex_;
  ThreadMap thread_map_;
};
}  // namespace bwe
}  // namespace testing
}  // namespace webrtc

#endif  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
#endif  // MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
