/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_
#define DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_

#include "MediaData.h"
#include "mozilla/MozPromise.h"
#include "mozilla/dom/MediaKeySystemAccessBinding.h"
#include "mozilla/dom/MediaKeysBinding.h"
#include "nsString.h"
#include "nsTArray.h"

namespace mozilla {

struct KeySystemConfigRequest;

struct KeySystemConfig {
 public:
  using SupportedConfigsPromise =
      MozPromise<nsTArray<KeySystemConfig>, bool /* aIgnored */,
                 /* IsExclusive = */ true>;
  using KeySystemConfigPromise =
      MozPromise<dom::MediaKeySystemConfiguration, bool /* aIgnored */,
                 /* IsExclusive = */ true>;

  // EME MediaKeysRequirement:
  // https://www.w3.org/TR/encrypted-media/#dom-mediakeysrequirement
  MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(Requirement,
                                                     (Required, Optional,
                                                      NotAllowed));

  // EME MediaKeySessionType:
  // https://www.w3.org/TR/encrypted-media/#dom-mediakeysessiontype
  MOZ_DEFINE_ENUM_CLASS_WITH_TOSTRING_AT_CLASS_SCOPE(SessionType,
                                                     (Temporary,
                                                      PersistentLicense));

  using EMECodecString = nsCString;
  static constexpr auto EME_CODEC_AAC = "aac"_ns;
  static constexpr auto EME_CODEC_OPUS = "opus"_ns;
  static constexpr auto EME_CODEC_VORBIS = "vorbis"_ns;
  static constexpr auto EME_CODEC_FLAC = "flac"_ns;
  static constexpr auto EME_CODEC_H264 = "h264"_ns;
  static constexpr auto EME_CODEC_AV1 = "av1"_ns;
  static constexpr auto EME_CODEC_VP8 = "vp8"_ns;
  static constexpr auto EME_CODEC_VP9 = "vp9"_ns;
  static constexpr auto EME_CODEC_HEVC = "hevc"_ns;

  using EMEEncryptionSchemeString = nsCString;
  static constexpr auto EME_ENCRYPTION_SCHEME_CENC = "cenc"_ns;
  static constexpr auto EME_ENCRYPTION_SCHEME_CBCS = "cbcs"_ns;

  // A codec can be decrypted-and-decoded by the CDM, or only decrypted
  // by the CDM and decoded by Gecko. Not both.
  struct ContainerSupport {
    ContainerSupport() = default;
    ~ContainerSupport() = default;
    ContainerSupport(const ContainerSupport& aOther) {
      mCodecsDecoded = aOther.mCodecsDecoded.Clone();
      mCodecsDecrypted = aOther.mCodecsDecrypted.Clone();
    }
    ContainerSupport& operator=(const ContainerSupport& aOther) {
      if (this == &aOther) {
        return *this;
      }
      mCodecsDecoded = aOther.mCodecsDecoded.Clone();
      mCodecsDecrypted = aOther.mCodecsDecrypted.Clone();
      return *this;
    }
    ContainerSupport(ContainerSupport&& aOther) = default;
    ContainerSupport& operator=(ContainerSupport&&) = default;

    bool IsSupported() const {
      return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
    }

    // True if a codec and scheme pair can be decrypted and decoded
    bool DecryptsAndDecodes(
        const EMECodecString& aCodec,
        const Maybe<CryptoScheme>& aScheme = Nothing()) const {
      return CheckCodecAndSchemePair(mCodecsDecoded, aCodec, aScheme);
    }

    // True if a codec and scheme pair can be decrypted
    bool Decrypts(const EMECodecString& aCodec,
                  const Maybe<CryptoScheme>& aScheme = Nothing()) const {
      return CheckCodecAndSchemePair(mCodecsDecrypted, aCodec, aScheme);
    }

    void SetCanDecryptAndDecode(
        const EMECodecString& aCodec,
        const Maybe<CryptoSchemeSet>& aSchemes = Nothing{}) {
      // Can't both decrypt and decrypt-and-decode a codec.
      MOZ_ASSERT(!ContainsDecryptedOnlyCodec(aCodec));
      // Prevent duplicates.
      MOZ_ASSERT(!ContainsDecryptedAndDecodedCodec(aCodec));

      mCodecsDecoded.AppendElement(CodecSchemePair{aCodec, aSchemes});
    }

    void SetCanDecrypt(const EMECodecString& aCodec,
                       const Maybe<CryptoSchemeSet>& aSchemes = Nothing{}) {
      // Prevent duplicates.
      MOZ_ASSERT(!ContainsDecryptedOnlyCodec(aCodec));
      // Can't both decrypt and decrypt-and-decode a codec.
      MOZ_ASSERT(!ContainsDecryptedAndDecodedCodec(aCodec));
      mCodecsDecrypted.AppendElement(CodecSchemePair{aCodec, aSchemes});
    }

    EMECodecString GetDebugInfo() const {
      EMECodecString info;
      info.AppendLiteral("decoding-and-decrypting:[");
      for (size_t idx = 0; idx < mCodecsDecoded.Length(); idx++) {
        const auto& cur = mCodecsDecoded[idx];
        info.Append(cur.first);
        if (cur.second) {
          info.AppendLiteral("(");
          info.Append(CryptoSchemeSetToString(*cur.second));
          info.AppendLiteral(")");
        } else {
          info.AppendLiteral("(all)");
        }
        if (idx + 1 < mCodecsDecoded.Length()) {
          info.AppendLiteral(",");
        }
      }
      info.AppendLiteral("],");
      info.AppendLiteral("decrypting-only:[");
      for (size_t idx = 0; idx < mCodecsDecrypted.Length(); idx++) {
        const auto& cur = mCodecsDecrypted[idx];
        info.Append(cur.first);
        if (cur.second) {
          info.AppendLiteral("(");
          info.Append(CryptoSchemeSetToString(*cur.second));
          info.AppendLiteral(")");
        } else {
          info.AppendLiteral("(all)");
        }
        if (idx + 1 < mCodecsDecrypted.Length()) {
          info.AppendLiteral(",");
        }
      }
      info.AppendLiteral("]");
      return info;
    }

