/*
 *  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 WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
#define WEBRTC_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 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.
// |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)

// 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 "webrtc/base/constructormagic.h"
#include "webrtc/common_types.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, 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, value,        \
                                                       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 {

class CriticalSectionWrapper;

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();
   private:
    RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(Context);
  };

  static Logging* GetInstance();

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

  void Log(const char format[], ...);
  void Plot(int figure, double value);
  void Plot(int figure, double value, 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 {
    State global_state;
    std::stack<State> stack;
  };
  typedef std::map<uint32_t, ThreadState> ThreadMap;

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

  static Logging g_Logging;
  std::unique_ptr<CriticalSectionWrapper> crit_sect_;
  ThreadMap thread_map_;

  RTC_DISALLOW_COPY_AND_ASSIGN(Logging);
};
}  // namespace bwe
}  // namespace testing
}  // namespace webrtc

#endif  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE
#endif  // WEBRTC_MODULES_REMOTE_BITRATE_ESTIMATOR_TEST_BWE_TEST_LOGGING_H_
