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

#include "RemoteCDMParent.h"
#include "RemoteMediaManagerParent.h"
#include "mozilla/EnumeratedRange.h"

namespace mozilla {

namespace {
template <typename T>
void RejectResolver(Maybe<T>& aResolver, const char* aWhere) {
  if (aResolver) {
    auto resolver = std::move(*aResolver);
    aResolver.reset();
    resolver(MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, aWhere));
  }
}
}  // namespace

RemoteDecoderParent::RemoteDecoderParent(
    RemoteMediaManagerParent* aParent,
    const CreateDecoderParams::OptionSet& aOptions,
    nsISerialEventTarget* aManagerThread, TaskQueue* aDecodeTaskQueue,
    const Maybe<uint64_t>& aMediaEngineId, Maybe<TrackingId> aTrackingId,
    RemoteCDMParent* aCDM)
    : ShmemRecycleAllocator(this),
      mParent(aParent),
      mOptions(aOptions),
      mDecodeTaskQueue(aDecodeTaskQueue),
      mCDM(aCDM),
      mTrackingId(aTrackingId),
      mMediaEngineId(aMediaEngineId),
      mManagerThread(aManagerThread) {
  MOZ_COUNT_CTOR(RemoteDecoderParent);
  MOZ_ASSERT(OnManagerThread());
}

