// Copyright 2019 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/sharing_message/sharing_metrics.h"

#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/sharing_message/sharing_device_registration_result.h"
#include "components/version_info/version_info.h"

namespace {
const char* GetEnumStringValue(SharingFeatureName feature) {
  DCHECK(feature != SharingFeatureName::kUnknown)
      << "Feature needs to be specified for metrics logging.";

  switch (feature) {
    case SharingFeatureName::kUnknown:
      return "Unknown";
    case SharingFeatureName::kSmsRemoteFetcher:
      return "SmsRemoteFetcher";
  }
}

// Maps SharingChannelType enum values to strings used as histogram
// suffixes. Keep in sync with "SharingChannelType" in histograms.xml.
std::string SharingChannelTypeToString(SharingChannelType channel_type) {
  switch (channel_type) {
    case SharingChannelType::kUnknown:
      return "Unknown";
    case SharingChannelType::kFcmSenderId:
      return "FcmSenderId";
    case SharingChannelType::kServer:
      return "Server";
    case SharingChannelType::kWebRtc:
      return "WebRTC";
    case SharingChannelType::kIosPush:
      return "IosPush";
  }
}

// Maps SharingDevicePlatform enum values to strings used as histogram
// suffixes. Keep in sync with "SharingDevicePlatform" in histograms.xml.
std::string DevicePlatformToString(SharingDevicePlatform device_platform) {
  switch (device_platform) {
    case SharingDevicePlatform::kAndroid:
      return "Android";
    case SharingDevicePlatform::kChromeOS:
      return "ChromeOS";
    case SharingDevicePlatform::kIOS:
      return "iOS";
    case SharingDevicePlatform::kLinux:
      return "Linux";
    case SharingDevicePlatform::kMac:
      return "Mac";
    case SharingDevicePlatform::kWindows:
      return "Windows";
    case SharingDevicePlatform::kServer:
      return "Server";
    case SharingDevicePlatform::kUnknown:
      return "Unknown";
  }
}

// Maps pulse intervals to strings used as histogram suffixes. Keep in sync with
// "SharingPulseInterval" in histograms.xml.
std::string PulseIntervalToString(base::TimeDelta pulse_interval) {
  if (pulse_interval < base::Hours(4)) {
    return "PulseIntervalShort";
  }
  if (pulse_interval > base::Hours(12)) {
    return "PulseIntervalLong";
  }
  return "PulseIntervalMedium";
}

// Major Chrome version comparison with the receiver device.
// These values are logged to UMA. Entries should not be renumbered and numeric
// values should never be reused. Please keep in sync with
// "SharingMajorVersionComparison" in enums.xml.
enum class SharingMajorVersionComparison {
  kUnknown = 0,
  kSenderIsLower = 1,
  kSame = 2,
  kSenderIsHigher = 3,
  kMaxValue = kSenderIsHigher,
};
}  // namespace

std::string SharingSendMessageResultToString(SharingSendMessageResult result) {
  switch (result) {
    case SharingSendMessageResult::kSuccessful:
      return "Successful";
    case SharingSendMessageResult::kDeviceNotFound:
      return "DeviceNotFound";
    case SharingSendMessageResult::kNetworkError:
      return "NetworkError";
    case SharingSendMessageResult::kPayloadTooLarge:
      return "PayloadTooLarge";
    case SharingSendMessageResult::kAckTimeout:
      return "AckTimeout";
    case SharingSendMessageResult::kInternalError:
      return "InternalError";
    case SharingSendMessageResult::kEncryptionError:
      return "EncryptionError";
    case SharingSendMessageResult::kCommitTimeout:
      return "CommitTimeout";
    case SharingSendMessageResult::kCancelled:
      return "RequestCancelled";
  }
}

