// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_VIZ_SERVICE_DEBUGGER_VIZ_DEBUGGER_H_
#define COMPONENTS_VIZ_SERVICE_DEBUGGER_VIZ_DEBUGGER_H_

#include <atomic>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/debug/debugging_buildflags.h"
#include "base/macros/concat.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/traced_value.h"
#include "base/values.h"
#include "components/viz/common/buildflags.h"
#include "components/viz/service/debugger/mojom/viz_debugger.mojom.h"
#include "components/viz/service/debugger/rwlock.h"
#include "components/viz/service/viz_service_export.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/perfetto/include/perfetto/tracing/string_helpers.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/vector2d_f.h"

// The visual debugger runtime can be completely disabled/enabled at compile
// time via the |USE_VIZ_DEBUGGER| build flag which corresponds to boolean gn
// arg 'use_viz_debugger'. Consult README.md for more information.

// To allow specific debug calls to become traces for end users you need to
// specify the DBG_USE_VIZ_DEBUGGER_TRACE() macro to the value 1. You can define
// this just prior to the callsite rather than needing to definite this prior to
// the header inclusion.

#if BUILDFLAG(USE_VIZ_DEBUGGER)

#define VIZ_DEBUGGER_IS_ON() true

namespace viz {

class VIZ_SERVICE_EXPORT VizDebugger {
 public:
  // These functions are called on a gpu thread that is not the
  // 'VizCompositorThread' and therefore have mulithreaded considerations.
  void FilterDebugStream(base::DictValue json);
  void StartDebugStream(
      mojo::PendingRemote<mojom::VizDebugOutput> pending_debug_output);
  void StopDebugStream();

  struct VIZ_SERVICE_EXPORT StaticSource {
    StaticSource(const char* anno_name,
                 const char* file_name,
                 int file_line,
                 const char* func_name);
    inline bool IsActive() const { return active; }
    inline bool IsEnabled() const { return enabled; }
    const char* anno = nullptr;
    const char* file = nullptr;
    const char* func = nullptr;
    const int line = 0;

    int reg_index = 0;
    bool active = false;
    bool enabled = false;
  };

  struct DrawOption {
    // TODO(petermcneeley): Consider moving this custom rgba color data over to
    // |SkColor| representation.
    uint8_t color_r;
    uint8_t color_g;
    uint8_t color_b;
    // Alpha is applied to rect fill only.
    uint8_t color_a;
  };

  ALWAYS_INLINE static bool IsEnabled() {
    return enabled_.load(std::memory_order_acquire);
  }

  static VizDebugger* GetInstance();

  // Parse the given hex string to produce a SkColor value. The hex string must
  // have a length of 7 and starts with a '#', e.g. "#ffffff". Returns
  // SK_ColorTRANSPARENT on parse failure.
  static SkColor HexStringToSkColor(std::string_view hex_string);

  // Converts a SkColor value to its corresponding hex string. e.g. SK_ColorRED
  // will produce "#ff0000".
  static std::string SkColorToHexString(SkColor color);

  VizDebugger();
  ~VizDebugger();

  struct VIZ_SERVICE_EXPORT BufferInfo {
    BufferInfo();
    ~BufferInfo();
    BufferInfo(const BufferInfo& a);
    SkBitmap bitmap;
  };

  struct Buffer {
    int id;
    BufferInfo buffer_info;
  };

  void SubmitBuffer(int buff_id, BufferInfo&& buffer);

  void CompleteFrame(const uint64_t counter,
                     const gfx::Size& window_pix,
                     base::TimeTicks time_ticks);
  void Draw(const gfx::SizeF& obj_size,
            const gfx::Vector2dF& pos,
            const StaticSource* dcs,
            DrawOption option,
            int* id,
            const gfx::RectF& uv,
            const std::string& text);

  void AddLogMessage(std::string log,
                     const StaticSource* dcs,
                     DrawOption option);

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

 private:
  friend class VizDebuggerInternal;
  static std::atomic<bool> enabled_;
  base::Value FrameAsJson(const uint64_t counter,
                          const gfx::Size& window_pix,
                          base::TimeTicks time_ticks);

