// Copyright 2015 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 <string>
#include <vector>

#include "base/bind.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "media/base/eme_constants.h"
#include "media/base/key_systems.h"
#include "media/base/media_permission.h"
#include "media/blink/key_system_config_selector.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/platform/WebEncryptedMediaTypes.h"
#include "third_party/WebKit/public/platform/WebMediaKeySystemConfiguration.h"
#include "third_party/WebKit/public/platform/WebSecurityOrigin.h"
#include "third_party/WebKit/public/platform/WebString.h"
#include "url/gurl.h"

namespace media {

namespace {

const char kSupported[] = "supported";
const char kRecommendIdentifier[] = "recommend_identifier";
const char kRequireIdentifier[] = "require_identifier";
const char kUnsupported[] = "unsupported";

// TODO(sandersd): Audio and video variants (doesn't matter for now because the
// checks are in KeySystems).
const char kSupportedContainer[] = "video/webm";
const char kUnsupportedContainer[] = "video/foo";

// TODO(sandersd): Extended codec variants (requires proprietary codec support).
const char kSupportedCodec[] = "vp8";
const char kUnsupportedCodec[] = "foo";
const char kUnsupportedCodecs[] = "vp8,foo";
const char kSupportedCodecs[] = "vp8,vp8";

const char kDefaultSecurityOrigin[] = "https://example.com/";

// The IDL for MediaKeySystemConfiguration specifies some defaults, so
// create a default object that mimics what would be created if an empty
// dictionary was passed in.
blink::WebMediaKeySystemConfiguration DefaultConfiguration() {
  // http://w3c.github.io/encrypted-media/#mediakeysystemconfiguration-dictionary
  // If this member (sessionTypes) is not present when the dictionary
  // is passed to requestMediaKeySystemAccess(), the dictionary will
  // be treated as if this member is set to [ "temporary" ].
  std::vector<blink::WebEncryptedMediaSessionType> session_types;
  session_types.push_back(blink::WebEncryptedMediaSessionType::Temporary);

  blink::WebMediaKeySystemConfiguration config;
  config.label = "";
  config.sessionTypes = session_types;
  return config;
}

class FakeKeySystems : public KeySystems {
 public:
  ~FakeKeySystems() override {
  }

  bool IsSupportedKeySystem(const std::string& key_system) const override {
    if (key_system == kSupported)
      return true;
    return false;
  }

  // TODO(sandersd): Move implementation into KeySystemConfigSelector?
  bool IsSupportedInitDataType(const std::string& key_system,
                               EmeInitDataType init_data_type) const override {
    switch (init_data_type) {
      case EmeInitDataType::UNKNOWN:
        return false;
      case EmeInitDataType::WEBM:
        return init_data_type_webm_supported_;
      case EmeInitDataType::CENC:
        return init_data_type_cenc_supported_;
      case EmeInitDataType::KEYIDS:
        return init_data_type_keyids_supported_;
    }
    NOTREACHED();
    return false;
  }

  // TODO(sandersd): Secure codec simulation.
  EmeConfigRule GetContentTypeConfigRule(
      const std::string& key_system,
      EmeMediaType media_type,
      const std::string& container_mime_type,
      const std::vector<std::string>& codecs) const override {
    if (container_mime_type == kUnsupportedContainer)
      return EmeConfigRule::NOT_SUPPORTED;
    DCHECK_EQ(kSupportedContainer, container_mime_type);
    for (const std::string& codec : codecs) {
      if (codec == kUnsupportedCodec)
        return EmeConfigRule::NOT_SUPPORTED;
      DCHECK_EQ(kSupportedCodec, codec);
    }
    return EmeConfigRule::SUPPORTED;
  }

  EmeConfigRule GetRobustnessConfigRule(
      const std::string& key_system,
      EmeMediaType media_type,
      const std::string& requested_robustness) const override {
    if (requested_robustness == kUnsupported)
      return EmeConfigRule::NOT_SUPPORTED;
    if (requested_robustness == kRequireIdentifier)
      return EmeConfigRule::IDENTIFIER_REQUIRED;
    if (requested_robustness == kRecommendIdentifier)
      return EmeConfigRule::IDENTIFIER_RECOMMENDED;
    if (requested_robustness == kSupported)
      return EmeConfigRule::SUPPORTED;
    NOTREACHED();
    return EmeConfigRule::NOT_SUPPORTED;
  }

  EmeSessionTypeSupport GetPersistentLicenseSessionSupport(
      const std::string& key_system) const override {
    return persistent_license;
  }