RemoteDecoderParent::~RemoteDecoderParent() {
  MOZ_COUNT_DTOR(RemoteDecoderParent);
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvInit(
    InitResolver&& aResolver) {
  MOZ_ASSERT(OnManagerThread());
  if (!mDecoder) {
    aResolver(MediaResult(NS_ERROR_ABORT, __func__));
    return IPC_OK();
  }

  MOZ_DIAGNOSTIC_ASSERT(!mPendingInitResolver,
                        "overlapping Init in RemoteDecoderParent");
  mPendingInitResolver.emplace(std::move(aResolver));
  RefPtr<RemoteDecoderParent> self = this;
  mInitRequest.DisconnectIfExists();
  mDecoder->Init()
      ->Then(
          mManagerThread, __func__,
          [self](MediaDataDecoder::InitPromise::ResolveOrRejectValue&& aValue) {
            self->mInitRequest.Complete();
            if (!self->CanSend() || !self->mPendingInitResolver) {
              return;
            }
            auto resolver = std::move(*self->mPendingInitResolver);
            self->mPendingInitResolver.reset();
            if (aValue.IsReject()) {
              resolver(aValue.RejectValue());
              return;
            }
            auto track = aValue.ResolveValue();
            MOZ_ASSERT(track == TrackInfo::kAudioTrack ||
                       track == TrackInfo::kVideoTrack);
            if (self->mDecoder) {
              nsCString hardwareReason;
              bool hardwareAccelerated =
                  self->mDecoder->IsHardwareAccelerated(hardwareReason);
              nsTArray<DecodePropertyIPDL> properties;
              for (auto name : MakeInclusiveEnumeratedRange(
                       MediaDataDecoder::sHighestPropertyName)) {
                if (auto v = self->mDecoder->GetDecodeProperty(name)) {
                  properties.AppendElement(
                      DecodePropertyIPDL(name, std::move(v.ref())));
                }
              }
              resolver(InitCompletionIPDL{
                  track, self->mDecoder->GetDescriptionName(),
                  self->mDecoder->GetProcessName(),
                  self->mDecoder->GetCodecName(), hardwareAccelerated,
                  hardwareReason, self->mDecoder->NeedsConversion(),
                  self->mDecoder->ShouldDecoderAlwaysBeRecycled(), properties});
            }
          })
      ->Track(mInitRequest);
  return IPC_OK();
}

void RemoteDecoderParent::DecodeNextSample(
    const RefPtr<ArrayOfRemoteMediaRawData>& aData, size_t aIndex,
    MediaDataDecoder::DecodedData&& aOutput) {
  MOZ_ASSERT(OnManagerThread());

  if (!CanSend() || !mPendingDecodeResolver) {
    return;
  }

  if (!mDecoder) {
    auto resolver = std::move(*mPendingDecodeResolver);
    mPendingDecodeResolver.reset();
    resolver(MediaResult(NS_ERROR_ABORT, __func__));
    return;
  }

  if (aData->Count() == aIndex) {
    auto resolver = std::move(*mPendingDecodeResolver);
    mPendingDecodeResolver.reset();
    DecodedOutputIPDL result;
    MediaResult rv = ProcessDecodedData(std::move(aOutput), result);
    if (NS_FAILED(rv)) {
      resolver(std::move(rv));  // Out of Memory.
    } else {
      resolver(std::move(result));
    }
    return;
  }

  RefPtr<MediaRawData> rawData = aData->ElementAt(aIndex);
  if (!rawData) {
    auto resolver = std::move(*mPendingDecodeResolver);
    mPendingDecodeResolver.reset();
    resolver(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
    return;
  }

  mDecodeRequest.DisconnectIfExists();
  mDecoder->Decode(rawData)
      ->Then(mManagerThread, __func__,
             [self = RefPtr{this}, this, aData, aIndex,
              output = std::move(aOutput)](
                 MediaDataDecoder::DecodePromise::ResolveOrRejectValue&&
                     aValue) mutable {
               mDecodeRequest.Complete();
               if (aValue.IsReject()) {
                 if (mPendingDecodeResolver) {
                   auto resolver = std::move(*mPendingDecodeResolver);
                   mPendingDecodeResolver.reset();
                   resolver(aValue.RejectValue());
                 }
                 return;
               }

               output.AppendElements(std::move(aValue.ResolveValue()));

               // Call again in case we have more data to decode.
               DecodeNextSample(aData, aIndex + 1, std::move(output));
             })
      ->Track(mDecodeRequest);
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvDecode(
    ArrayOfRemoteMediaRawData* aData, DecodeResolver&& aResolver) {
  MOZ_ASSERT(OnManagerThread());
  // If we are here, we know all previously returned DecodedOutputIPDL got
  // used by the child. We can mark all previously sent ShmemBuffer as
  // available again.
  ReleaseAllBuffers();
  MOZ_DIAGNOSTIC_ASSERT(!mPendingDecodeResolver,
                        "overlapping Decode in RemoteDecoderParent");
  mPendingDecodeResolver.emplace(std::move(aResolver));
  MediaDataDecoder::DecodedData output;
  DecodeNextSample(aData, 0, std::move(output));

  return IPC_OK();
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvFlush(
    FlushResolver&& aResolver) {
  MOZ_ASSERT(OnManagerThread());
  if (!mDecoder) {
    aResolver(MediaResult(NS_ERROR_ABORT, __func__));
    return IPC_OK();
  }

  MOZ_DIAGNOSTIC_ASSERT(!mPendingFlushResolver,
                        "overlapping Flush in RemoteDecoderParent");
  mPendingFlushResolver.emplace(std::move(aResolver));
  RefPtr<RemoteDecoderParent> self = this;
  mFlushRequest.DisconnectIfExists();
  mDecoder->Flush()
      ->Then(
          mManagerThread, __func__,
          [self](
              MediaDataDecoder::FlushPromise::ResolveOrRejectValue&& aValue) {
            self->mFlushRequest.Complete();
            if (!self->mPendingFlushResolver) {
              return;
            }
            auto resolver = std::move(*self->mPendingFlushResolver);
            self->mPendingFlushResolver.reset();
            self->ReleaseAllBuffers();
            if (aValue.IsReject()) {
              resolver(aValue.RejectValue());
            } else {
              resolver(MediaResult(NS_OK));
            }
          })
      ->Track(mFlushRequest);

  return IPC_OK();
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvDrain(
    DrainResolver&& aResolver) {
  MOZ_ASSERT(OnManagerThread());
  if (!mDecoder) {
    aResolver(MediaResult(NS_ERROR_ABORT, __func__));
    return IPC_OK();
  }

  MOZ_DIAGNOSTIC_ASSERT(!mPendingDrainResolver,
                        "overlapping Drain in RemoteDecoderParent");
  mPendingDrainResolver.emplace(std::move(aResolver));
  RefPtr<RemoteDecoderParent> self = this;
  mDrainRequest.DisconnectIfExists();
  mDecoder->Drain()
      ->Then(
          mManagerThread, __func__,
          [self, this](
              MediaDataDecoder::DecodePromise::ResolveOrRejectValue&& aValue) {
            mDrainRequest.Complete();
            if (!mPendingDrainResolver) {
              return;
            }
            auto resolver = std::move(*mPendingDrainResolver);
            mPendingDrainResolver.reset();
            ReleaseAllBuffers();
            if (!self->CanSend()) {
              resolver(MediaResult(NS_ERROR_DOM_MEDIA_CANCELED, __func__));
              return;
            }
            if (aValue.IsReject()) {
              resolver(aValue.RejectValue());
              return;
            }
            DecodedOutputIPDL output;
            MediaResult rv =
                ProcessDecodedData(std::move(aValue.ResolveValue()), output);
            if (NS_FAILED(rv)) {
              resolver(rv);
            } else {
              resolver(std::move(output));
            }
          })
      ->Track(mDrainRequest);
  return IPC_OK();
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvShutdown(
    ShutdownResolver&& aResolver) {
  MOZ_ASSERT(OnManagerThread());
  AbortPendingRequests();
  if (mDecoder) {
    RefPtr<RemoteDecoderParent> self = this;
    mDecoder->Shutdown()->Then(
        mManagerThread, __func__,
        [self, resolver = std::move(aResolver)](
            const ShutdownPromise::ResolveOrRejectValue& aValue) {
          MOZ_ASSERT(aValue.IsResolve());
          self->ReleaseAllBuffers();
          resolver(true);
        });
  } else {
    aResolver(true);
  }
  mDecoder = nullptr;
  mShutdown = true;
  return IPC_OK();
}

mozilla::ipc::IPCResult RemoteDecoderParent::RecvSetSeekThreshold(
    const TimeUnit& aTime) {
  MOZ_ASSERT(OnManagerThread());
  if (mDecoder) {
    mDecoder->SetSeekThreshold(aTime);
  }
  return IPC_OK();
}

void RemoteDecoderParent::ActorDestroy(ActorDestroyReason aWhy) {
  MOZ_ASSERT(OnManagerThread());
  AbortPendingRequests();
  if (mDecoder) {
    mDecoder->Shutdown();
    mDecoder = nullptr;
  }
  CleanupShmemRecycleAllocator();
}

void RemoteDecoderParent::AbortPendingRequests() {
  MOZ_ASSERT(OnManagerThread());
  RejectResolver(mPendingInitResolver, __func__);
  RejectResolver(mPendingDecodeResolver, __func__);
  RejectResolver(mPendingFlushResolver, __func__);
  RejectResolver(mPendingDrainResolver, __func__);
  mInitRequest.DisconnectIfExists();
  mDecodeRequest.DisconnectIfExists();
  mFlushRequest.DisconnectIfExists();
  mDrainRequest.DisconnectIfExists();
}

bool RemoteDecoderParent::OnManagerThread() {
  return RemoteMediaManagerParent::OnManagerThread();
}

}  // namespace mozilla
