/*
 *  Copyright 2024 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.
 */

#include "api/stats/attribute.h"

#include <cstdint>
#include <cstdio>
#include <map>
#include <optional>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

#include "rtc_base/checks.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/strings/string_builder.h"

namespace webrtc {

namespace {

struct VisitIsSequence {
  // Any type of vector is a sequence.
  template <typename T>
  bool operator()(const std::optional<std::vector<T>>* attribute) {
    return true;
  }
  // Any other type is not.
  template <typename T>
  bool operator()(const std::optional<T>* attribute) {
    return false;
  }
};

// Converts the attribute to string in a JSON-compatible way.
struct VisitToString {
  template <typename T,
            typename std::enable_if_t<
                std::is_same_v<T, int32_t> || std::is_same_v<T, uint32_t> ||
                    std::is_same_v<T, bool> || std::is_same_v<T, std::string>,
                bool> = true>
  std::string ValueToString(const T& value) {
    if constexpr (std::is_same_v<T, bool>) {
      return BoolToString(value);
    }
    return absl::StrCat(value);
  }

  // Convert 64-bit integers to doubles before converting to string because JSON
  // represents all numbers as floating points with ~15 digits of precision.
  template <typename T,
            typename std::enable_if_t<std::is_same_v<T, int64_t> ||
                                          std::is_same_v<T, uint64_t> ||
                                          std::is_same_v<T, double>,
                                      bool> = true>
  std::string ValueToString(const T& value) {
    char buf[32];
    const int len = std::snprintf(&buf[0], std::size(buf), "%.16g",
                                  static_cast<double>(value));
    RTC_DCHECK_LE(len, std::ssize(buf));
    return std::string(&buf[0], len);
  }

  // Vector attributes.
  template <typename T>
  std::string operator()(const std::optional<std::vector<T>>* attribute) {
    StringBuilder sb;
    sb << "[";
    const char* separator = "";
    constexpr bool element_is_string = std::is_same<T, std::string>::value;
    for (const T& element : attribute->value()) {
      sb << separator;
      if (element_is_string) {
        sb << "\"";
      }
      sb << ValueToString(element);
      if (element_is_string) {
        sb << "\"";
      }
      separator = ",";
    }
    sb << "]";
    return sb.Release();
  }
  // Map attributes.
  template <typename T>
  std::string operator()(
      const std::optional<std::map<std::string, T>>* attribute) {
    StringBuilder sb;
    sb << "{";
    const char* separator = "";
    constexpr bool element_is_string = std::is_same<T, std::string>::value;
    for (const auto& pair : attribute->value()) {
      sb << separator;
      sb << "\"" << pair.first << "\":";
      if (element_is_string) {
        sb << "\"";
      }
      sb << ValueToString(pair.second);
      if (element_is_string) {
        sb << "\"";
      }
      separator = ",";
    }
    sb << "}";
    return sb.Release();
  }
  // Simple attributes.
  template <typename T>
  std::string operator()(const std::optional<T>* attribute) {
    return ValueToString(attribute->value());
  }
};

struct VisitIsEqual {
  template <typename T>
  bool operator()(const std::optional<T>* attribute) {
    if (!other.holds_alternative<T>()) {
      return false;
    }
    return *attribute == other.as_optional<T>();
  }

  const Attribute& other;
};

}  // namespace

const char* Attribute::name() const {
  return name_;
}

const Attribute::StatVariant& Attribute::as_variant() const {
  return attribute_;
}

bool Attribute::has_value() const {
  return std::visit([](const auto* attr) { return attr->has_value(); },
                    attribute_);
}

bool Attribute::is_sequence() const {
  return std::visit(VisitIsSequence(), attribute_);
}

bool Attribute::is_string() const {
  return std::holds_alternative<const std::optional<std::string>*>(attribute_);
}

std::string Attribute::ToString() const {
  if (!has_value()) {
    return "null";
  }
  return std::visit(VisitToString(), attribute_);
}

bool Attribute::operator==(const Attribute& other) const {
  return std::visit(VisitIsEqual{.other = other}, attribute_);
}

bool Attribute::operator!=(const Attribute& other) const {
  return !(*this == other);
}

AttributeInit::AttributeInit(const char* name,
                             const Attribute::StatVariant& variant)
    : name(name), variant(variant) {}

}  // namespace webrtc
