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

#include "MFTDecoder.h"
#include "VideoUtils.h"
#include "mozilla/MFMediaEngineParent.h"
#include "mozilla/MFMediaEngineUtils.h"
#include "mozilla/RemoteDecoderModule.h"
#include "mozilla/RemoteMediaManagerChild.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/mscom/EnsureMTA.h"

namespace mozilla {

#define LOG(msg, ...) \
  MOZ_LOG_FMT(gMFMediaEngineLog, LogLevel::Debug, msg, ##__VA_ARGS__)

/* static */
void MFMediaEngineDecoderModule::Init() {
  // TODO : Init any thing that media engine would need. Implement this when we
  // start implementing media engine in following patches.
}

/* static */
already_AddRefed<PlatformDecoderModule> MFMediaEngineDecoderModule::Create() {
  RefPtr<MFMediaEngineDecoderModule> module = new MFMediaEngineDecoderModule();
  return module.forget();
}

/* static */
bool MFMediaEngineDecoderModule::SupportsConfig(const TrackInfo& aConfig) {
  RefPtr<PlatformDecoderModule> module = RemoteDecoderModule::Create(
      RemoteMediaIn::UtilityProcess_MFMediaEngineCDM);
  return !module->Supports(SupportDecoderParams(aConfig), nullptr).isEmpty();
}

already_AddRefed<MediaDataDecoder>
MFMediaEngineDecoderModule::CreateVideoDecoder(
    const CreateDecoderParams& aParams) {
  if (!aParams.mMediaEngineId ||
      !StaticPrefs::media_wmf_media_engine_enabled()) {
    return nullptr;
  }
  RefPtr<MFMediaEngineParent> mediaEngine =
      MFMediaEngineParent::GetMediaEngineById(*aParams.mMediaEngineId);
  if (!mediaEngine) {
    LOG("Can't find media engine {} for video decoder",
        *aParams.mMediaEngineId);
    return nullptr;
  }
  LOG("MFMediaEngineDecoderModule, CreateVideoDecoder");
  RefPtr<MediaDataDecoder> decoder = mediaEngine->GetMediaEngineStream(
      TrackInfo::TrackType::kVideoTrack, aParams);
  return decoder.forget();
}

already_AddRefed<MediaDataDecoder>
MFMediaEngineDecoderModule::CreateAudioDecoder(
    const CreateDecoderParams& aParams) {
  if (!aParams.mMediaEngineId ||
      !StaticPrefs::media_wmf_media_engine_enabled()) {
    return nullptr;
  }
  RefPtr<MFMediaEngineParent> mediaEngine =
      MFMediaEngineParent::GetMediaEngineById(*aParams.mMediaEngineId);
  if (!mediaEngine) {
    LOG("Can't find media engine {} for audio decoder",
        *aParams.mMediaEngineId);
    return nullptr;
  }
  LOG("MFMediaEngineDecoderModule, CreateAudioDecoder");
  RefPtr<MediaDataDecoder> decoder = mediaEngine->GetMediaEngineStream(
      TrackInfo::TrackType::kAudioTrack, aParams);
  return decoder.forget();
}

media::DecodeSupportSet MFMediaEngineDecoderModule::SupportsMimeType(
    const nsACString& aMimeType, DecoderDoctorDiagnostics* aDiagnostics) const {
  UniquePtr<TrackInfo> trackInfo = CreateTrackInfoWithMIMEType(aMimeType);
  if (!trackInfo) {
    return media::DecodeSupportSet{};
  }
  return SupportInternal(SupportDecoderParams(*trackInfo), aDiagnostics);
}

media::DecodeSupportSet MFMediaEngineDecoderModule::Supports(
    const SupportDecoderParams& aParams,
    DecoderDoctorDiagnostics* aDiagnostics) const {
  return SupportInternal(aParams, aDiagnostics);
}

media::DecodeSupportSet MFMediaEngineDecoderModule::SupportInternal(
    const SupportDecoderParams& aParams,
    DecoderDoctorDiagnostics* aDiagnostics) const {
  if (!StaticPrefs::media_wmf_media_engine_enabled()) {
    return media::DecodeSupportSet{};
  }
  // Only support hardware decoding.
  if (!gfx::gfxVars::CanUseHardwareVideoDecoding() &&
      !StaticPrefs::media_wmf_media_engine_bypass_gfx_blocklist()) {
    return media::DecodeSupportSet{};
  }
  bool supports = false;
  WMFStreamType type = GetStreamTypeFromMimeType(aParams.MimeType());
  if (type != WMFStreamType::Unknown) {
    supports = CanCreateMFTDecoder(type);
  }
  MOZ_LOG_FMT(sPDMLog, LogLevel::Debug,
              "MFMediaEngine decoder {} requested type '{}'",
              supports ? "supports" : "rejects", aParams.MimeType().get());
  if (!supports) {
    return media::DecodeSupportSet{};
  }
  return StreamTypeIsVideo(type) ? media::DecodeSupport::HardwareDecode
                                 : media::DecodeSupport::SoftwareDecode;
}

static bool CreateMFTDecoderOnMTA(const WMFStreamType& aType) {
  RefPtr<MFTDecoder> decoder = new MFTDecoder();
  static std::unordered_map<WMFStreamType, bool> sResults;
  if (auto rv = sResults.find(aType); rv != sResults.end()) {
    return rv->second;
  }

  bool result = false;
  switch (aType) {
    case WMFStreamType::MP3:
      result = SUCCEEDED(decoder->Create(CLSID_CMP3DecMediaObject));
      break;
    case WMFStreamType::AAC:
      result = SUCCEEDED(decoder->Create(CLSID_CMSAACDecMFT));
      break;
    // Opus and vorbis are supported via extension.
    // https://www.microsoft.com/en-us/p/web-media-extensions/9n5tdp8vcmhs
    case WMFStreamType::OPUS:
      result = SUCCEEDED(decoder->Create(CLSID_MSOpusDecoder));
      break;
    case WMFStreamType::VORBIS:
      result = SUCCEEDED(decoder->Create(
          MFT_CATEGORY_AUDIO_DECODER, MFAudioFormat_Vorbis, MFAudioFormat_PCM));
      break;
    case WMFStreamType::H264:
      result = SUCCEEDED(decoder->Create(CLSID_CMSH264DecoderMFT));
      break;
    case WMFStreamType::VP8:
    case WMFStreamType::VP9: {
      static const uint32_t VPX_USABLE_BUILD = 16287;
      if (IsWindows10BuildOrLater(VPX_USABLE_BUILD)) {
        result = SUCCEEDED(decoder->Create(CLSID_CMSVPXDecMFT));
      }
      break;
    }
    case WMFStreamType::AV1:
      result = SUCCEEDED(decoder->Create(MFT_CATEGORY_VIDEO_DECODER,
                                         MFVideoFormat_AV1, GUID_NULL));
      break;
    case WMFStreamType::HEVC:
      if (StaticPrefs::media_hevc_enabled()) {
        result =
            SUCCEEDED(decoder->Create(MFT_CATEGORY_VIDEO_DECODER,
                                      MFVideoFormat_HEVC, MFVideoFormat_NV12));
      }
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected type");
  }
  sResults.insert({aType, result});
  return result;
}

bool MFMediaEngineDecoderModule::CanCreateMFTDecoder(
    const WMFStreamType& aType) const {
  // TODO : caching the result to prevent performing on MTA thread everytime.
  bool canCreateDecoder = false;
  mozilla::mscom::EnsureMTA(
      [&]() { canCreateDecoder = CreateMFTDecoderOnMTA(aType); });
  return canCreateDecoder;
}

#undef LOG

}  // namespace mozilla
