/* 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 "mozilla/image/ImageUtils.h"
#include "DecodePool.h"
#include "Decoder.h"
#include "DecoderFactory.h"
#include "IDecodingTask.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Logging.h"
#include "nsNetUtil.h"
#include "nsStreamUtils.h"

namespace mozilla::image {

static LazyLogModule sLog("ImageUtils");

AnonymousDecoder::AnonymousDecoder() = default;

AnonymousDecoder::~AnonymousDecoder() = default;

class AnonymousDecoderTask : public IDecodingTask {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecoderTask, final)

  AnonymousDecoderTask(RefPtr<Decoder>&& aDecoder,
                       ThreadSafeWeakPtr<AnonymousDecoder>&& aOwner)
      : mOwner(std::move(aOwner)),
        mDecoderMutex("AnonymousDecoderTask::mDecoderMutex"),
        mDecoder(std::move(aDecoder)) {}

  bool ShouldPreferSyncRun() const final { return false; }

  TaskPriority Priority() const final { return TaskPriority::eLow; }

  bool IsValid() const {
    return !AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal) &&
           !mOwner.IsDead();
  }

  bool MaybeStart() {
    if (!IsValid()) {
      return false;
    }

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderTask::Start -- queue", this));
    DecodePool::Singleton()->AsyncRun(this);
    return true;
  }

  void Resume() final {
    if (!IsValid()) {
      return;
    }

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderTask::Resume -- queue", this));
    DecodePool::Singleton()->AsyncRun(this);
  }

  void Run() final {
    bool resume = true;
    while (!mOwner.IsDead() && resume) {
      LexerResult result(TerminalState::FAILURE);
      RefPtr<imgFrame> frame;
      {
        MutexAutoLock lock(mDecoderMutex);
        PrepareDecoder();
        result = mDecoder->Decode(WrapNotNull(this));
        if (result == LexerResult(Yield::NEED_MORE_DATA)) {
          MOZ_LOG(sLog, LogLevel::Debug,
                  ("[%p] AnonymousDecoderTask::Run -- need more data", this));
          OnNeedMoreData();
          return;
        }
        frame = mDecoder->GetCurrentFrame();
      }

      // Check if we have a new frame to process.
      if (frame) {
        RefPtr<gfx::SourceSurface> surface = frame->GetSourceSurface();
        if (surface) {
          MOZ_LOG(sLog, LogLevel::Debug,
                  ("[%p] AnonymousDecoderTask::Run -- new frame %p", this,
                   frame.get()));
          resume = OnFrameAvailable(std::move(frame), std::move(surface));
        } else {
          MOZ_ASSERT_UNREACHABLE("No surface from frame?");
        }
      }

      if (result.is<TerminalState>()) {
        MOZ_LOG(sLog, LogLevel::Debug,
                ("[%p] AnonymousDecoderTask::Run -- complete", this));
        {
          MutexAutoLock lock(mDecoderMutex);
          OnComplete(result == LexerResult(TerminalState::SUCCESS));
        }
        break;
      }

      MOZ_ASSERT(result == LexerResult(Yield::OUTPUT_AVAILABLE));
    }
  }

 protected:
  virtual ~AnonymousDecoderTask() = default;

  virtual void PrepareDecoder() MOZ_REQUIRES(mDecoderMutex) {}

  virtual void OnNeedMoreData() MOZ_REQUIRES(mDecoderMutex) {}

  // Returns true if the caller should continue decoding more frames if
  // possible.
  virtual bool OnFrameAvailable(RefPtr<imgFrame>&& aFrame,
                                RefPtr<gfx::SourceSurface>&& aSurface) {
    MOZ_ASSERT_UNREACHABLE("Unhandled frame!");
    return true;
  }

  virtual void OnComplete(bool aSuccess) MOZ_REQUIRES(mDecoderMutex) = 0;

  ThreadSafeWeakPtr<AnonymousDecoder> mOwner;
  Mutex mDecoderMutex;
  RefPtr<Decoder> mDecoder MOZ_GUARDED_BY(mDecoderMutex);
};

class AnonymousMetadataDecoderTask final : public AnonymousDecoderTask {
 public:
  AnonymousMetadataDecoderTask(RefPtr<Decoder>&& aDecoder,
                               ThreadSafeWeakPtr<AnonymousDecoder>&& aOwner)
      : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {}

 protected:
  void OnComplete(bool aSuccess) override MOZ_REQUIRES(mDecoderMutex) {
    RefPtr<AnonymousDecoder> owner(mOwner);
    if (!owner) {
      return;
    }

    if (!aSuccess) {
      owner->OnMetadata(nullptr);
      return;
    }

    const auto& mdIn = mDecoder->GetImageMetadata();
    owner->OnMetadata(&mdIn);
  }
};

class AnonymousFrameCountDecoderTask final : public AnonymousDecoderTask {
 public:
  AnonymousFrameCountDecoderTask(RefPtr<Decoder>&& aDecoder,
                                 ThreadSafeWeakPtr<AnonymousDecoder>&& aOwner)
      : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {}

 protected:
  void UpdateFrameCount(bool aComplete) MOZ_REQUIRES(mDecoderMutex) {
    RefPtr<AnonymousDecoder> owner(mOwner);
    if (!owner) {
      return;
    }

    const auto& mdIn = mDecoder->GetImageMetadata();
    uint32_t frameCount = mdIn.HasFrameCount() ? mdIn.GetFrameCount() : 0;
    owner->OnFrameCount(frameCount, aComplete);
  }

  void OnNeedMoreData() override MOZ_REQUIRES(mDecoderMutex) {
    UpdateFrameCount(/* aComplete */ false);
  }

  void OnComplete(bool aSuccess) override MOZ_REQUIRES(mDecoderMutex) {
    UpdateFrameCount(/* aComplete */ true);
  }
};

