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

#include "media/blink/webmediacapabilitiesclient_impl.h"

#include <string>
#include <vector>

#include "base/bind_helpers.h"
#include "media/base/audio_codecs.h"
#include "media/base/decode_capabilities.h"
#include "media/base/mime_util.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/filters/stream_parser_factory.h"
#include "media/mojo/interfaces/media_types.mojom.h"
#include "mojo/public/cpp/bindings/associated_interface_ptr.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h"
#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h"
#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
#include "third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/scoped_web_callbacks.h"

namespace media {

void BindToHistoryService(mojom::VideoDecodePerfHistoryPtr* history_ptr) {
  DVLOG(2) << __func__;
  blink::Platform* platform = blink::Platform::Current();
  service_manager::Connector* connector = platform->GetConnector();

  connector->BindInterface(platform->GetBrowserServiceName(),
                           mojo::MakeRequest(history_ptr));
}

bool CheckAudioSupport(const blink::WebAudioConfiguration& audio_config) {
  bool audio_supported = false;
  AudioCodec audio_codec = kUnknownAudioCodec;
  bool is_audio_codec_ambiguous = true;

  if (!ParseAudioCodecString(audio_config.mime_type.Ascii(),
                             audio_config.codec.Ascii(),
                             &is_audio_codec_ambiguous, &audio_codec)) {
    // TODO(chcunningham): Replace this and other DVLOGs here with MEDIA_LOG.
    // MediaCapabilities may need its own tab in chrome://media-internals.
    DVLOG(2) << __func__ << " Failed to parse audio contentType: "
             << audio_config.mime_type.Ascii()
             << "; codecs=" << audio_config.codec.Ascii();
    audio_supported = false;
  } else if (is_audio_codec_ambiguous) {
    DVLOG(2) << __func__ << " Invalid (ambiguous) audio codec string:"
             << audio_config.codec.Ascii();
    audio_supported = false;
  } else {
    AudioConfig audio_config = {audio_codec};
    audio_supported = IsSupportedAudioConfig(audio_config);
  }

  return audio_supported;
}

bool CheckVideoSupport(const blink::WebVideoConfiguration& video_config,
                       VideoCodecProfile* out_video_profile) {
  bool video_supported = false;
  VideoCodec video_codec = kUnknownVideoCodec;
  uint8_t video_level = 0;
  VideoColorSpace video_color_space;
  bool is_video_codec_ambiguous = true;

  if (!ParseVideoCodecString(
          video_config.mime_type.Ascii(), video_config.codec.Ascii(),
          &is_video_codec_ambiguous, &video_codec, out_video_profile,
          &video_level, &video_color_space)) {
    DVLOG(2) << __func__ << " Failed to parse video contentType: "
             << video_config.mime_type.Ascii()
             << "; codecs=" << video_config.codec.Ascii();
    video_supported = false;
  } else if (is_video_codec_ambiguous) {
    DVLOG(2) << __func__ << " Invalid (ambiguous) video codec string:"
             << video_config.codec.Ascii();
    video_supported = false;
  } else {
    video_supported = IsSupportedVideoConfig(
        {video_codec, *out_video_profile, video_level, video_color_space});
  }

  return video_supported;
}

bool CheckMseSupport(const blink::WebMediaConfiguration& configuration) {
  DCHECK_EQ(blink::MediaConfigurationType::kMediaSource, configuration.type);

  // For MSE queries, we assume the queried audio and video streams will be
  // placed into separate source buffers.
  // TODO(chcunningham): Clarify this assumption in the spec.

  // Media MIME API expects a vector of codec strings. We query audio and video
  // separately, so |codec_string|.size() should always be 1 or 0 (when no
  // codecs parameter is required for the given mime type).
  std::vector<std::string> codec_vector;

  if (configuration.audio_configuration) {
    const blink::WebAudioConfiguration& audio_config =
        configuration.audio_configuration.value();

    if (!audio_config.codec.Ascii().empty())
      codec_vector.push_back(audio_config.codec.Ascii());

    if (!media::StreamParserFactory::IsTypeSupported(
            audio_config.mime_type.Ascii(), codec_vector)) {
      DVLOG(2) << __func__ << " MSE does not support audio config: "
               << audio_config.mime_type.Ascii() << " "
               << (codec_vector.empty() ? "" : codec_vector[1]);
      return false;
    }
  }

  if (configuration.video_configuration) {
    const blink::WebVideoConfiguration& video_config =
        configuration.video_configuration.value();

    codec_vector.clear();
    if (!video_config.codec.Ascii().empty())
      codec_vector.push_back(video_config.codec.Ascii());

    if (!media::StreamParserFactory::IsTypeSupported(
            video_config.mime_type.Ascii(), codec_vector)) {
      DVLOG(2) << __func__ << " MSE does not support video config: "
               << video_config.mime_type.Ascii() << " "
               << (codec_vector.empty() ? "" : codec_vector[1]);
      return false;
    }
  }

  return true;
}

WebMediaCapabilitiesClientImpl::WebMediaCapabilitiesClientImpl() = default;

WebMediaCapabilitiesClientImpl::~WebMediaCapabilitiesClientImpl() = default;

namespace {
void VideoPerfInfoCallback(
    blink::ScopedWebCallbacks<blink::WebMediaCapabilitiesQueryCallbacks>
        scoped_callbacks,
    std::unique_ptr<blink::WebMediaCapabilitiesInfo> info,
    bool is_smooth,
    bool is_power_efficient) {
  DCHECK(info->supported);
  info->smooth = is_smooth;
  info->power_efficient = is_power_efficient;
  scoped_callbacks.PassCallbacks()->OnSuccess(std::move(info));
}

void OnGetPerfInfoError(
    std::unique_ptr<blink::WebMediaCapabilitiesQueryCallbacks> callbacks) {
  callbacks->OnError();
}
}  // namespace

void WebMediaCapabilitiesClientImpl::DecodingInfo(
    const blink::WebMediaConfiguration& configuration,
    std::unique_ptr<blink::WebMediaCapabilitiesQueryCallbacks> callbacks) {
  std::unique_ptr<blink::WebMediaCapabilitiesInfo> info(
      new blink::WebMediaCapabilitiesInfo());

  // MSE support is cheap to check (regex matching). Do it first.
  if (configuration.type == blink::MediaConfigurationType::kMediaSource &&
      !CheckMseSupport(configuration)) {
    info->supported = info->smooth = info->power_efficient = false;
    callbacks->OnSuccess(std::move(info));
    return;
  }

  bool audio_supported = true;
  if (configuration.audio_configuration)
    audio_supported =
        CheckAudioSupport(configuration.audio_configuration.value());

  // No need to check video capabilities if video not included in configuration
  // or when audio is already known to be unsupported.
  if (!audio_supported || !configuration.video_configuration) {
    // Supported audio-only configurations are always considered smooth and
    // power efficient.
    info->supported = info->smooth = info->power_efficient = audio_supported;
    callbacks->OnSuccess(std::move(info));
    return;
  }

  // Audio is supported and video configuration is provided in the query; all
  // that remains is to check video support and performance.
  DCHECK(audio_supported);
  DCHECK(configuration.video_configuration);
  const blink::WebVideoConfiguration& video_config =
      configuration.video_configuration.value();
  VideoCodecProfile video_profile = VIDEO_CODEC_PROFILE_UNKNOWN;
  bool video_supported = CheckVideoSupport(video_config, &video_profile);

  // Return early for unsupported configurations.
  if (!video_supported) {
    info->supported = info->smooth = info->power_efficient = video_supported;
    callbacks->OnSuccess(std::move(info));
    return;
  }

  // Video is supported! Check its performance history.
  info->supported = true;

  if (!decode_history_ptr_.is_bound())
    BindToHistoryService(&decode_history_ptr_);
  DCHECK(decode_history_ptr_.is_bound());

  mojom::PredictionFeaturesPtr features = mojom::PredictionFeatures::New(
      video_profile, gfx::Size(video_config.width, video_config.height),
      video_config.framerate);

  decode_history_ptr_->GetPerfInfo(
      std::move(features),
      base::BindOnce(
          &VideoPerfInfoCallback,
          blink::MakeScopedWebCallbacks(std::move(callbacks),
                                        base::BindOnce(&OnGetPerfInfoError)),
          std::move(info)));
}

void WebMediaCapabilitiesClientImpl::BindVideoDecodePerfHistoryForTests(
    mojom::VideoDecodePerfHistoryPtr decode_history_ptr) {
  decode_history_ptr_ = std::move(decode_history_ptr);
}

}  // namespace media
