/* 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_PLATFORM_WMF_CLEARKEY_WMFCLEARKEYCDMUTILS_H
#define DOM_MEDIA_PLATFORM_WMF_CLEARKEY_WMFCLEARKEYCDMUTILS_H

#include <initguid.h>
#include <mfidl.h>
#include <mutex>
#include <string>
#include <thread>
#include <windows.h>
#include <wrl.h>
#include <stdio.h>

#include "MFCDMExtra.h"
#include "mozilla/Assertions.h"
#include "mozilla/EndianUtils.h"

namespace mozilla {

inline constexpr WCHAR kCLEARKEY_SYSTEM_NAME[] = L"org.w3.clearkey";

#define WMF_CLEARKEY_DEBUG 0

#if WMF_CLEARKEY_DEBUG
#  define LOG(msg, ...)                                                   \
    printf(("[Thread %lu]: D/WMFClearKey " msg "\n"),                     \
           static_cast<unsigned long>(                                    \
               std::hash<std::thread::id>{}(std::this_thread::get_id())), \
           ##__VA_ARGS__)
#else
#  define LOG(msg, ...)
#endif

// Returns a trimmed pretty-function name as a std::string. Callers must
// bind the result to a named local before calling .c_str() on it, so the
// backing buffer outlives the pointer handed to printf.
inline std::string GetPrettyFunctionName(const char* aPrettyFunc) {
  std::string prettyFunction(aPrettyFunc);
  std::size_t pos1 = prettyFunction.find("::");
  std::size_t pos2 = prettyFunction.find("(", pos1);
  return prettyFunction.substr(0, pos2);
}

// We can't reuse the definition in the `MediaEngineUtils.h` due to the
// restriction of not being able to use XPCOM string in the external library.
#ifndef RETURN_IF_FAILED
#  define RETURN_IF_FAILED(x)               \
    do {                                    \
      HRESULT rv = x;                       \
      if (FAILED(rv)) {                     \
        LOG("(" #x ") failed, rv=%lx", rv); \
        return rv;                          \
      }                                     \
    } while (false)
#endif

#if WMF_CLEARKEY_DEBUG
#  define NOT_IMPLEMENTED()                                       \
    do {                                                          \
      const std::string prettyFunc =                              \
          mozilla::GetPrettyFunctionName(__PRETTY_FUNCTION__);    \
      LOG("WARNING : '%s' NOT IMPLEMENTED!", prettyFunc.c_str()); \
    } while (0)
#  define ENTRY_LOG(...)                                       \
    do {                                                       \
      const std::string prettyFunc =                           \
          mozilla::GetPrettyFunctionName(__PRETTY_FUNCTION__); \
      LOG("%s [%p]", prettyFunc.c_str(), this);                \
    } while (0)
#  define ENTRY_LOG_ARGS(fmt, ...)                                   \
    do {                                                             \
      const std::string prettyFunc =                                 \
          mozilla::GetPrettyFunctionName(__PRETTY_FUNCTION__);       \
      LOG("%s [%p]: " fmt, prettyFunc.c_str(), this, ##__VA_ARGS__); \
    } while (0)
#else
#  define NOT_IMPLEMENTED() ((void)0)
#  define ENTRY_LOG(...) ((void)0)
#  define ENTRY_LOG_ARGS(fmt, ...) \
    do {                           \
      (void)fmt;                   \
      (void)(0, ##__VA_ARGS__);    \
    } while (0)
#endif

// Convert a GUID (little-endian GUID byte order) to a 16-byte big-endian key
// ID. In Media Foundation, key IDs are GUIDs but the actual key ID bytes are
// big-endian. Data1/2/3 are stored little-endian in a GUID, so we swap them.
inline void GuidToKeyId(const GUID& aGuid, uint8_t aKeyId[16]) {
  GUID swapped = aGuid;
  swapped.Data1 = NativeEndian::swapToBigEndian(aGuid.Data1);
  swapped.Data2 = NativeEndian::swapToBigEndian(aGuid.Data2);
  swapped.Data3 = NativeEndian::swapToBigEndian(aGuid.Data3);
  // Data4 is already a byte array (big-endian), no swap needed.
  memcpy(aKeyId, &swapped, 16);
}

// TODO : should we use Microsoft's definition or Chromium's defintion?

// This is defined in Microsoft's sample code
// https://github.com/microsoft/media-foundation/blob/dc81175a3e893c7c58fcbf1a943ac342e39f172c/samples/storecdm/clearkeyStoreCDM/clearkeydll/guids.h#L17-L18C14
// DEFINE_GUID(CLEARKEY_GUID_CLEARKEY_PROTECTION_SYSTEM_ID, 0x9b0ff3ca, 0x1378,
//             0x4f28, 0x96, 0x65, 0x18, 0x9e, 0x15, 0x30, 0x2a, 0x71);

// Media Foundation Clear Key protection system ID from Chromium
// {E4E94971-696A-447E-96E4-93FDF3A57A7A}
// https://source.chromium.org/chromium/chromium/src/+/main:media/cdm/win/test/media_foundation_clear_key_guids.h;l=16-29?q=CLEARKEY_GUID_CLEARKEY_PROTECTION_SYSTEM_ID&ss=chromium%2Fchromium%2Fsrc
DEFINE_GUID(CLEARKEY_GUID_CLEARKEY_PROTECTION_SYSTEM_ID, 0xe4e94971, 0x696a,
            0x447e, 0x96, 0xe4, 0x93, 0xfd, 0xf3, 0xa5, 0x7a, 0x7a);

// Media Foundation Clear Key content enabler type
// {C262FD73-2F13-41C2-94E7-4CAF087AE1D1}
DEFINE_GUID(MEDIA_FOUNDATION_CLEARKEY_GUID_CONTENT_ENABLER_TYPE, 0xc262fd73,
            0x2f13, 0x41c2, 0x94, 0xe7, 0x4c, 0xaf, 0x8, 0x7a, 0xe1, 0xd1);

// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:media/cdm/win/test/media_foundation_clear_key_guids.h;l=46;drc=fcefb4563e8ea5b2036ecde882f4f228dd1e4338
#define PLAYREADY_GUID_MEDIA_PROTECTION_SYSTEM_ID_STRING \
  L"{F4637010-03C3-42CD-B932-B48ADF3A6A54}"

// A PROPVARIANT that is automatically initialized and cleared upon respective
// construction and destruction of this class.
class AutoPropVar final {
 public:
  AutoPropVar() { PropVariantInit(&mVar); }

  ~AutoPropVar() { Reset(); }

  AutoPropVar(const AutoPropVar&) = delete;
  AutoPropVar& operator=(const AutoPropVar&) = delete;
  bool operator==(const AutoPropVar&) const = delete;
  bool operator!=(const AutoPropVar&) const = delete;

  // Returns a pointer to the underlying PROPVARIANT for use as an out param
  // in a function call.
  PROPVARIANT* Receive() {
    MOZ_ASSERT(mVar.vt == VT_EMPTY);
    return &mVar;
  }

  // Clears the instance to prepare it for re-use (e.g., via Receive).
  void Reset() {
    if (mVar.vt != VT_EMPTY) {
      HRESULT hr = PropVariantClear(&mVar);
      MOZ_ASSERT(SUCCEEDED(hr));
      (void)hr;
    }
  }

  const PROPVARIANT& get() const { return mVar; }
  const PROPVARIANT* ptr() const { return &mVar; }

 private:
  PROPVARIANT mVar;
};

template <typename T>
class ScopedCoMem final {
 public:
  ScopedCoMem() : mPtr(nullptr) {}

  ~ScopedCoMem() { Reset(nullptr); }

  ScopedCoMem(const ScopedCoMem&) = delete;
  ScopedCoMem& operator=(const ScopedCoMem&) = delete;

  T** operator&() {               // NOLINT
    MOZ_ASSERT(mPtr == nullptr);  // To catch memory leaks.
    return &mPtr;
  }

  operator T*() { return mPtr; }

  T* operator->() {
    MOZ_ASSERT(mPtr != nullptr);
    return mPtr;
  }

  const T* operator->() const {
    MOZ_ASSERT(mPtr != nullptr);
    return mPtr;
  }

  explicit operator bool() const { return mPtr; }

  friend bool operator==(const ScopedCoMem& lhs, std::nullptr_t) {
    return lhs.Get() == nullptr;
  }

  friend bool operator==(std::nullptr_t, const ScopedCoMem& rhs) {
    return rhs.Get() == nullptr;
  }

  friend bool operator!=(const ScopedCoMem& lhs, std::nullptr_t) {
    return lhs.Get() != nullptr;
  }

  friend bool operator!=(std::nullptr_t, const ScopedCoMem& rhs) {
    return rhs.Get() != nullptr;
  }

  void Reset(T* ptr) {
    if (mPtr) {
      CoTaskMemFree(mPtr);
    }
    mPtr = ptr;
  }

  T* Get() const { return mPtr; }

 private:
  T* mPtr;
};

template <typename T>
class DataMutex final {
 public:
  DataMutex() = default;

  template <typename... Args>
  explicit DataMutex(Args&&... args) : mData(std::forward<Args>(args)...) {}

  class AutoLock {
   public:
    AutoLock(std::unique_lock<std::mutex>&& lock, DataMutex<T>* mutex)
        : mLock(std::move(lock)), mMutex(mutex) {}
    T& operator*() { return ref(); }
    T* operator->() { return &ref(); }
    T& ref() const& { return mMutex->mData; }
    operator T*() const& { return &ref(); }
    ~AutoLock() { mMutex = nullptr; }

   private:
    std::unique_lock<std::mutex> mLock;
    DataMutex<T>* mMutex;
  };

  AutoLock Lock() {
    return AutoLock(std::unique_lock<std::mutex>(mMutex), this);
  }

 private:
  std::mutex mMutex;
  T mData;
};

}  // namespace mozilla

#endif  // DOM_MEDIA_PLATFORM_WMF_CLEARKEY_WMFCLEARKEYCDMUTILS_H