class AnonymousFramesDecoderTask final : public AnonymousDecoderTask {
 public:
  AnonymousFramesDecoderTask(RefPtr<Decoder>&& aDecoder,
                             ThreadSafeWeakPtr<AnonymousDecoder>&& aOwner)
      : AnonymousDecoderTask(std::move(aDecoder), std::move(aOwner)) {}

  void SetOutputSize(const OrientedIntSize& aSize) {
    mPendingOutputSize = Some(aSize);
  }

 protected:
  void PrepareDecoder() override MOZ_REQUIRES(mDecoderMutex) {
    if (mPendingOutputSize) {
      mDecoder->SetOutputSize(*mPendingOutputSize);
      mPendingOutputSize = Nothing();
    }
  }

  bool OnFrameAvailable(RefPtr<imgFrame>&& aFrame,
                        RefPtr<gfx::SourceSurface>&& aSurface) override {
    RefPtr<AnonymousDecoder> owner(mOwner);
    if (!owner) {
      return false;
    }

    return owner->OnFrameAvailable(std::move(aFrame), std::move(aSurface));
  }

  void OnComplete(bool aSuccess) override MOZ_REQUIRES(mDecoderMutex) {
    RefPtr<AnonymousDecoder> owner(mOwner);
    if (!owner) {
      return;
    }

    owner->OnFramesComplete();
  }

 private:
  Maybe<OrientedIntSize> mPendingOutputSize;
};

class AnonymousDecoderImpl final : public AnonymousDecoder {
 public:
  explicit AnonymousDecoderImpl(const Maybe<gfx::IntSize>& aOutputSize)
      : mMutex("mozilla::image::AnonymousDecoderImpl::mMutex"),
        mOutputSize(aOutputSize) {}

  ~AnonymousDecoderImpl() override { Destroy(); }

#ifdef MOZ_REFCOUNTED_LEAK_CHECKING
  const char* typeName() const override {
    return "mozilla::image::AnonymousDecoderImpl";
  }

  size_t typeSize() const override { return sizeof(*this); }
#endif

  bool Initialize(RefPtr<Decoder>&& aDecoder) override {
    MutexAutoLock lock(mMutex);

    if (NS_WARN_IF(!aDecoder)) {
      MOZ_LOG(sLog, LogLevel::Error,
              ("[%p] AnonymousDecoderImpl::Initialize -- bad decoder", this));
      return false;
    }

    RefPtr<Decoder> metadataDecoder =
        DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder);
    if (NS_WARN_IF(!metadataDecoder)) {
      MOZ_LOG(sLog, LogLevel::Error,
              ("[%p] AnonymousDecoderImpl::Initialize -- failed clone metadata "
               "decoder",
               this));
      return false;
    }

    DecoderFlags flags =
        aDecoder->GetDecoderFlags() | DecoderFlags::COUNT_FRAMES;
    RefPtr<Decoder> frameCountDecoder =
        DecoderFactory::CloneAnonymousMetadataDecoder(aDecoder, Some(flags));
    if (NS_WARN_IF(!frameCountDecoder)) {
      MOZ_LOG(sLog, LogLevel::Error,
              ("[%p] AnonymousDecoderImpl::Initialize -- failed clone frame "
               "count decoder",
               this));
      return false;
    }