sharing_message::MessageType SharingPayloadCaseToMessageType(
    components_sharing_message::SharingMessage::PayloadCase payload_case) {
  switch (payload_case) {
    case components_sharing_message::SharingMessage::PAYLOAD_NOT_SET:
      return sharing_message::UNKNOWN_MESSAGE;
    case components_sharing_message::SharingMessage::kPingMessage:
      return sharing_message::PING_MESSAGE;
    case components_sharing_message::SharingMessage::kAckMessage:
      return sharing_message::ACK_MESSAGE;
    case components_sharing_message::SharingMessage::kSmsFetchRequest:
      return sharing_message::SMS_FETCH_REQUEST;
    case components_sharing_message::SharingMessage::kRemoteCopyMessage:
      return sharing_message::REMOTE_COPY_MESSAGE;
    case components_sharing_message::SharingMessage::
        kPeerConnectionOfferMessage:
      return sharing_message::PEER_CONNECTION_OFFER_MESSAGE;
    case components_sharing_message::SharingMessage::
        kPeerConnectionIceCandidatesMessage:
      return sharing_message::PEER_CONNECTION_ICE_CANDIDATES_MESSAGE;
    case components_sharing_message::SharingMessage::kDiscoveryRequest:
      return sharing_message::DISCOVERY_REQUEST;
    case components_sharing_message::SharingMessage::kWebRtcSignalingFrame:
      return sharing_message::WEB_RTC_SIGNALING_FRAME;
    case components_sharing_message::SharingMessage::
        kOptimizationGuidePushNotification:
      return sharing_message::OPTIMIZATION_GUIDE_PUSH_NOTIFICATION;
    case components_sharing_message::SharingMessage::
        kOneTimeTokenBackendNotification:
      return sharing_message::ONE_TIME_TOKEN_BACKEND_NOTIFICATION;
    case components_sharing_message::SharingMessage::
        kGlicExperimentalTriggering:
      return sharing_message::GLIC_EXPERIMENTAL_TRIGGERING;
  }
  // For proto3 enums unrecognized enum values are kept when parsing, and a new
  // payload case received over the network would not default to
  // PAYLOAD_NOT_SET. Explicitly return UNKNOWN_MESSAGE here to handle this
  // case.
  return sharing_message::UNKNOWN_MESSAGE;
}

sharing_message::MessageType SharingPayloadCaseToMessageType(
    sync_pb::UnencryptedSharingMessage::PayloadCase payload_case) {
  switch (payload_case) {
    case sync_pb::UnencryptedSharingMessage::PAYLOAD_NOT_SET:
      return sharing_message::UNKNOWN_MESSAGE;
    case sync_pb::UnencryptedSharingMessage::kSendTabMessage:
      return sharing_message::SEND_TAB_TO_SELF_PUSH_NOTIFICATION;
    case sync_pb::UnencryptedSharingMessage::kDesktopToMobilePromoMessage:
      return sharing_message::DESKTOP_TO_MOBILE_PROMO_PUSH_NOTIFICATION;
  }
  // For proto3 enums unrecognized enum values are kept when parsing, and a new
  // payload case received over the network would not default to
  // PAYLOAD_NOT_SET. Explicitly return UNKNOWN_MESSAGE here to handle this
  // case.
  return sharing_message::UNKNOWN_MESSAGE;
}

const std::string& SharingMessageTypeToString(
    sharing_message::MessageType message_type) {
  // For proto3 enums unrecognized enum values are kept when parsing and their
  // name is an empty string. We don't want to use that as a histogram suffix.
  if (!sharing_message::MessageType_IsValid(message_type)) {
    return sharing_message::MessageType_Name(sharing_message::UNKNOWN_MESSAGE);
  }
  return sharing_message::MessageType_Name(message_type);
}

int GenerateSharingTraceId() {
  static int next_id = 0;
  return next_id++;
}

void LogSharingMessageReceived(
    components_sharing_message::SharingMessage::PayloadCase payload_case) {
  base::UmaHistogramExactLinear("Sharing.MessageReceivedType",
                                SharingPayloadCaseToMessageType(payload_case),
                                sharing_message::MessageType_ARRAYSIZE);
}

