/* 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/. */

#include "mozilla/EMEUtils.h"

#include "KeySystemConfig.h"
#include "MediaData.h"
#include "jsfriendapi.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/dom/BufferSourceBinding.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/KeySystemNames.h"
#include "mozilla/dom/UnionTypes.h"
#include "nsContentUtils.h"
#include "nsIScriptObjectPrincipal.h"

#ifdef MOZ_WMF_CDM
#  include "mozilla/PMFCDM.h"
#endif

namespace mozilla {

LogModule* GetEMELog() {
  static LazyLogModule log("EME");
  return log;
}

LogModule* GetEMEVerboseLog() {
  static LazyLogModule log("EMEV");
  return log;
}

void CopyArrayBufferViewOrArrayBufferData(
    const dom::BufferSource& aBufferOrView, nsTArray<uint8_t>& aOutData) {
  aOutData.Clear();
  (void)dom::AppendTypedArrayDataTo(aBufferOrView, aOutData);
}

bool IsClearkeyKeySystem(const nsAString& aKeySystem) {
  if (StaticPrefs::media_clearkey_test_key_systems_enabled()) {
    return aKeySystem.EqualsLiteral(kClearKeyKeySystemName) ||
           aKeySystem.EqualsLiteral(kClearKeyWithProtectionQueryKeySystemName);
  }
  return aKeySystem.EqualsLiteral(kClearKeyKeySystemName);
}

bool IsWidevineKeySystem(const nsAString& aKeySystem) {
  return aKeySystem.EqualsLiteral(kWidevineKeySystemName);
}

#ifdef MOZ_WMF_CDM
bool IsMediaFoundationCDMPlaybackEnabled() {
  // 1=enabled encrypted and clear, 2=enabled encrypted.
  return StaticPrefs::media_wmf_media_engine_enabled() == 1 ||
         StaticPrefs::media_wmf_media_engine_enabled() == 2;
}

bool IsPlayReadyEnabled() {
  return StaticPrefs::media_eme_playready_enabled() &&
         IsMediaFoundationCDMPlaybackEnabled();
}

bool IsPlayReadyKeySystemAndSupported(const nsAString& aKeySystem) {
  if (!IsPlayReadyEnabled()) {
    return false;
  }
  return aKeySystem.EqualsLiteral(kPlayReadyKeySystemName) ||
         aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) ||
         aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName);
}

bool IsWidevineHardwareDecryptionEnabled() {
  return StaticPrefs::media_eme_widevine_experiment_enabled() &&
         IsMediaFoundationCDMPlaybackEnabled();
}

bool IsWidevineExperimentKeySystemAndSupported(const nsAString& aKeySystem) {
  if (!IsWidevineHardwareDecryptionEnabled()) {
    return false;
  }
  return aKeySystem.EqualsLiteral(kWidevineExperimentKeySystemName) ||
         aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName);
}

bool IsWMFClearKeySystemAndSupported(const nsAString& aKeySystem) {
  if (!StaticPrefs::media_eme_wmf_clearkey_enabled()) {
    return false;
  }
  if (!IsMediaFoundationCDMPlaybackEnabled()) {
    return false;
  }
  return aKeySystem.EqualsLiteral(kClearKeyKeySystemName);
}
#endif

nsString KeySystemToProxyName(const nsAString& aKeySystem) {
  if (IsClearkeyKeySystem(aKeySystem)) {
#ifdef MOZ_WMF_CDM
    if (StaticPrefs::media_eme_wmf_clearkey_enabled()) {
      return u"mfcdm-clearkey"_ns;
    }
#endif
    return u"gmp-clearkey"_ns;
  }
  if (IsWidevineKeySystem(aKeySystem)) {
    return u"gmp-widevinecdm"_ns;
  }
#ifdef MOZ_WMF_CDM
  if (IsPlayReadyKeySystemAndSupported(aKeySystem)) {
    return u"mfcdm-playready"_ns;
  }
  if (IsWidevineExperimentKeySystemAndSupported(aKeySystem)) {
    return u"mfcdm-widevine"_ns;
  }
#endif
  MOZ_ASSERT_UNREACHABLE("Not supported key system!");
  return u""_ns;
}

bool IsHardwareDecryptionSupported(
    const dom::MediaKeySystemConfiguration& aConfig) {
  for (const auto& capabilities : aConfig.mAudioCapabilities) {
    if (capabilities.mRobustness.EqualsLiteral("HW_SECURE_ALL")) {
      return true;
    }
  }
  for (const auto& capabilities : aConfig.mVideoCapabilities) {
    if (capabilities.mRobustness.EqualsLiteral("3000") ||
        capabilities.mRobustness.EqualsLiteral("HW_SECURE_ALL") ||
        capabilities.mRobustness.EqualsLiteral("HW_SECURE_DECODE")) {
      return true;
    }
  }
  return false;
}