    mMetadataTask = new AnonymousMetadataDecoderTask(
        std::move(metadataDecoder), ThreadSafeWeakPtr<AnonymousDecoder>(this));
    mFrameCountTask = new AnonymousFrameCountDecoderTask(
        std::move(frameCountDecoder),
        ThreadSafeWeakPtr<AnonymousDecoder>(this));
    mFramesTask = new AnonymousFramesDecoderTask(
        std::move(aDecoder), ThreadSafeWeakPtr<AnonymousDecoder>(this));

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::Initialize -- success", this));
    return true;
  }

  void Destroy() override {
    MutexAutoLock lock(mMutex);
    DestroyLocked(NS_ERROR_ABORT);
  }

  void DestroyLocked(nsresult aResult) MOZ_REQUIRES(mMutex) {
    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::Destroy", this));

    mFramesToDecode = 0;
    mMetadataTask = nullptr;
    mFrameCountTask = nullptr;
    mFramesTask = nullptr;
    mPendingFramesResult.mFrames.Clear();
    mPendingFramesResult.mFinished = true;
    mMetadataPromise.RejectIfExists(aResult, __func__);
    mFrameCountPromise.RejectIfExists(aResult, __func__);
    mFramesPromise.RejectIfExists(aResult, __func__);
  }

  void OnMetadata(const ImageMetadata* aMetadata) override {
    MutexAutoLock lock(mMutex);

    // We must have already gotten destroyed before metadata decoding finished.
    if (!mMetadataTask) {
      return;
    }

    if (!aMetadata) {
      MOZ_LOG(sLog, LogLevel::Error,
              ("[%p] AnonymousDecoderImpl::OnMetadata -- failed", this));
      DestroyLocked(NS_ERROR_FAILURE);
      return;
    }

    mMetadataResult.mNativeSizes = aMetadata->GetNativeSizes().Clone();

    const auto size = aMetadata->GetSize();
    mMetadataResult.mWidth = size.width;
    mMetadataResult.mHeight = size.height;
    mMetadataResult.mRepetitions = aMetadata->GetLoopCount();
    mMetadataResult.mAnimated = aMetadata->HasAnimation();

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::OnMetadata -- %dx%d, repetitions %d, "
             "animated %d",
             this, size.width, size.height, mMetadataResult.mRepetitions,
             mMetadataResult.mAnimated));

    if (mOutputSize && !mMetadataResult.mAnimated && mFramesTask &&
        !mFramesTaskRunning) {
      if (mOutputSize->width <= size.width &&
          mOutputSize->height <= size.height) {
        MOZ_LOG(
            sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::OnMetadata -- use output size %dx%d",
             this, mOutputSize->width, mOutputSize->height));
        mFramesTask->SetOutputSize(
            OrientedIntSize::FromUnknownSize(*mOutputSize));
      } else {
        MOZ_LOG(sLog, LogLevel::Debug,
                ("[%p] AnonymousDecoderImpl::OnMetadata -- cannot use output "
                 "size %dx%d, exceeds metadata size",
                 this, mOutputSize->width, mOutputSize->height));
      }
    }

    if (mFrameCountTask && !mFrameCountTaskRunning) {
      MOZ_LOG(
          sLog, LogLevel::Debug,
          ("[%p] AnonymousDecoderImpl::OnMetadata -- start frame count task",
           this));
      mFrameCountTaskRunning = mFrameCountTask->MaybeStart();
      return;
    }

    mMetadataPromise.Resolve(mMetadataResult, __func__);

    if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::OnMetadata -- start frames task, "
               "want %zu",
               this, mFramesToDecode));
      mFramesTaskRunning = mFramesTask->MaybeStart();
    }
  }

  void OnFrameCount(uint32_t aFrameCount, bool aComplete) override {
    MutexAutoLock lock(mMutex);

    // We must have already gotten destroyed before frame count decoding
    // finished.
    if (!mFrameCountTask) {
      return;
    }

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::OnFrameCount -- frameCount %u, "
             "complete %d",
             this, aFrameCount, aComplete));

    bool resolve = aComplete;
    if (mFrameCount < aFrameCount) {
      mFrameCount = aFrameCount;
      resolve = true;
    }

    // If the frame count task discovered more than one frame, the image is
    // actually animated even if the metadata decoder missed it (e.g. a GIF
    // whose first frame has delay_time=0).
    if (mFrameCount > 1 && !mMetadataResult.mAnimated) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::OnFrameCount -- discovered "
               "animation, frameCount %u",
               this, mFrameCount));
      mMetadataResult.mAnimated = true;
    }

    // If metadata completing is waiting on an updated frame count, resolve it.
    mMetadataResult.mFrameCount = mFrameCount;
    mMetadataResult.mFrameCountComplete = aComplete;
    mMetadataPromise.ResolveIfExists(mMetadataResult, __func__);

    if (mMetadataTask) {
      mMetadataTask = nullptr;
      if (mFramesTask && mFramesToDecode > 0 && !mFramesTaskRunning) {
        MOZ_LOG(
            sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::OnFrameCount -- start frames task, "
             "want %zu",
             this, mFramesToDecode));
        mFramesTaskRunning = mFramesTask->MaybeStart();
      }
    }

    if (resolve) {
      mFrameCountPromise.ResolveIfExists(
          DecodeFrameCountResult{aFrameCount, aComplete}, __func__);
    }

    if (aComplete) {
      mFrameCountTask = nullptr;
    }
  }

  bool OnFrameAvailable(RefPtr<imgFrame>&& aFrame,
                        RefPtr<gfx::SourceSurface>&& aSurface) override {
    MutexAutoLock lock(mMutex);

    MOZ_DIAGNOSTIC_ASSERT(mFramesTaskRunning);

    // We must have already gotten destroyed before frame decoding finished.
    if (!mFramesTask) {
      mFramesTaskRunning = false;
      return false;
    }

    // Filter duplicate frames.
    if (mLastFrame == aFrame) {
      return true;
    }

    mPendingFramesResult.mFrames.AppendElement(
        DecodedFrame{std::move(aSurface), mMetadataResult.mAnimated
                                              ? aFrame->GetTimeout()
                                              : FrameTimeout::Forever()});
    mLastFrame = std::move(aFrame);

    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::OnFrameAvailable -- want %zu, got %zu",
             this, mFramesToDecode, mPendingFramesResult.mFrames.Length()));

    // Check if we have satisfied the number of requested frames.
    if (mFramesToDecode > mPendingFramesResult.mFrames.Length()) {
      return true;
    }

    mFramesToDecode = 0;
    if (!mFramesPromise.IsEmpty()) {
      mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__);
    }
    mFramesTaskRunning = false;
    return false;
  }

  void OnFramesComplete() override {
    MutexAutoLock lock(mMutex);

    // We must have already gotten destroyed before frame decoding finished.
    if (!mFramesTask) {
      return;
    }

    MOZ_LOG(
        sLog, LogLevel::Debug,
        ("[%p] AnonymousDecoderImpl::OnFramesComplete -- wanted %zu, got %zu",
         this, mFramesToDecode, mPendingFramesResult.mFrames.Length()));

    mFramesToDecode = 0;
    mPendingFramesResult.mFinished = true;
    if (!mFramesPromise.IsEmpty()) {
      mFramesPromise.Resolve(std::move(mPendingFramesResult), __func__);
    }
    mLastFrame = nullptr;
    mFramesTask = nullptr;
  }

  RefPtr<DecodeMetadataPromise> DecodeMetadata() override {
    MutexAutoLock lock(mMutex);

    if (!mMetadataTask) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::DecodeMetadata -- already complete",
               this));
      if (mMetadataResult.mWidth > 0 && mMetadataResult.mHeight > 0) {
        return DecodeMetadataPromise::CreateAndResolve(mMetadataResult,
                                                       __func__);
      }
      return DecodeMetadataPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
    }

    if (!mMetadataTaskRunning) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::DecodeMetadata -- queue", this));
      mMetadataTaskRunning = mMetadataTask->MaybeStart();
    }

    return mMetadataPromise.Ensure(__func__);
  }

  RefPtr<DecodeFrameCountPromise> DecodeFrameCount(
      uint32_t aKnownFrameCount) override {
    MutexAutoLock lock(mMutex);

    MOZ_ASSERT(mFrameCountPromise.IsEmpty());

    // If we have finished, or we have an updated frame count, return right
    // away. This may drive the frame decoder for the application as the data
    // comes in from the network.
    if (!mFrameCountTask || aKnownFrameCount < mFrameCount) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- known %u, "
               "detected %u, complete %d",
               this, aKnownFrameCount, mFrameCount, !mFrameCountTask));
      return DecodeFrameCountPromise::CreateAndResolve(
          DecodeFrameCountResult{mFrameCount,
                                 /* mFinished */ !mFrameCountTask},
          __func__);
    }

    // mFrameCountTask is launching when metadata decoding is finished.
    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::DecodeFrameCount -- waiting, known "
             "%u, detected %u",
             this, aKnownFrameCount, mFrameCount));
    return mFrameCountPromise.Ensure(__func__);
  }

  RefPtr<DecodeFramesPromise> DecodeFrames(size_t aCount) override {
    MutexAutoLock lock(mMutex);

    // If we cleared our task reference, then we know we finished decoding.
    if (!mFramesTask) {
      mPendingFramesResult.mFinished = true;
      return DecodeFramesPromise::CreateAndResolve(
          std::move(mPendingFramesResult), __func__);
    }

    // If we are not waiting on any frames, then we know we paused decoding.
    // If we still are metadata decoding, we need to wait.
    if (mFramesToDecode == 0 && !mMetadataTask && !mFramesTaskRunning) {
      MOZ_LOG(sLog, LogLevel::Debug,
              ("[%p] AnonymousDecoderImpl::DecodeFrames -- queue", this));
      mFramesTaskRunning = mFramesTask->MaybeStart();
    }

    mFramesToDecode = std::max(mFramesToDecode, aCount);
    return mFramesPromise.Ensure(__func__);
  }

  void CancelDecodeFrames() override {
    MutexAutoLock lock(mMutex);
    MOZ_LOG(sLog, LogLevel::Debug,
            ("[%p] AnonymousDecoderImpl::CancelDecodeFrames", this));
    mFramesToDecode = 0;
    mFramesPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
  }

 private:
  Mutex mMutex;
  MozPromiseHolder<DecodeMetadataPromise> mMetadataPromise
      MOZ_GUARDED_BY(mMutex);
  MozPromiseHolder<DecodeFrameCountPromise> mFrameCountPromise
      MOZ_GUARDED_BY(mMutex);
  MozPromiseHolder<DecodeFramesPromise> mFramesPromise MOZ_GUARDED_BY(mMutex);
  RefPtr<AnonymousFramesDecoderTask> mFramesTask MOZ_GUARDED_BY(mMutex);
  RefPtr<AnonymousMetadataDecoderTask> mMetadataTask MOZ_GUARDED_BY(mMutex);
  RefPtr<AnonymousFrameCountDecoderTask> mFrameCountTask MOZ_GUARDED_BY(mMutex);
  RefPtr<imgFrame> mLastFrame MOZ_GUARDED_BY(mMutex);
  DecodeMetadataResult mMetadataResult MOZ_GUARDED_BY(mMutex);
  DecodeFramesResult mPendingFramesResult MOZ_GUARDED_BY(mMutex);
  Maybe<gfx::IntSize> mOutputSize MOZ_GUARDED_BY(mMutex);
  size_t mFramesToDecode MOZ_GUARDED_BY(mMutex) = 1;
  uint32_t mFrameCount MOZ_GUARDED_BY(mMutex) = 0;
  bool mMetadataTaskRunning MOZ_GUARDED_BY(mMutex) = false;
  bool mFrameCountTaskRunning MOZ_GUARDED_BY(mMutex) = false;
  bool mFramesTaskRunning MOZ_GUARDED_BY(mMutex) = false;
};

/* static */ already_AddRefed<AnonymousDecoder> ImageUtils::CreateDecoder(
    SourceBuffer* aSourceBuffer, DecoderType aType,
    const Maybe<gfx::IntSize>& aOutputSize, SurfaceFlags aSurfaceFlags) {
  if (NS_WARN_IF(!aSourceBuffer)) {
    return nullptr;
  }

  if (NS_WARN_IF(aType == DecoderType::UNKNOWN)) {
    return nullptr;
  }

  RefPtr<Decoder> decoder = DecoderFactory::CreateAnonymousDecoder(
      aType, WrapNotNull(aSourceBuffer), Nothing(),
      DecoderFlags::IMAGE_IS_TRANSIENT, aSurfaceFlags);
  if (NS_WARN_IF(!decoder)) {
    return nullptr;
  }

  auto anonymousDecoder = MakeRefPtr<AnonymousDecoderImpl>(aOutputSize);
  if (NS_WARN_IF(!anonymousDecoder->Initialize(std::move(decoder)))) {
    return nullptr;
  }

  return anonymousDecoder.forget();
}

/* static */ DecoderType ImageUtils::GetDecoderType(
    const nsACString& aMimeType) {
  return DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
}

}  // namespace mozilla::image