void LogSharingDevicesToShow(SharingFeatureName feature,
                             const char* histogram_suffix,
                             int count) {
  auto* feature_str = GetEnumStringValue(feature);
  // Explicitly log both the base and the suffixed histogram because the base
  // aggregation is not automatically generated.
  base::UmaHistogramExactLinear(
      base::StrCat({"Sharing.", feature_str, "DevicesToShow"}), count,
      /*value_max=*/20);
  if (!histogram_suffix) {
    return;
  }
  base::UmaHistogramExactLinear(
      base::StrCat(
          {"Sharing.", feature_str, "DevicesToShow.", histogram_suffix}),
      count,
      /*value_max=*/20);
}

void LogSharingAppsToShow(SharingFeatureName feature,
                          const char* histogram_suffix,
                          int count) {
  auto* feature_str = GetEnumStringValue(feature);
  // Explicitly log both the base and the suffixed histogram because the base
  // aggregation is not automatically generated.
  base::UmaHistogramExactLinear(
      base::StrCat({"Sharing.", feature_str, "AppsToShow"}), count,
      /*value_max=*/20);
  if (!histogram_suffix) {
    return;
  }
  base::UmaHistogramExactLinear(
      base::StrCat({"Sharing.", feature_str, "AppsToShow.", histogram_suffix}),
      count,
      /*value_max=*/20);
}

void LogSharingSelectedIndex(SharingFeatureName feature,
                             const char* histogram_suffix,
                             int index,
                             SharingIndexType index_type) {
  auto* feature_str = GetEnumStringValue(feature);
  // Explicitly log both the base and the suffixed histogram because the base
  // aggregation is not automatically generated.
  std::string name = base::StrCat(
      {"Sharing.", feature_str, "Selected",
       (index_type == SharingIndexType::kDevice) ? "Device" : "App", "Index"});
  base::UmaHistogramExactLinear(name, index, /*value_max=*/20);
  if (!histogram_suffix) {
    return;
  }
  base::UmaHistogramExactLinear(base::StrCat({name, ".", histogram_suffix}),
                                index,
                                /*value_max=*/20);
}

void LogSharingDialogShown(SharingFeatureName feature, SharingDialogType type) {
  base::UmaHistogramEnumeration(
      base::StrCat({"Sharing.", GetEnumStringValue(feature), "DialogShown"}),
      type);
}

void LogSendSharingMessageResult(
    sharing_message::MessageType message_type,
    SharingDevicePlatform receiving_device_platform,
    SharingChannelType channel_type,
    base::TimeDelta pulse_interval,
    SharingSendMessageResult result) {
  const std::string metric_prefix = "Sharing.SendMessageResult";

  base::UmaHistogramEnumeration(metric_prefix, result);

  base::UmaHistogramEnumeration(
      base::StrCat(
          {metric_prefix, ".", SharingMessageTypeToString(message_type)}),
      result);

  base::UmaHistogramEnumeration(
      base::StrCat({metric_prefix, ".",
                    DevicePlatformToString(receiving_device_platform)}),
      result);

  base::UmaHistogramEnumeration(
      base::StrCat({metric_prefix, ".",
                    DevicePlatformToString(receiving_device_platform), ".",
                    SharingMessageTypeToString(message_type)}),
      result);

  base::UmaHistogramEnumeration(
      base::StrCat(
          {metric_prefix, ".", SharingChannelTypeToString(channel_type)}),
      result);

  // There is no "invalid" bucket so only log valid pulse intervals.
  if (!pulse_interval.is_zero()) {
    base::UmaHistogramEnumeration(
        base::StrCat(
            {metric_prefix, ".", PulseIntervalToString(pulse_interval)}),
        result);
    base::UmaHistogramEnumeration(
        base::StrCat({metric_prefix, ".",
                      DevicePlatformToString(receiving_device_platform), ".",
                      PulseIntervalToString(pulse_interval)}),
        result);
  }
}
