/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "VideoDecoderChild.h"
#include "VideoDecoderManagerChild.h"
#include "mozilla/layers/TextureClient.h"
#include "base/thread.h"
#include "MediaInfo.h"
#include "ImageContainer.h"
#include "GPUVideoImage.h"

namespace mozilla {
namespace dom {

using base::Thread;
using namespace ipc;
using namespace layers;
using namespace gfx;

VideoDecoderChild::VideoDecoderChild()
  : mThread(VideoDecoderManagerChild::GetManagerThread())
  , mCanSend(false)
  , mInitialized(false)
  , mIsHardwareAccelerated(false)
{
}

VideoDecoderChild::~VideoDecoderChild()
{
  AssertOnManagerThread();
  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
}

bool
VideoDecoderChild::RecvOutput(const VideoDataIPDL& aData)
{
  AssertOnManagerThread();
  VideoInfo info(aData.display().width, aData.display().height);

  // The Image here creates a TextureData object that takes ownership
  // of the SurfaceDescriptor, and is responsible for making sure that
  // it gets deallocated.
  RefPtr<Image> image = new GPUVideoImage(GetManager(), aData.sd(), aData.display());

  RefPtr<VideoData> video = VideoData::CreateFromImage(info,
                                                       aData.base().offset(),
                                                       aData.base().time(),
                                                       aData.base().duration(),
                                                       image,
                                                       aData.base().keyframe(),
                                                       aData.base().timecode(),
                                                       IntRect());
  if (mCallback) {
    mCallback->Output(video);
  }
  return true;
}

bool
VideoDecoderChild::RecvInputExhausted()
{
  AssertOnManagerThread();
  if (mCallback) {
    mCallback->InputExhausted();
  }
  return true;
}

bool
VideoDecoderChild::RecvDrainComplete()
{
  AssertOnManagerThread();
  if (mCallback) {
    mCallback->DrainComplete();
  }
  return true;
}

bool
VideoDecoderChild::RecvError(const nsresult& aError)
{
  AssertOnManagerThread();
  if (mCallback) {
    mCallback->Error(aError);
  }
  return true;
}

bool
VideoDecoderChild::RecvInitComplete(const bool& aHardware, const nsCString& aHardwareReason)
{
  AssertOnManagerThread();
  mInitPromise.Resolve(TrackInfo::kVideoTrack, __func__);
  mInitialized = true;
  mIsHardwareAccelerated = aHardware;
  mHardwareAcceleratedReason = aHardwareReason;
  return true;
}

bool
VideoDecoderChild::RecvInitFailed(const nsresult& aReason)
{
  AssertOnManagerThread();
  mInitPromise.Reject(aReason, __func__);
  return true;
}

void
VideoDecoderChild::ActorDestroy(ActorDestroyReason aWhy)
{
  if (aWhy == AbnormalShutdown) {
    // Defer reporting an error until we've recreated the manager so that
    // it'll be safe for MediaFormatReader to recreate decoders
    RefPtr<VideoDecoderChild> ref = this;
    GetManager()->RunWhenRecreated(NS_NewRunnableFunction([=]() {
      if (ref->mInitialized && ref->mCallback) {
        ref->mCallback->Error(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER);
      } else {
        ref->mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER, __func__);
      }
    }));
  }
  mCanSend = false;
}

void
VideoDecoderChild::InitIPDL(MediaDataDecoderCallback* aCallback,
                            const VideoInfo& aVideoInfo,
                            const layers::TextureFactoryIdentifier& aIdentifier)
{
  RefPtr<VideoDecoderManagerChild> manager = VideoDecoderManagerChild::GetSingleton();
  // If the manager isn't available, then don't initialize mIPDLSelfRef and leave
  // us in an error state. We'll then immediately reject the promise when Init()
  // is called and the caller can try again. Hopefully by then the new manager is
  // ready, or we've notified the caller of it being no longer available.
  // If not, then the cycle repeats until we're ready.
  if (!manager || !manager->CanSend()) {
    return;
  }

  mIPDLSelfRef = this;
  mCallback = aCallback;
  mVideoInfo = aVideoInfo;
  mIdentifier = aIdentifier;
  if (manager->SendPVideoDecoderConstructor(this)) {
    mCanSend = true;
  }
}

void
VideoDecoderChild::DestroyIPDL()
{
  if (mCanSend) {
    PVideoDecoderChild::Send__delete__(this);
  }
}

void
VideoDecoderChild::IPDLActorDestroyed()
{
  mIPDLSelfRef = nullptr;
}

// MediaDataDecoder methods

RefPtr<MediaDataDecoder::InitPromise>
VideoDecoderChild::Init()
{
  AssertOnManagerThread();

  if (!mIPDLSelfRef) {
    return MediaDataDecoder::InitPromise::CreateAndReject(
      NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__);
  }
  // If we failed to send this, then we'll still resolve the Init promise
  // as ActorDestroy handles it.
  if (mCanSend) {
    SendInit(mVideoInfo, mIdentifier);
  }
  return mInitPromise.Ensure(__func__);
}

void
VideoDecoderChild::Input(MediaRawData* aSample)
{
  AssertOnManagerThread();
  if (!mCanSend) {
    return;
  }

  // TODO: It would be nice to add an allocator method to
  // MediaDataDecoder so that the demuxer could write directly
  // into shmem rather than requiring a copy here.
  Shmem buffer;
  if (!AllocShmem(aSample->Size(), Shmem::SharedMemory::TYPE_BASIC, &buffer)) {
    mCallback->Error(NS_ERROR_DOM_MEDIA_DECODE_ERR);
    return;
  }

  memcpy(buffer.get<uint8_t>(), aSample->Data(), aSample->Size());

  MediaRawDataIPDL sample(MediaDataIPDL(aSample->mOffset,
                                        aSample->mTime,
                                        aSample->mTimecode,
                                        aSample->mDuration,
                                        aSample->mFrames,
                                        aSample->mKeyframe),
                          buffer);
  SendInput(sample);
}

void
VideoDecoderChild::Flush()
{
  AssertOnManagerThread();
  if (mCanSend) {
    SendFlush();
  }
}

void
VideoDecoderChild::Drain()
{
  AssertOnManagerThread();
  if (mCanSend) {
    SendDrain();
  }
}

void
VideoDecoderChild::Shutdown()
{
  AssertOnManagerThread();
  mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
  if (mCanSend) {
    SendShutdown();
  }
  mInitialized = false;
  mCallback = nullptr;
}

bool
VideoDecoderChild::IsHardwareAccelerated(nsACString& aFailureReason) const
{
  aFailureReason = mHardwareAcceleratedReason;
  return mIsHardwareAccelerated;
}

void
VideoDecoderChild::SetSeekThreshold(const media::TimeUnit& aTime)
{
  AssertOnManagerThread();
  if (mCanSend) {
    SendSetSeekThreshold(aTime.ToMicroseconds());
  }
}

void
VideoDecoderChild::AssertOnManagerThread()
{
  MOZ_ASSERT(NS_GetCurrentThread() == mThread);
}

VideoDecoderManagerChild*
VideoDecoderChild::GetManager()
{
  if (!mCanSend) {
    return nullptr;
  }
  return static_cast<VideoDecoderManagerChild*>(Manager());
}

} // namespace dom
} // namespace mozilla