bool IsHardwareDecryptionSupported(const KeySystemConfig& aConfig) {
  for (const auto& robustness : aConfig.mAudioRobustness) {
    if (robustness.EqualsLiteral("HW_SECURE_ALL")) {
      return true;
    }
  }
  for (const auto& robustness : aConfig.mVideoRobustness) {
    if (robustness.EqualsLiteral("3000") ||
        robustness.EqualsLiteral("HW_SECURE_ALL") ||
        robustness.EqualsLiteral("HW_SECURE_DECODE")) {
      return true;
    }
  }
  return false;
}

#ifdef MOZ_WMF_CDM
void MFCDMCapabilitiesIPDLToKeySystemConfig(
    const MFCDMCapabilitiesIPDL& aCDMConfig,
    KeySystemConfig& aKeySystemConfig) {
  aKeySystemConfig.mKeySystem = aCDMConfig.keySystem();

  for (const auto& type : aCDMConfig.initDataTypes()) {
    aKeySystemConfig.mInitDataTypes.AppendElement(type);
  }

  for (const auto& type : aCDMConfig.sessionTypes()) {
    aKeySystemConfig.mSessionTypes.AppendElement(type);
  }

  for (const auto& c : aCDMConfig.videoCapabilities()) {
    if (!c.robustness().IsEmpty() &&
        !aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) {
      aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness());
    }
    CryptoSchemeSet schemes;
    for (const auto& scheme : c.encryptionSchemes()) {
      schemes += scheme;
    }
    aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
        NS_ConvertUTF16toUTF8(c.contentType()), Some(schemes));
  }
  for (const auto& c : aCDMConfig.audioCapabilities()) {
    if (!c.robustness().IsEmpty() &&
        !aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) {
      aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness());
    }
    CryptoSchemeSet schemes;
    for (const auto& scheme : c.encryptionSchemes()) {
      schemes += scheme;
    }
    aKeySystemConfig.mMP4.SetCanDecryptAndDecode(
        NS_ConvertUTF16toUTF8(c.contentType()), Some(schemes));
  }
  aKeySystemConfig.mPersistentState = aCDMConfig.persistentState();
  aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID();
  EME_LOG("New Capabilities={}",
          NS_ConvertUTF16toUTF8(aKeySystemConfig.GetDebugInfo()).get());
}
#endif

bool DoesKeySystemSupportClearLead(const nsAString& aKeySystem) {
  // I believe that Widevine L3 supports clear-lead, but I couldn't find any
  // official documentation to prove that. The only one I can find is that Shaka
  // player mentions the clear lead feature. So we expect L3 should have that as
  // well. But for HWDRM, Widevine L1 and SL3000 needs to rely on special checks
  // to know whether clearlead is supported. That will be implemented by
  // querying for special key system names.
  // https://shaka-project.github.io/shaka-packager/html/documentation.html
#ifdef MOZ_WMF_CDM
  if (aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName) ||
      aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName)) {
    return true;
  }
#endif
  return aKeySystem.EqualsLiteral(kWidevineKeySystemName);
}

bool CheckIfHarewareDRMConfigExists(
    const nsTArray<dom::MediaKeySystemConfiguration>& aConfigs) {
  bool foundHWDRMconfig = false;
  for (const auto& config : aConfigs) {
    if (IsHardwareDecryptionSupported(config)) {
      foundHWDRMconfig = true;
      break;
    }
  }
  return foundHWDRMconfig;
}

bool DoesKeySystemSupportHardwareDecryption(const nsAString& aKeySystem) {
#ifdef MOZ_WMF_CDM
  if (aKeySystem.EqualsLiteral(kPlayReadyKeySystemHardware) ||
      aKeySystem.EqualsLiteral(kPlayReadyHardwareClearLeadKeySystemName) ||
      aKeySystem.EqualsLiteral(kWidevineExperimentKeySystemName) ||
      aKeySystem.EqualsLiteral(kWidevineExperiment2KeySystemName)) {
    return true;
  }
#endif
  return false;
}

void DeprecationWarningLog(const dom::Document* aDocument,
                           const char* aMsgName) {
  if (!aDocument || !aMsgName) {
    return;
  }
  EME_LOG("DeprecationWarning Logging deprecation warning '{}' to WebConsole.",
          aMsgName);
  nsTHashMap<nsCharPtrHashKey, bool> warnings;
  warnings.InsertOrUpdate(aMsgName, true);
  AutoTArray<nsString, 1> params;
  nsString& uri = *params.AppendElement();
  (void)aDocument->GetDocumentURI(uri);
  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Media"_ns,
                                  aDocument, PropertiesFile::DOM_PROPERTIES,
                                  aMsgName, params);
}

Maybe<nsCString> GetOrigin(const dom::Document* aDocument) {
  if (!aDocument) {
    return Nothing();
  }
  nsCOMPtr<nsIScriptObjectPrincipal> sop =
      do_QueryInterface(aDocument->GetInnerWindow());
  if (!sop) {
    return Nothing();
  }
  auto* principal = sop->GetPrincipal();
  nsAutoCString origin;
  nsresult rv = principal->GetOrigin(origin);
  if (NS_FAILED(rv)) {
    return Nothing();
  }
  return Some(origin);
}

}  // namespace mozilla