  EmeSessionTypeSupport GetPersistentReleaseMessageSessionSupport(
      const std::string& key_system) const override {
    return persistent_release_message;
  }

  EmeFeatureSupport GetPersistentStateSupport(
      const std::string& key_system) const override {
    return persistent_state;
  }

  EmeFeatureSupport GetDistinctiveIdentifierSupport(
      const std::string& key_system) const override {
    return distinctive_identifier;
  }

  bool init_data_type_webm_supported_ = false;
  bool init_data_type_cenc_supported_ = false;
  bool init_data_type_keyids_supported_ = false;

  // INVALID so that they must be set in any test that needs them.
  EmeSessionTypeSupport persistent_license = EmeSessionTypeSupport::INVALID;
  EmeSessionTypeSupport persistent_release_message =
      EmeSessionTypeSupport::INVALID;

  // Every test implicitly requires these, so they must be set. They are set to
  // values that are likely to cause tests to fail if they are accidentally
  // depended on. Test cases explicitly depending on them should set them, as
  // the default values may be changed.
  EmeFeatureSupport persistent_state = EmeFeatureSupport::NOT_SUPPORTED;
  EmeFeatureSupport distinctive_identifier = EmeFeatureSupport::REQUESTABLE;
};

class FakeMediaPermission : public MediaPermission {
 public:
  void HasPermission(Type type,
                     const GURL& security_origin,
                     const PermissionStatusCB& permission_status_cb) override {
    permission_status_cb.Run(is_granted);
  }

  void RequestPermission(
      Type type,
      const GURL& security_origin,
      const PermissionStatusCB& permission_status_cb) override {
    requests++;
    permission_status_cb.Run(is_granted);
  }

  int requests = 0;
  bool is_granted = false;
};

}  // namespace

class KeySystemConfigSelectorTest : public testing::Test {
 public:
  KeySystemConfigSelectorTest()
      : key_systems_(new FakeKeySystems()),
        media_permission_(new FakeMediaPermission()) {}

  void SelectConfig() {
    media_permission_->requests = 0;
    succeeded_count_ = 0;
    not_supported_count_ = 0;
    KeySystemConfigSelector(key_systems_.get(), media_permission_.get())
        .SelectConfig(key_system_, configs_, security_origin_, false,
                      base::Bind(&KeySystemConfigSelectorTest::OnSucceeded,
                                 base::Unretained(this)),
                      base::Bind(&KeySystemConfigSelectorTest::OnNotSupported,
                                 base::Unretained(this)));
  }

  bool SelectConfigReturnsConfig() {
    SelectConfig();
    EXPECT_EQ(0, media_permission_->requests);
    EXPECT_EQ(1, succeeded_count_);
    EXPECT_EQ(0, not_supported_count_);
    return (succeeded_count_ != 0);
  }

  bool SelectConfigReturnsError() {
    SelectConfig();
    EXPECT_EQ(0, media_permission_->requests);
    EXPECT_EQ(0, succeeded_count_);
    EXPECT_EQ(1, not_supported_count_);
    return (not_supported_count_ != 0);
  }

  bool SelectConfigRequestsPermissionAndReturnsConfig() {
    SelectConfig();
    EXPECT_EQ(1, media_permission_->requests);
    EXPECT_EQ(1, succeeded_count_);
    EXPECT_EQ(0, not_supported_count_);
    return (media_permission_->requests != 0 && succeeded_count_ != 0);
  }

  bool SelectConfigRequestsPermissionAndReturnsError() {
    SelectConfig();
    EXPECT_EQ(1, media_permission_->requests);
    EXPECT_EQ(0, succeeded_count_);
    EXPECT_EQ(1, not_supported_count_);
    return (media_permission_->requests != 0 && not_supported_count_ != 0);
  }

  void OnSucceeded(const blink::WebMediaKeySystemConfiguration& result,
                   const CdmConfig& cdm_config) {
    succeeded_count_++;
    config_ = result;
  }

  void OnNotSupported(const blink::WebString&) { not_supported_count_++; }

  std::unique_ptr<FakeKeySystems> key_systems_;
  std::unique_ptr<FakeMediaPermission> media_permission_;

  // Held values for the call to SelectConfig().
  blink::WebString key_system_ = blink::WebString::fromUTF8(kSupported);
  std::vector<blink::WebMediaKeySystemConfiguration> configs_;
  blink::WebSecurityOrigin security_origin_ =
      blink::WebSecurityOrigin::createFromString(kDefaultSecurityOrigin);