  void AddFrame();
  void UpdateFilters();
  void RegisterSource(StaticSource* source);
  void DrawInternal(const gfx::SizeF& obj_size,
                    const gfx::Vector2dF& pos,
                    const StaticSource* dcs,
                    DrawOption option,
                    int* id,
                    const gfx::RectF& uv,
                    const std::string& text);
  void ApplyFilters(VizDebugger::StaticSource* source);
  mojo::Remote<mojom::VizDebugOutput> debug_output_;

  // This |task_runner_| is required to send json through mojo.
  scoped_refptr<base::SequencedTaskRunner> gpu_thread_task_runner_;

  struct CallSubmitCommon {
    CallSubmitCommon() = default;
    CallSubmitCommon(int index,
                     int source,
                     int64_t thread,
                     DrawOption draw_option)
        : draw_index(index),
          source_index(source),
          thread_id(thread),
          option(draw_option) {}
    base::DictValue GetDictionaryValue() const;
    int draw_index;
    int source_index;
    int64_t thread_id;
    VizDebugger::DrawOption option;
  };

  struct DrawCall : public CallSubmitCommon {
    DrawCall() = default;
    DrawCall(int index,
             int source,
             int64_t thread,
             DrawOption draw_option,
             gfx::SizeF size,
             gfx::Vector2dF position,
             int buffer_id,
             gfx::RectF uv_rect,
             std::string text_str)
        : CallSubmitCommon(index, source, thread, draw_option),
          obj_size(size),
          pos(position),
          buff_id(buffer_id),
          uv(uv_rect),
          text(text_str) {}
    gfx::SizeF obj_size;
    gfx::Vector2dF pos;
    int buff_id;
    gfx::RectF uv;
    std::string text;
  };

  struct LogCall : public CallSubmitCommon {
    LogCall() = default;
    LogCall(int index,
            int source,
            int64_t thread,
            DrawOption draw_option,
            std::string str)
        : CallSubmitCommon(index, source, thread, draw_option),
          value(std::move(str)) {}
    std::string value;
  };

  struct VIZ_SERVICE_EXPORT FilterBlock {
    FilterBlock(const std::string file_str,
                const std::string func_str,
                const std::string anno_str,
                bool is_active,
                bool is_enabled);
    ~FilterBlock();
    FilterBlock(const FilterBlock& other);
    std::string file;
    std::string func;
    std::string anno;
    bool active = false;
    bool enabled = false;
  };

  // Synchronize access to buffers and variables mutated by multiple threads.
  rwlock::RWLock read_write_lock_;

  // New filters to promoted to cached filters on next frame.
  std::vector<FilterBlock> new_filters_;
  bool apply_new_filters_next_frame_ = false;

  // Json is saved out every frame on the call to 'CompleteFrame' but may not be
  // uploaded immediately due to task runner sequencing.
  base::Value json_frame_output_;
  size_t last_sent_source_count_ = 0;

  // Cached filters to apply filtering to new sources not just on filter update.
  std::vector<FilterBlock> cached_filters_;
  int buffer_id = 0;

  // Common counter for all submissions. This variable can be accessed by
  // multiple threads atomically.
  std::atomic<int> submission_count_ = 0;

  // Default starting size for each buffer/vector.
  static constexpr int kDefaultBufferSize = 64;

  // Buffers/vectors for each type of debug calls. These vectors can be accessed
  // and mutated by multiple threads simultaneously or individually.
  std::vector<DrawCall> draw_rect_calls_{kDefaultBufferSize};
  std::vector<LogCall> logs_{kDefaultBufferSize};
  std::vector<raw_ptr<StaticSource>> sources_;
  std::vector<Buffer> buffers_;

  // Individual tail indices tracker variables for next insertion index in
  // each buffer. These variables can be accessed by multiple threads
  // atomically.
  std::atomic<int> draw_calls_tail_idx_ = 0;
  std::atomic<int> logs_tail_idx_ = 0;
};

}  // namespace viz

#define DBG_OPT_RED viz::VizDebugger::DrawOption({255, 0, 0, 0})
#define DBG_OPT_GREEN viz::VizDebugger::DrawOption({0, 255, 0, 0})
#define DBG_OPT_BLUE viz::VizDebugger::DrawOption({0, 0, 255, 0})
#define DBG_OPT_BLACK viz::VizDebugger::DrawOption({0, 0, 0, 0})
#define DBG_DEFAULT_UV gfx::RectF(0, 0, 1, 1)

