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

#include "components/mirroring/service/openscreen_stats_client.h"

#include <stdint.h>

#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "media/cast/logging/stats_event_subscriber.h"

namespace mirroring {

namespace {

media::cast::StatsEventSubscriber::CastStat StatisticTypeToCastStat(
    const openscreen::cast::StatisticType& stat_type) {
  switch (stat_type) {
    case openscreen::cast::StatisticType::kEnqueueFps:
      return media::cast::StatsEventSubscriber::CastStat::ENQUEUE_FPS;
    case openscreen::cast::StatisticType::kAvgCaptureLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          AVG_CAPTURE_LATENCY_MS;
    case openscreen::cast::StatisticType::kAvgEncodeTimeMs:
      return media::cast::StatsEventSubscriber::CastStat::AVG_ENCODE_TIME_MS;
    case openscreen::cast::StatisticType::kAvgQueueingLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          AVG_QUEUEING_LATENCY_MS;
    case openscreen::cast::StatisticType::kAvgNetworkLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          AVG_NETWORK_LATENCY_MS;
    case openscreen::cast::StatisticType::kAvgPacketLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::AVG_PACKET_LATENCY_MS;
    case openscreen::cast::StatisticType::kAvgFrameLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::AVG_FRAME_LATENCY_MS;
    case openscreen::cast::StatisticType::kAvgEndToEndLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::AVG_E2E_LATENCY_MS;
    case openscreen::cast::StatisticType::kEncodeRateKbps:
      return media::cast::StatsEventSubscriber::CastStat::ENCODE_KBPS;
    case openscreen::cast::StatisticType::kPacketTransmissionRateKbps:
      return media::cast::StatsEventSubscriber::CastStat::TRANSMISSION_KBPS;
    case openscreen::cast::StatisticType::kTimeSinceLastReceiverResponseMs:
      return media::cast::StatsEventSubscriber::CastStat::
          MS_SINCE_LAST_RECEIVER_RESPONSE;
    case openscreen::cast::StatisticType::kNumFramesCaptured:
      return media::cast::StatsEventSubscriber::CastStat::NUM_FRAMES_CAPTURED;
    case openscreen::cast::StatisticType::kNumFramesDroppedByEncoder:
      return media::cast::StatsEventSubscriber::CastStat::
          NUM_FRAMES_DROPPED_BY_ENCODER;
    case openscreen::cast::StatisticType::kNumLateFrames:
      return media::cast::StatsEventSubscriber::CastStat::NUM_FRAMES_LATE;
    case openscreen::cast::StatisticType::kNumPacketsSent:
      return media::cast::StatsEventSubscriber::CastStat::NUM_PACKETS_SENT;
    case openscreen::cast::StatisticType::kNumPacketsReceived:
      return media::cast::StatsEventSubscriber::CastStat::NUM_PACKETS_RECEIVED;
    case openscreen::cast::StatisticType::kFirstEventTimeMs:
      return media::cast::StatsEventSubscriber::CastStat::FIRST_EVENT_TIME_MS;
    case openscreen::cast::StatisticType::kLastEventTimeMs:
      return media::cast::StatsEventSubscriber::CastStat::LAST_EVENT_TIME_MS;
    default:
      return media::cast::StatsEventSubscriber::CastStat::
          UNKNOWN_OPEN_SCREEN_STAT;
  }
}

media::cast::StatsEventSubscriber::CastStat HistogramTypeToCastStat(
    const openscreen::cast::HistogramType& histogram_type) {
  switch (histogram_type) {
    case openscreen::cast::HistogramType::kCaptureLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          CAPTURE_LATENCY_MS_HISTO;
    case openscreen::cast::HistogramType::kEncodeTimeMs:
      return media::cast::StatsEventSubscriber::CastStat::ENCODE_TIME_MS_HISTO;
    case openscreen::cast::HistogramType::kQueueingLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          QUEUEING_LATENCY_MS_HISTO;
    case openscreen::cast::HistogramType::kNetworkLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          NETWORK_LATENCY_MS_HISTO;
    case openscreen::cast::HistogramType::kPacketLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::
          PACKET_LATENCY_MS_HISTO;
    case openscreen::cast::HistogramType::kEndToEndLatencyMs:
      return media::cast::StatsEventSubscriber::CastStat::E2E_LATENCY_MS_HISTO;
    case openscreen::cast::HistogramType::kFrameLatenessMs:
      return media::cast::StatsEventSubscriber::CastStat::LATE_FRAME_MS_HISTO;
    default:
      return media::cast::StatsEventSubscriber::CastStat::
          UNKNOWN_OPEN_SCREEN_HISTO;
  }
}
}  // namespace

OpenscreenStatsClient::OpenscreenStatsClient() = default;
OpenscreenStatsClient::~OpenscreenStatsClient() = default;

base::DictValue OpenscreenStatsClient::GetStats() const {
  return most_recent_stats_.Clone();
}

base::DictValue OpenscreenStatsClient::ConvertSenderStatsToDict(
    const openscreen::cast::SenderStats& updated_stats) const {
  base::DictValue ret;

  base::DictValue audio_stats =
      ConvertStatisticsListToDict(updated_stats.audio_statistics);
  audio_stats.Merge(
      ConvertHistogramsListToDict(updated_stats.audio_histograms));
  ret.Set(media::cast::StatsEventSubscriber::kAudioStatsDictKey,
          std::move(audio_stats));

  base::DictValue video_stats =
      ConvertStatisticsListToDict(updated_stats.video_statistics);
  video_stats.Merge(
      ConvertHistogramsListToDict(updated_stats.video_histograms));
  ret.Set(media::cast::StatsEventSubscriber::kVideoStatsDictKey,
          std::move(video_stats));
  return ret;
}

base::DictValue OpenscreenStatsClient::ConvertStatisticsListToDict(
    const openscreen::cast::SenderStats::StatisticsList& stats_list) const {
  base::DictValue ret;
  for (std::size_t i = 0; i < stats_list.size(); ++i) {
    const char* key = media::cast::StatsEventSubscriber::CastStatToString(
        StatisticTypeToCastStat(
            static_cast<openscreen::cast::StatisticType>(i)));
    if (std::isfinite(stats_list[i])) {
      ret.Set(key, stats_list[i]);
    } else {
      ret.Set(key, "null");
    }
  }
  return ret;
}

base::DictValue OpenscreenStatsClient::ConvertHistogramsListToDict(
    const openscreen::cast::SenderStats::HistogramsList& histograms_list)
    const {
  base::DictValue ret;
  for (std::size_t i = 0; i < histograms_list.size(); ++i) {
    ret.Set(media::cast::StatsEventSubscriber::CastStatToString(
                HistogramTypeToCastStat(
                    static_cast<openscreen::cast::HistogramType>(i))),
            ConvertOpenscreenHistogramToList(histograms_list[i]));
  }
  return ret;
}

base::ListValue OpenscreenStatsClient::ConvertOpenscreenHistogramToList(
    const openscreen::cast::SimpleHistogram& histogram) const {
  // If there are no buckets then we return an empty list.
  if (histogram.buckets.empty()) {
    return base::ListValue();
  }

  base::ListValue histo;

  if (histogram.buckets.front()) {
    base::DictValue bucket;
    bucket.Set(base::StringPrintf("<%" PRId64, histogram.min),
               histogram.buckets.front());
    histo.Append(std::move(bucket));
  }

  for (std::size_t i = 1; i < histogram.buckets.size() - 1; ++i) {
    if (!histogram.buckets[i]) {
      continue;
    }
    base::DictValue bucket;
    int64_t lower = histogram.min + (i - 1) * histogram.width;
    int64_t upper = lower + histogram.width - 1;
    bucket.Set(base::StringPrintf("%" PRId64 "-%" PRId64, lower, upper),
               histogram.buckets[i]);
    histo.Append(std::move(bucket));
  }

  if (histogram.buckets.back()) {
    base::DictValue bucket;
    bucket.Set(base::StringPrintf(">=%" PRId64, histogram.max),
               histogram.buckets.back());
    histo.Append(std::move(bucket));
  }
  return histo;
}

void OpenscreenStatsClient::OnStatisticsUpdated(
    const openscreen::cast::SenderStats& updated_stats) {
  most_recent_stats_ = ConvertSenderStatsToDict(std::move(updated_stats));
}

}  // namespace mirroring