  // Holds the last successful accumulated configuration.
  blink::WebMediaKeySystemConfiguration config_;

  int succeeded_count_;
  int not_supported_count_;

  DISALLOW_COPY_AND_ASSIGN(KeySystemConfigSelectorTest);
};

// --- Basics ---

TEST_F(KeySystemConfigSelectorTest, NoConfigs) {
  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, DefaultConfig) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();

  // label = "";
  ASSERT_EQ("", config.label);

  // initDataTypes = [];
  ASSERT_EQ(0u, config.initDataTypes.size());

  // audioCapabilities = [];
  ASSERT_EQ(0u, config.audioCapabilities.size());

  // videoCapabilities = [];
  ASSERT_EQ(0u, config.videoCapabilities.size());

  // distinctiveIdentifier = "optional";
  ASSERT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Optional,
            config.distinctiveIdentifier);

  // persistentState = "optional";
  ASSERT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Optional,
            config.persistentState);

  // If this member is not present when the dictionary is passed to
  // requestMediaKeySystemAccess(), the dictionary will be treated as
  // if this member is set to [ "temporary" ].
  ASSERT_EQ(1u, config.sessionTypes.size());
  ASSERT_EQ(blink::WebEncryptedMediaSessionType::Temporary,
            config.sessionTypes[0]);
}

// Most of the tests below assume that the default config is valid.
TEST_F(KeySystemConfigSelectorTest, EmptyConfig) {
  configs_.push_back(DefaultConfiguration());

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ("", config_.label);
  EXPECT_TRUE(config_.initDataTypes.isEmpty());
  EXPECT_TRUE(config_.audioCapabilities.isEmpty());
  EXPECT_TRUE(config_.videoCapabilities.isEmpty());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed,
            config_.distinctiveIdentifier);
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed,
            config_.persistentState);
  ASSERT_EQ(1u, config_.sessionTypes.size());
  EXPECT_EQ(blink::WebEncryptedMediaSessionType::Temporary,
            config_.sessionTypes[0]);
}

TEST_F(KeySystemConfigSelectorTest, Label) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.label = "foo";
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ("foo", config_.label);
}

// --- keySystem ---
// Empty is not tested because the empty check is in Blink.

TEST_F(KeySystemConfigSelectorTest, KeySystem_NonAscii) {
  key_system_ = "\xde\xad\xbe\xef";
  configs_.push_back(DefaultConfiguration());
  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, KeySystem_Unsupported) {
  key_system_ = kUnsupported;
  configs_.push_back(DefaultConfiguration());
  ASSERT_TRUE(SelectConfigReturnsError());
}

// --- initDataTypes ---

TEST_F(KeySystemConfigSelectorTest, InitDataTypes_Empty) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
}

TEST_F(KeySystemConfigSelectorTest, InitDataTypes_NoneSupported) {
  key_systems_->init_data_type_webm_supported_ = true;

  std::vector<blink::WebEncryptedMediaInitDataType> init_data_types;
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Unknown);
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Cenc);

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.initDataTypes = init_data_types;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, InitDataTypes_SubsetSupported) {
  key_systems_->init_data_type_webm_supported_ = true;

  std::vector<blink::WebEncryptedMediaInitDataType> init_data_types;
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Unknown);
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Cenc);
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Webm);

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.initDataTypes = init_data_types;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(1u, config_.initDataTypes.size());
  EXPECT_EQ(blink::WebEncryptedMediaInitDataType::Webm,
            config_.initDataTypes[0]);
}

// --- distinctiveIdentifier ---

TEST_F(KeySystemConfigSelectorTest, DistinctiveIdentifier_Default) {
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed,
            config_.distinctiveIdentifier);
}

TEST_F(KeySystemConfigSelectorTest, DistinctiveIdentifier_Forced) {
  media_permission_->is_granted = true;
  key_systems_->distinctive_identifier = EmeFeatureSupport::ALWAYS_ENABLED;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.distinctiveIdentifier);
}

TEST_F(KeySystemConfigSelectorTest, DistinctiveIdentifier_Blocked) {
  key_systems_->distinctive_identifier = EmeFeatureSupport::NOT_SUPPORTED;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Required;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, DistinctiveIdentifier_RequestsPermission) {
  media_permission_->is_granted = true;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Required;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.distinctiveIdentifier);
}

TEST_F(KeySystemConfigSelectorTest, DistinctiveIdentifier_RespectsPermission) {
  media_permission_->is_granted = false;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Required;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsError());
}

