/* 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 "ChromiumCDMCallbackProxy.h"

#include <type_traits>

#include "ChromiumCDMProxy.h"
#include "content_decryption_module.h"

namespace mozilla {

template <class Func, class... Args>
void ChromiumCDMCallbackProxy::DispatchToMainThread(const char* const aLabel,
                                                    Func aFunc,
                                                    Args&&... aArgs) {
  mMainThread->Dispatch(
      // Use decay to ensure all the types are passed by value not by reference.
      NewRunnableMethod<std::decay_t<Args>...>(aLabel, mProxy, aFunc,
                                               std::forward<Args>(aArgs)...),
      NS_DISPATCH_NORMAL);
}

void ChromiumCDMCallbackProxy::SetSessionId(uint32_t aPromiseId,
                                            const nsCString& aSessionId) {
  DispatchToMainThread("ChromiumCDMProxy::OnSetSessionId",
                       &ChromiumCDMProxy::OnSetSessionId, aPromiseId,
                       NS_ConvertUTF8toUTF16(aSessionId));
}

void ChromiumCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
                                                         bool aSuccessful) {
  DispatchToMainThread("ChromiumCDMProxy::OnResolveLoadSessionPromise",
                       &ChromiumCDMProxy::OnResolveLoadSessionPromise,
                       aPromiseId, aSuccessful);
}

void ChromiumCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId) {
  DispatchToMainThread("ChromiumCDMProxy::ResolvePromise",
                       &ChromiumCDMProxy::ResolvePromise, aPromiseId);
}

void ChromiumCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
                                             ErrorResult&& aException,
                                             const nsCString& aErrorMessage) {
  // Use CopyableErrorResult to store our exception in the runnable,
  // because ErrorResult is not OK to move across threads.
  DispatchToMainThread<decltype(&ChromiumCDMProxy::RejectPromiseOnMainThread),
                       int32_t, StoreCopyPassByRRef<CopyableErrorResult>,
                       const nsCString&>(
      "ChromiumCDMProxy::RejectPromise",
      &ChromiumCDMProxy::RejectPromiseOnMainThread, aPromiseId,
      std::move(aException), aErrorMessage);
}

static dom::MediaKeyMessageType ToDOMMessageType(
    cdm::MessageType aMessageType) {
  switch (aMessageType) {
    case cdm::kLicenseRequest:
      return dom::MediaKeyMessageType::License_request;
    case cdm::kLicenseRenewal:
      return dom::MediaKeyMessageType::License_renewal;
    case cdm::kLicenseRelease:
      return dom::MediaKeyMessageType::License_release;
    case cdm::kIndividualizationRequest:
      return dom::MediaKeyMessageType::Individualization_request;
  }
  MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
  return dom::MediaKeyMessageType::License_request;
}

void ChromiumCDMCallbackProxy::SessionMessage(const nsACString& aSessionId,
                                              cdm::MessageType aMessageType,
                                              nsTArray<uint8_t>&& aMessage) {
  DispatchToMainThread("ChromiumCDMProxy::OnSessionMessage",
                       &ChromiumCDMProxy::OnSessionMessage,
                       NS_ConvertUTF8toUTF16(aSessionId),
                       ToDOMMessageType(aMessageType), std::move(aMessage));
}

static dom::MediaKeyStatus ToDOMMediaKeyStatus(cdm::KeyStatus aStatus) {
  switch (aStatus) {
    case cdm::kUsable:
      return dom::MediaKeyStatus::Usable;
    case cdm::kInternalError:
      return dom::MediaKeyStatus::Internal_error;
    case cdm::kExpired:
      return dom::MediaKeyStatus::Expired;
    case cdm::kOutputRestricted:
      return dom::MediaKeyStatus::Output_restricted;
    case cdm::kOutputDownscaled:
      return dom::MediaKeyStatus::Output_downscaled;
    case cdm::kStatusPending:
      return dom::MediaKeyStatus::Status_pending;
    case cdm::kReleased:
      return dom::MediaKeyStatus::Released;
  }
  MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
  return dom::MediaKeyStatus::Internal_error;
}

void ChromiumCDMCallbackProxy::ResolvePromiseWithKeyStatus(
    uint32_t aPromiseId, cdm::KeyStatus aKeyStatus) {
  DispatchToMainThread("ChromiumCDMProxy::OnResolvePromiseWithKeyStatus",
                       &ChromiumCDMProxy::OnResolvePromiseWithKeyStatus,
                       aPromiseId, ToDOMMediaKeyStatus(aKeyStatus));
}

void ChromiumCDMCallbackProxy::SessionKeysChange(
    const nsCString& aSessionId,
    nsTArray<mozilla::gmp::CDMKeyInformation>&& aKeysInfo) {
  bool keyStatusesChange = false;
  {
    auto caps = mProxy->Capabilites().Lock();
    for (const auto& keyInfo : aKeysInfo) {
      keyStatusesChange |= caps->SetKeyStatus(
          keyInfo.mKeyId(), NS_ConvertUTF8toUTF16(aSessionId),
          dom::Optional<dom::MediaKeyStatus>(
              ToDOMMediaKeyStatus(keyInfo.mStatus())));
    }
  }
  if (keyStatusesChange) {
    DispatchToMainThread("ChromiumCDMProxy::OnKeyStatusesChange",
                         &ChromiumCDMProxy::OnKeyStatusesChange,
                         NS_ConvertUTF8toUTF16(aSessionId));
  }
}

void ChromiumCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
                                                double aSecondsSinceEpoch) {
  DispatchToMainThread("ChromiumCDMProxy::OnExpirationChange",
                       &ChromiumCDMProxy::OnExpirationChange,
                       NS_ConvertUTF8toUTF16(aSessionId),
                       UnixTime(aSecondsSinceEpoch * 1000));
}

void ChromiumCDMCallbackProxy::SessionClosed(const nsCString& aSessionId) {
  DispatchToMainThread("ChromiumCDMProxy::OnSessionClosed",
                       &ChromiumCDMProxy::OnSessionClosed,
                       NS_ConvertUTF8toUTF16(aSessionId),
                       dom::MediaKeySessionClosedReason::Closed_by_application);
}

void ChromiumCDMCallbackProxy::QueryOutputProtectionStatus() {
  DispatchToMainThread("ChromiumCDMProxy::QueryOutputProtectionStatus",
                       &ChromiumCDMProxy::QueryOutputProtectionStatus);
}

void ChromiumCDMCallbackProxy::Terminated() {
  DispatchToMainThread("ChromiumCDMProxy::Terminated",
                       &ChromiumCDMProxy::Terminated);
}

void ChromiumCDMCallbackProxy::Shutdown() {
  DispatchToMainThread("ChromiumCDMProxy::Shutdown",
                       &ChromiumCDMProxy::Shutdown);
}

}  // namespace mozilla