#define DBG_DRAW_RECTANGLE_OPT_BUFF_UV_TEXT(anno, option, pos, size, id, uv,   \
                                            text)                              \
  do {                                                                         \
    if (viz::VizDebugger::IsEnabled()) {                                       \
      static viz::VizDebugger::StaticSource dcs(anno, __FILE__, __LINE__,      \
                                                __func__);                     \
      if (dcs.IsActive()) {                                                    \
        viz::VizDebugger::GetInstance()->Draw(size, pos, &dcs, option, id, uv, \
                                              text);                           \
      }                                                                        \
    }                                                                          \
  } while (0)

#define DBG_COMPLETE_BUFFERS(buff_id, buffer)                           \
  do {                                                                  \
    if (viz::VizDebugger::IsEnabled()) {                                \
      viz::VizDebugger::GetInstance()->SubmitBuffer(buff_id,            \
                                                    std::move(buffer)); \
    }                                                                   \
  } while (0)

#define DBG_LOG_OPT(anno, option, format, ...)                            \
  do {                                                                    \
    if (viz::VizDebugger::IsEnabled()) {                                  \
      static viz::VizDebugger::StaticSource dcs(anno, __FILE__, __LINE__, \
                                                __func__);                \
      if (dcs.IsActive()) {                                               \
        viz::VizDebugger::GetInstance()->AddLogMessage(                   \
            base::StringPrintf(format, ##__VA_ARGS__), &dcs, option);     \
      }                                                                   \
    }                                                                     \
  } while (0)

#define DBG_CONNECTED_OR_TRACING(is_enabled) \
  is_enabled = VizDebugger::GetInstance()->IsEnabled();

#define DBG_FLAG_FBOOL(anno, fun_name)                                    \
  namespace {                                                             \
  bool fun_name() {                                                       \
    if (viz::VizDebugger::IsEnabled()) {                                  \
      static viz::VizDebugger::StaticSource dcs(anno, __FILE__, __LINE__, \
                                                __func__);                \
      if (dcs.IsEnabled()) {                                              \
        return true;                                                      \
      }                                                                   \
    }                                                                     \
    return false;                                                         \
  }                                                                       \
  }  // namespace

#else  //  !BUILDFLAG(USE_VIZ_DEBUGGER)

#define VIZ_DEBUGGER_IS_ON() false

// Viz Debugger is not enabled. The |VizDebugger| class is minimally defined to
// reduce the need for if/def checks in external code. All debugging macros
// compiled to empty statements but do eat some parameters to prevent used
// variable warnings.

namespace viz {
class VIZ_SERVICE_EXPORT VizDebugger {
 public:
  VizDebugger() = default;
  static inline VizDebugger* GetInstance() {
    static VizDebugger g_debugger;
    return &g_debugger;
  }

  // These structures are part of public API and must be included.
  struct VIZ_SERVICE_EXPORT BufferInfo {
    BufferInfo();
    ~BufferInfo();
    BufferInfo(const BufferInfo& a);
    SkBitmap bitmap;
  };

  struct DrawOption {
    uint8_t color_r;
    uint8_t color_g;
    uint8_t color_b;
    uint8_t color_a;
  };

  inline void CompleteFrame(uint64_t counter,
                            const gfx::Size& window_pix,
                            base::TimeTicks time_ticks) {}

  static inline bool IsEnabled() { return false; }
  VizDebugger(const VizDebugger&) = delete;
  VizDebugger& operator=(const VizDebugger&) = delete;
};

std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
DrawRectToTraceValue(const gfx::Vector2dF& pos,
                     const gfx::SizeF& size,
                     const std::string& text);

}  // namespace viz

#define VIZ_DEBUGGER_TRACING_CATEGORY "viz.visual_debugger"
#define DBG_OPT_RED 0

#define DBG_OPT_GREEN 0

#define DBG_OPT_BLUE 0

#define DBG_OPT_BLACK 0

#define DBG_DEFAULT_UV 0

#define DBG_VIZ_DEBUGGER_TRACE_IMPL(anno, pos, size, text)                    \
  TRACE_EVENT_INSTANT(                                                        \
      TRACE_DISABLED_BY_DEFAULT(VIZ_DEBUGGER_TRACING_CATEGORY), anno, "args", \
      viz::DrawRectToTraceValue(pos, size, text))

#define DBG_DRAW_RECTANGLE_OPT_BUFF_UV_TEXT(anno, option, pos, size, id, uv, \
                                            text)                            \
  std::ignore = option;                                                      \
  std::ignore = id;                                                          \
  std::ignore = uv;                                                          \
  DBG_VIZ_DEBUGGER_TRACE_IMPL(perfetto::StaticString(anno), pos, size, text)

#define DBG_COMPLETE_BUFFERS(buff_id, buffer) \
  std::ignore = buff_id;                      \
  std::ignore = buffer;

#define DBG_LOG_OPT(anno, option, format, ...) \
  std::ignore = anno;                          \
  std::ignore = option;                        \
  std::ignore = format;

#define DBG_CONNECTED_OR_TRACING(enabled)                             \
  TRACE_EVENT_CATEGORY_GROUP_ENABLED(      \
      TRACE_DISABLED_BY_DEFAULT(VIZ_DEBUGGER_TRACING_CATEGORY), (&enabled))

#define DBG_FLAG_FBOOL(anno, fun_name) \
  namespace {                          \
  constexpr bool fun_name() {          \
    return false;                      \
  }                                    \
  }

#endif  // BUILDFLAG(USE_VIZ_DEBUGGER)

// These are forwarding macro implementations common to the enabled/disabled
// compile paths
#define DBG_DRAW_RECTANGLE_OPT_BUFF_UV(anno, option, pos, size, id, uv) \
  DBG_DRAW_RECTANGLE_OPT_BUFF_UV_TEXT(anno, option, pos, size, id, uv, "")

#define DBG_DRAW_RECTANGLE_OPT(anno, option, pos, size) \
  DBG_DRAW_RECTANGLE_OPT_BUFF(anno, option, pos, size, nullptr)

#define DBG_DRAW_RECTANGLE(anno, pos, size) \
  DBG_DRAW_RECTANGLE_OPT_BUFF(anno, DBG_OPT_BLACK, pos, size, nullptr)

#define DBG_DRAW_RECTANGLE_OPT_BUFF(anno, option, pos, size, id) \
  DBG_DRAW_RECTANGLE_OPT_BUFF_UV(anno, option, pos, size, id, DBG_DEFAULT_UV)

#define DBG_DRAW_RECT_OPT_BUFF(anno, option, rect, id) \
  DBG_DRAW_RECT_OPT_BUFF_UV(anno, option, rect, id, DBG_DEFAULT_UV)

#define DBG_DRAW_RECT_OPT(anno, option, rect) \
  DBG_DRAW_RECT_OPT_BUFF(anno, option, rect, nullptr)

#define DBG_DRAW_RECT_BUFF(anno, rect, id) \
  DBG_DRAW_RECT_OPT_BUFF(anno, DBG_OPT_BLACK, rect, id)

#define DBG_DRAW_RECT_BUFF_UV(anno, rect, id, uv) \
  DBG_DRAW_RECT_OPT_BUFF_UV(anno, DBG_OPT_BLACK, rect, id, uv)

#define DBG_DRAW_RECT(anno, rect) DBG_DRAW_RECT_OPT(anno, DBG_OPT_BLACK, rect)

#define DBG_DRAW_TEXT_OPT(anno, option, pos, text)                        \
  DBG_DRAW_RECTANGLE_OPT_BUFF_UV_TEXT(                                    \
      anno, option, gfx::Vector2dF(pos.x(), pos.y()), gfx::SizeF(-1, -1), \
      nullptr, DBG_DEFAULT_UV, text)

#define DBG_DRAW_TEXT(anno, pos, text) \
  DBG_DRAW_TEXT_OPT(anno, DBG_OPT_BLACK, pos, text)

#define DBG_LOG(anno, format, ...) \
  DBG_LOG_OPT(anno, DBG_OPT_BLACK, format, ##__VA_ARGS__)

#define DBG_DRAW_RECT_OPT_BUFF_UV(anno, option, rect, id, uv)             \
  DBG_DRAW_RECTANGLE_OPT_BUFF_UV(                                         \
      anno, option, gfx::Vector2dF(rect.origin().x(), rect.origin().y()), \
      gfx::SizeF(rect.size()), id, uv)

#endif  // COMPONENTS_VIZ_SERVICE_DEBUGGER_VIZ_DEBUGGER_H_