// --- persistentState ---

TEST_F(KeySystemConfigSelectorTest, PersistentState_Default) {
  key_systems_->persistent_state = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.persistentState =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed,
            config_.persistentState);
}

TEST_F(KeySystemConfigSelectorTest, PersistentState_Forced) {
  key_systems_->persistent_state = EmeFeatureSupport::ALWAYS_ENABLED;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.persistentState =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.persistentState);
}

TEST_F(KeySystemConfigSelectorTest, PersistentState_Blocked) {
  key_systems_->persistent_state = EmeFeatureSupport::ALWAYS_ENABLED;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.persistentState =
      blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

// --- sessionTypes ---

TEST_F(KeySystemConfigSelectorTest, SessionTypes_Empty) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();

  // Default configuration has [ "temporary" ].
  std::vector<blink::WebEncryptedMediaSessionType> session_types;
  config.sessionTypes = session_types;

  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_TRUE(config_.sessionTypes.isEmpty());
}

TEST_F(KeySystemConfigSelectorTest, SessionTypes_SubsetSupported) {
  // Allow persistent state, as it would be required to be successful.
  key_systems_->persistent_state = EmeFeatureSupport::REQUESTABLE;
  key_systems_->persistent_license = EmeSessionTypeSupport::NOT_SUPPORTED;

  std::vector<blink::WebEncryptedMediaSessionType> session_types;
  session_types.push_back(blink::WebEncryptedMediaSessionType::Temporary);
  session_types.push_back(
      blink::WebEncryptedMediaSessionType::PersistentLicense);

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.sessionTypes = session_types;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, SessionTypes_AllSupported) {
  // Allow persistent state, and expect it to be required.
  key_systems_->persistent_state = EmeFeatureSupport::REQUESTABLE;
  key_systems_->persistent_license = EmeSessionTypeSupport::SUPPORTED;

  std::vector<blink::WebEncryptedMediaSessionType> session_types;
  session_types.push_back(blink::WebEncryptedMediaSessionType::Temporary);
  session_types.push_back(
      blink::WebEncryptedMediaSessionType::PersistentLicense);

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.persistentState =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  config.sessionTypes = session_types;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.persistentState);
  ASSERT_EQ(2u, config_.sessionTypes.size());
  EXPECT_EQ(blink::WebEncryptedMediaSessionType::Temporary,
            config_.sessionTypes[0]);
  EXPECT_EQ(blink::WebEncryptedMediaSessionType::PersistentLicense,
            config_.sessionTypes[1]);
}

TEST_F(KeySystemConfigSelectorTest, SessionTypes_PermissionCanBeRequired) {
  media_permission_->is_granted = true;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;
  key_systems_->persistent_state = EmeFeatureSupport::REQUESTABLE;
  key_systems_->persistent_license =
      EmeSessionTypeSupport::SUPPORTED_WITH_IDENTIFIER;

  std::vector<blink::WebEncryptedMediaSessionType> session_types;
  session_types.push_back(
      blink::WebEncryptedMediaSessionType::PersistentLicense);

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  config.persistentState =
      blink::WebMediaKeySystemConfiguration::Requirement::Optional;
  config.sessionTypes = session_types;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.distinctiveIdentifier);
}

// --- videoCapabilities ---

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_Empty) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_NoneSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(2);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kUnsupportedContainer;
  video_capabilities[1].contentType = "b";
  video_capabilities[1].mimeType = kSupportedContainer;
  video_capabilities[1].codecs = kUnsupportedCodec;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_SubsetSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(2);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kUnsupportedContainer;
  video_capabilities[1].contentType = "b";
  video_capabilities[1].mimeType = kSupportedContainer;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(1u, config_.videoCapabilities.size());
  EXPECT_EQ("b", config_.videoCapabilities[0].contentType);
  EXPECT_EQ(kSupportedContainer, config_.videoCapabilities[0].mimeType);
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_AllSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(2);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[1].contentType = "b";
  video_capabilities[1].mimeType = kSupportedContainer;
  video_capabilities[1].codecs = kSupportedCodecs;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(2u, config_.videoCapabilities.size());
  EXPECT_EQ("a", config_.videoCapabilities[0].contentType);
  EXPECT_EQ("b", config_.videoCapabilities[1].contentType);
}

