text: // Copyright 2020 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  #ifndef SRC_MEDIA_AUDIO_AUDIO_CORE_STREAM_USAGE_H_
  #define SRC_MEDIA_AUDIO_AUDIO_CORE_STREAM_USAGE_H_
  
  #include <fuchsia/media/cpp/fidl.h>
  #include <lib/fidl/cpp/enum.h>
  
  #include <type_traits>
  #include <unordered_set>
  #include <variant>
  
  #include "src/lib/syslog/cpp/logger.h"
  
  namespace media::audio {
  
  static_assert(fuchsia::media::RENDER_USAGE_COUNT == 5);
  #define EXPAND_EACH_FIDL_RENDER_USAGE \
    EXPAND_RENDER_USAGE(BACKGROUND)     \
    EXPAND_RENDER_USAGE(MEDIA)          \
    EXPAND_RENDER_USAGE(INTERRUPTION)   \
    EXPAND_RENDER_USAGE(SYSTEM_AGENT)   \
    EXPAND_RENDER_USAGE(COMMUNICATION)
  
  static constexpr uint32_t kStreamInternalRenderUsageCount = 1;
  #define EXPAND_EACH_INTERNAL_RENDER_USAGE EXPAND_RENDER_USAGE(ULTRASOUND)
  
  #define EXPAND_EACH_RENDER_USAGE \
    EXPAND_EACH_FIDL_RENDER_USAGE  \
    EXPAND_EACH_INTERNAL_RENDER_USAGE
  
  static_assert(fuchsia::media::CAPTURE_USAGE_COUNT == 4);
  #define EXPAND_EACH_FIDL_CAPTURE_USAGE \
    EXPAND_CAPTURE_USAGE(BACKGROUND)     \
    EXPAND_CAPTURE_USAGE(FOREGROUND)     \
    EXPAND_CAPTURE_USAGE(SYSTEM_AGENT)   \
    EXPAND_CAPTURE_USAGE(COMMUNICATION)
  
  static constexpr uint32_t kStreamInternalCaptureUsageCount = 2;
  #define EXPAND_EACH_INTERNAL_CAPTURE_USAGE \
    EXPAND_CAPTURE_USAGE(LOOPBACK)           \
    EXPAND_CAPTURE_USAGE(ULTRASOUND)
  
  #define EXPAND_EACH_CAPTURE_USAGE \
    EXPAND_EACH_FIDL_CAPTURE_USAGE  \
    EXPAND_EACH_INTERNAL_CAPTURE_USAGE
  
  static constexpr uint32_t kStreamRenderUsageCount =
      fuchsia::media::RENDER_USAGE_COUNT + kStreamInternalRenderUsageCount;
  enum class RenderUsage : std::underlying_type_t<fuchsia::media::AudioRenderUsage> {
  #define EXPAND_RENDER_USAGE(U) U = fidl::ToUnderlying(fuchsia::media::AudioRenderUsage::U),
    EXPAND_EACH_FIDL_RENDER_USAGE
  #undef EXPAND_RENDER_USAGE
  #define EXPAND_RENDER_USAGE(U) U,
        EXPAND_EACH_INTERNAL_RENDER_USAGE
  #undef EXPAND_RENDER_USAGE
  };
  
  constexpr std::array<RenderUsage, fuchsia::media::RENDER_USAGE_COUNT> kFidlRenderUsages = {{
  #define EXPAND_RENDER_USAGE(U) RenderUsage::U,
      EXPAND_EACH_FIDL_RENDER_USAGE
  #undef EXPAND_RENDER_USAGE
  }};
  
  constexpr std::array<RenderUsage, kStreamRenderUsageCount> kRenderUsages = {{
  #define EXPAND_RENDER_USAGE(U) RenderUsage::U,
      EXPAND_EACH_RENDER_USAGE
  #undef EXPAND_RENDER_USAGE
  }};
  
  static constexpr uint32_t kStreamCaptureUsageCount =
      fuchsia::media::CAPTURE_USAGE_COUNT + kStreamInternalCaptureUsageCount;
  enum class CaptureUsage : std::underlying_type_t<fuchsia::media::AudioCaptureUsage> {
  #define EXPAND_CAPTURE_USAGE(U) U = fidl::ToUnderlying(fuchsia::media::AudioCaptureUsage::U),
    EXPAND_EACH_FIDL_CAPTURE_USAGE
  #undef EXPAND_CAPTURE_USAGE
  #define EXPAND_CAPTURE_USAGE(U) U,
        EXPAND_EACH_INTERNAL_CAPTURE_USAGE
  #undef EXPAND_CAPTURE_USAGE
  };
  
  constexpr std::array<CaptureUsage, fuchsia::media::CAPTURE_USAGE_COUNT> kFidlCaptureUsages = {{
  #define EXPAND_CAPTURE_USAGE(U) CaptureUsage::U,
      EXPAND_EACH_FIDL_CAPTURE_USAGE
  #undef EXPAND_CAPTURE_USAGE
  }};
  
  static constexpr uint32_t kStreamUsageCount = kStreamRenderUsageCount + kStreamCaptureUsageCount;
  
  // Since we define the RenderUsage enum to have the same numeric values for each fidl enum entry,
  // we can convert by casting the underlying numeric value.
  static RenderUsage RenderUsageFromFidlRenderUsage(fuchsia::media::AudioRenderUsage u) {
    return static_cast<RenderUsage>(fidl::ToUnderlying(u));
  }
  static CaptureUsage CaptureUsageFromFidlCaptureUsage(fuchsia::media::AudioCaptureUsage u) {
    return static_cast<CaptureUsage>(fidl::ToUnderlying(u));
  }
  
  std::optional<fuchsia::media::AudioRenderUsage> FidlRenderUsageFromRenderUsage(RenderUsage u);
  
  std::optional<fuchsia::media::AudioCaptureUsage> FidlCaptureUsageFromCaptureUsage(CaptureUsage u);
  
  const char* RenderUsageToString(const RenderUsage& usage);
  const char* CaptureUsageToString(const CaptureUsage& usage);
  
  class StreamUsage {
   public:
    static constexpr StreamUsage WithRenderUsage(RenderUsage u) { return StreamUsage(u); }
    static constexpr StreamUsage WithCaptureUsage(CaptureUsage u) { return StreamUsage(u); }
    static constexpr StreamUsage WithRenderUsage(fuchsia::media::AudioRenderUsage u) {
      return StreamUsage(RenderUsageFromFidlRenderUsage(u));
    }
    static constexpr StreamUsage WithCaptureUsage(fuchsia::media::AudioCaptureUsage u) {
      return StreamUsage(CaptureUsageFromFidlCaptureUsage(u));
    }
  
    StreamUsage() = default;
  
    bool operator==(const StreamUsage& other) const { return usage_ == other.usage_; }
    bool operator!=(const StreamUsage& other) const { return !(*this == other); }
  
    // RenderUsage
    bool is_render_usage() const { return std::holds_alternative<RenderUsage>(usage_); }
    StreamUsage& set_render_usage(RenderUsage usage) {
      usage_ = usage;
      return *this;
    }
    RenderUsage render_usage() const {
      FX_DCHECK(is_render_usage());
      return std::get<RenderUsage>(usage_);
    }
  
    // CaptureUsage
    bool is_capture_usage() const { return std::holds_alternative<CaptureUsage>(usage_); }
    StreamUsage& set_capture_usage(CaptureUsage usage) {
      usage_ = usage;
      return *this;
    }
    CaptureUsage capture_usage() const {
      FX_DCHECK(is_capture_usage());
      return std::get<CaptureUsage>(usage_);
    }
  
    // A |StreamUsage| is empty if it contains neither a render usage or a capture usage. This state
    // exists to be similar to the semantics of a FIDL union in C++.
    bool is_empty() const { return std::holds_alternative<std::monostate>(usage_); }
  
    const char* ToString() const;
  
   private:
    using Usage = std::variant<std::monostate, RenderUsage, CaptureUsage>;
  
    explicit constexpr StreamUsage(RenderUsage usage) : usage_(usage) {}
    explicit constexpr StreamUsage(CaptureUsage usage) : usage_(usage) {}
  
    Usage usage_;
  };
  
  constexpr std::array<StreamUsage, kStreamUsageCount> kStreamUsages = {{
  #define EXPAND_RENDER_USAGE(U) StreamUsage::WithRenderUsage(RenderUsage::U),
      EXPAND_EACH_RENDER_USAGE
  #undef EXPAND_RENDER_USAGE
  #define EXPAND_CAPTURE_USAGE(U) StreamUsage::WithCaptureUsage(CaptureUsage::U),
          EXPAND_EACH_CAPTURE_USAGE
  #undef EXPAND_CAPTURE_USAGE
  }};
  
  static uint32_t HashStreamUsage(const StreamUsage& u) {
    if (u.is_render_usage()) {
      return static_cast<uint32_t>(u.render_usage());
    }
    if (u.is_capture_usage()) {
      return static_cast<uint32_t>(u.capture_usage()) + kStreamRenderUsageCount;
    }
    return kStreamUsageCount;
  }
  
  namespace internal {
  
  struct EnumHash {
    template <typename T>
    size_t operator()(T t) const {
      return static_cast<size_t>(t);
    }
  };
  
  struct StreamUsageHash {
    size_t operator()(StreamUsage u) const { return HashStreamUsage(u); }
  };
  
  }  // namespace internal
  
  using RenderUsageSet = std::unordered_set<RenderUsage, internal::EnumHash>;
  using CaptureUsageSet = std::unordered_set<CaptureUsage, internal::EnumHash>;
  using StreamUsageSet = std::unordered_set<StreamUsage, internal::StreamUsageHash>;
  
  StreamUsage StreamUsageFromFidlUsage(const fuchsia::media::Usage& usage);
  
  template <typename Container>
  static StreamUsageSet StreamUsageSetFromRenderUsages(const Container& container) {
    static_assert(std::is_same<typename Container::value_type, RenderUsage>::value);
    StreamUsageSet result;
    std::transform(container.cbegin(), container.cend(), std::inserter(result, result.begin()),
                   [](const auto& u) { return StreamUsage::WithRenderUsage(u); });
    return result;
  }
  
  template <typename Container>
  static StreamUsageSet StreamUsageSetFromCaptureUsages(const Container& container) {
    static_assert(std::is_same<typename Container::value_type, CaptureUsage>::value);
    StreamUsageSet result;
    std::transform(container.cbegin(), container.cend(), std::inserter(result, result.begin()),
                   [](const auto& u) { return StreamUsage::WithCaptureUsage(u); });
    return result;
  }
  
  }  // namespace media::audio
  
  #endif  // SRC_MEDIA_AUDIO_AUDIO_CORE_STREAM_USAGE_H_
  