/*
 *  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("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  Context1_Context2_Name  time  value
// |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.
#define BWE_TEST_LOGGING_PLOT(name, time, value)

#else  // BWE_TEST_LOGGING_COMPILE_TIME_ENABLE

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

#include "webrtc/base/constructormagic.h"
#include "webrtc/common_types.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.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(name, time, value)\
    do { \
      __BWE_TEST_LOGGING_CONTEXT_DECLARE(__bwe_log_, __LINE__, name, \
                                         static_cast<int64_t>(time), true); \
      webrtc::testing::bwe::Logging::GetInstance()->Plot(value); \
    } 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:
    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(double value);

 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;
  scoped_ptr<CriticalSectionWrapper> crit_sect_;
  ThreadMap thread_map_;

  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_