TEST_F(KeySystemConfigSelectorTest,
       VideoCapabilities_Codecs_SubsetSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].codecs = kUnsupportedCodecs;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_Codecs_AllSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].codecs = kSupportedCodecs;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(1u, config_.videoCapabilities.size());
  EXPECT_EQ(kSupportedCodecs, config_.videoCapabilities[0].codecs);
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_Robustness_Supported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].robustness = kSupported;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(1u, config_.videoCapabilities.size());
  EXPECT_EQ(kSupported, config_.videoCapabilities[0].robustness);
}

TEST_F(KeySystemConfigSelectorTest, VideoCapabilities_Robustness_Unsupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].robustness = kUnsupported;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsError());
}

TEST_F(KeySystemConfigSelectorTest,
       VideoCapabilities_Robustness_PermissionCanBeRequired) {
  media_permission_->is_granted = true;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].robustness = kRequireIdentifier;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::Required,
            config_.distinctiveIdentifier);
}

TEST_F(KeySystemConfigSelectorTest,
       VideoCapabilities_Robustness_PermissionCanBeRecommended) {
  media_permission_->is_granted = false;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  std::vector<blink::WebMediaKeySystemMediaCapability> video_capabilities(1);
  video_capabilities[0].contentType = "a";
  video_capabilities[0].mimeType = kSupportedContainer;
  video_capabilities[0].robustness = kRecommendIdentifier;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.videoCapabilities = video_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  EXPECT_EQ(blink::WebMediaKeySystemConfiguration::Requirement::NotAllowed,
            config_.distinctiveIdentifier);
}

// --- audioCapabilities ---
// These are handled by the same code as |videoCapabilities|, so only minimal
// additional testing is done.

TEST_F(KeySystemConfigSelectorTest, AudioCapabilities_SubsetSupported) {
  std::vector<blink::WebMediaKeySystemMediaCapability> audio_capabilities(2);
  audio_capabilities[0].contentType = "a";
  audio_capabilities[0].mimeType = kUnsupportedContainer;
  audio_capabilities[1].contentType = "b";
  audio_capabilities[1].mimeType = kSupportedContainer;

  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.audioCapabilities = audio_capabilities;
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ(1u, config_.audioCapabilities.size());
  EXPECT_EQ("b", config_.audioCapabilities[0].contentType);
  EXPECT_EQ(kSupportedContainer, config_.audioCapabilities[0].mimeType);
}

// --- Multiple configurations ---

TEST_F(KeySystemConfigSelectorTest, Configurations_AllSupported) {
  blink::WebMediaKeySystemConfiguration config = DefaultConfiguration();
  config.label = "a";
  configs_.push_back(config);
  config.label = "b";
  configs_.push_back(config);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ("a", config_.label);
}

TEST_F(KeySystemConfigSelectorTest, Configurations_SubsetSupported) {
  blink::WebMediaKeySystemConfiguration config1 = DefaultConfiguration();
  config1.label = "a";
  std::vector<blink::WebEncryptedMediaInitDataType> init_data_types;
  init_data_types.push_back(blink::WebEncryptedMediaInitDataType::Unknown);
  config1.initDataTypes = init_data_types;
  configs_.push_back(config1);

  blink::WebMediaKeySystemConfiguration config2 = DefaultConfiguration();
  config2.label = "b";
  configs_.push_back(config2);

  ASSERT_TRUE(SelectConfigReturnsConfig());
  ASSERT_EQ("b", config_.label);
}

TEST_F(KeySystemConfigSelectorTest,
       Configurations_FirstRequiresPermission_Allowed) {
  media_permission_->is_granted = true;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config1 = DefaultConfiguration();
  config1.label = "a";
  config1.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Required;
  configs_.push_back(config1);

  blink::WebMediaKeySystemConfiguration config2 = DefaultConfiguration();
  config2.label = "b";
  configs_.push_back(config2);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  ASSERT_EQ("a", config_.label);
}

TEST_F(KeySystemConfigSelectorTest,
       Configurations_FirstRequiresPermission_Rejected) {
  media_permission_->is_granted = false;
  key_systems_->distinctive_identifier = EmeFeatureSupport::REQUESTABLE;

  blink::WebMediaKeySystemConfiguration config1 = DefaultConfiguration();
  config1.label = "a";
  config1.distinctiveIdentifier =
      blink::WebMediaKeySystemConfiguration::Requirement::Required;
  configs_.push_back(config1);

  blink::WebMediaKeySystemConfiguration config2 = DefaultConfiguration();
  config2.label = "b";
  configs_.push_back(config2);

  ASSERT_TRUE(SelectConfigRequestsPermissionAndReturnsConfig());
  ASSERT_EQ("b", config_.label);
}

}  // namespace media