   private:
    using CodecSchemePair = std::pair<EMECodecString, Maybe<CryptoSchemeSet>>;
    // These two arrays are exclusive, the codec in one array can't appear on
    // another array. If CryptoSchemeSet is nothing, that means the codec has
    // support for all schemes, which is our default. Setting CryptoSchemeSet
    // explicitly can restrict avaiable schemes for a codec.
    nsTArray<CodecSchemePair> mCodecsDecoded;
    nsTArray<CodecSchemePair> mCodecsDecrypted;

    bool ContainsDecryptedOnlyCodec(const EMECodecString& aCodec) const {
      return std::any_of(
          mCodecsDecrypted.begin(), mCodecsDecrypted.end(),
          [&](const auto& aPair) { return aPair.first.Equals(aCodec); });
    }
    bool ContainsDecryptedAndDecodedCodec(const EMECodecString& aCodec) const {
      return std::any_of(
          mCodecsDecoded.begin(), mCodecsDecoded.end(),
          [&](const auto& aPair) { return aPair.first.Equals(aCodec); });
    }
    bool CheckCodecAndSchemePair(const nsTArray<CodecSchemePair>& aArray,
                                 const EMECodecString& aCodec,
                                 const Maybe<CryptoScheme>& aScheme) const {
      return std::any_of(aArray.begin(), aArray.end(), [&](const auto& aPair) {
        if (!aPair.first.Equals(aCodec)) {
          return false;
        }
        // No scheme is specified, which means accepting all schemes.
        if (!aPair.second || !aScheme) {
          return true;
        }
        return aPair.second->contains(*aScheme);
      });
    }
  };

  // Return true if given key system is supported on the current device.
  static bool Supports(const nsAString& aKeySystem);

  enum class DecryptionInfo : uint8_t {
    Software,
    Hardware,
  };
  static RefPtr<SupportedConfigsPromise> CreateKeySystemConfigs(
      const nsTArray<KeySystemConfigRequest>& aRequests);
  static void GetGMPKeySystemConfigs(dom::Promise* aPromise);

  KeySystemConfig() = default;
  ~KeySystemConfig() = default;
  explicit KeySystemConfig(const KeySystemConfig& aOther) {
    mKeySystem = aOther.mKeySystem;
    mInitDataTypes = aOther.mInitDataTypes.Clone();
    mPersistentState = aOther.mPersistentState;
    mDistinctiveIdentifier = aOther.mDistinctiveIdentifier;
    mSessionTypes = aOther.mSessionTypes.Clone();
    mVideoRobustness = aOther.mVideoRobustness.Clone();
    mAudioRobustness = aOther.mAudioRobustness.Clone();
    mMP4 = aOther.mMP4;
    mWebM = aOther.mWebM;
  }
  KeySystemConfig& operator=(const KeySystemConfig& aOther) {
    if (this == &aOther) {
      return *this;
    }
    mKeySystem = aOther.mKeySystem;
    mInitDataTypes = aOther.mInitDataTypes.Clone();
    mPersistentState = aOther.mPersistentState;
    mDistinctiveIdentifier = aOther.mDistinctiveIdentifier;
    mSessionTypes = aOther.mSessionTypes.Clone();
    mVideoRobustness = aOther.mVideoRobustness.Clone();
    mAudioRobustness = aOther.mAudioRobustness.Clone();
    mMP4 = aOther.mMP4;
    mWebM = aOther.mWebM;
    return *this;
  }
  KeySystemConfig(KeySystemConfig&&) = default;
  KeySystemConfig& operator=(KeySystemConfig&&) = default;

  nsString GetDebugInfo() const;

  nsString mKeySystem;
  nsTArray<nsString> mInitDataTypes;
  Requirement mPersistentState = Requirement::NotAllowed;
  Requirement mDistinctiveIdentifier = Requirement::NotAllowed;
  nsTArray<SessionType> mSessionTypes;
  nsTArray<nsString> mVideoRobustness;
  nsTArray<nsString> mAudioRobustness;
  ContainerSupport mMP4;
  ContainerSupport mWebM;

 private:
  static void CreateClearKeyKeySystemConfigs(
      const KeySystemConfigRequest& aRequest,
      nsTArray<KeySystemConfig>& aOutConfigs);
  static void CreateWivineL3KeySystemConfigs(
      const KeySystemConfigRequest& aRequest,
      nsTArray<KeySystemConfig>& aOutConfigs);
};

struct KeySystemConfigRequest final {
  KeySystemConfigRequest(const nsAString& aKeySystem,
                         KeySystemConfig::DecryptionInfo aDecryption,
                         bool aIsPrivateBrowsing)
      : mKeySystem(aKeySystem),
        mDecryption(aDecryption),
        mIsPrivateBrowsing(aIsPrivateBrowsing) {}
  const nsString mKeySystem;
  const KeySystemConfig::DecryptionInfo mDecryption;
  const bool mIsPrivateBrowsing;
};

KeySystemConfig::SessionType ConvertToKeySystemConfigSessionType(
    dom::MediaKeySessionType aType);

}  // namespace mozilla

#endif  // DOM_MEDIA_EME_KEYSYSTEMCONFIG_H_
