/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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 "Decoder.h"

#include "mozilla/gfx/2D.h"
#include "DecodePool.h"
#include "GeckoProfiler.h"
#include "IDecodingTask.h"
#include "ISurfaceProvider.h"
#include "nsProxyRelease.h"
#include "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"
#include "mozilla/Telemetry.h"

using mozilla::gfx::IntSize;
using mozilla::gfx::SurfaceFormat;

namespace mozilla {
namespace image {

class MOZ_STACK_CLASS AutoRecordDecoderTelemetry final
{
public:
  explicit AutoRecordDecoderTelemetry(Decoder* aDecoder)
    : mDecoder(aDecoder)
  {
    MOZ_ASSERT(mDecoder);

    // Begin recording telemetry data.
    mStartTime = TimeStamp::Now();
  }

  ~AutoRecordDecoderTelemetry()
  {
    // Finish telemetry.
    mDecoder->mDecodeTime += (TimeStamp::Now() - mStartTime);
  }

private:
  Decoder* mDecoder;
  TimeStamp mStartTime;
};

Decoder::Decoder(RasterImage* aImage)
  : mImageData(nullptr)
  , mImageDataLength(0)
  , mColormap(nullptr)
  , mColormapSize(0)
  , mImage(aImage)
  , mProgress(NoProgress)
  , mFrameCount(0)
  , mLoopLength(FrameTimeout::Zero())
  , mDecoderFlags(DefaultDecoderFlags())
  , mSurfaceFlags(DefaultSurfaceFlags())
  , mInitialized(false)
  , mMetadataDecode(false)
  , mHaveExplicitOutputSize(false)
  , mInFrame(false)
  , mFinishedNewFrame(false)
  , mReachedTerminalState(false)
  , mDecodeDone(false)
  , mError(false)
  , mShouldReportError(false)
{ }

Decoder::~Decoder()
{
  MOZ_ASSERT(mProgress == NoProgress || !mImage,
             "Destroying Decoder without taking all its progress changes");
  MOZ_ASSERT(mInvalidRect.IsEmpty() || !mImage,
             "Destroying Decoder without taking all its invalidations");
  mInitialized = false;

  if (mImage && !NS_IsMainThread()) {
    // Dispatch mImage to main thread to prevent it from being destructed by the
    // decode thread.
    NS_ReleaseOnMainThread(mImage.forget());
  }
}

/*
 * Common implementation of the decoder interface.
 */

nsresult
Decoder::Init()
{
  // No re-initializing
  MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");

  // All decoders must have a SourceBufferIterator.
  MOZ_ASSERT(mIterator);

  // Metadata decoders must not set an output size.
  MOZ_ASSERT_IF(mMetadataDecode, !mHaveExplicitOutputSize);

  // All decoders must be anonymous except for metadata decoders.
  // XXX(seth): Soon that exception will be removed.
  MOZ_ASSERT_IF(mImage, IsMetadataDecode());

  // Implementation-specific initialization.
  nsresult rv = InitInternal();

  mInitialized = true;

  return rv;
}

LexerResult
Decoder::Decode(IResumable* aOnResume /* = nullptr */)
{
  MOZ_ASSERT(mInitialized, "Should be initialized here");
  MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");

  // If we're already done, don't attempt to keep decoding.
  if (GetDecodeDone()) {
    return LexerResult(HasError() ? TerminalState::FAILURE
                                  : TerminalState::SUCCESS);
  }

  LexerResult lexerResult(TerminalState::FAILURE);
  {
    PROFILER_LABEL("ImageDecoder", "Decode", js::ProfileEntry::Category::GRAPHICS);
    AutoRecordDecoderTelemetry telemetry(this);

    lexerResult =  DoDecode(*mIterator, aOnResume);
  };

  if (lexerResult.is<Yield>()) {
    // We either need more data to continue (in which case either @aOnResume or
    // the caller will reschedule us to run again later), or the decoder is
    // yielding to allow the caller access to some intermediate output.
    return lexerResult;
  }

  // We reached a terminal state; we're now done decoding.
  MOZ_ASSERT(lexerResult.is<TerminalState>());
  mReachedTerminalState = true;

  // If decoding failed, record that fact.
  if (lexerResult.as<TerminalState>() == TerminalState::FAILURE) {
    PostError();
  }

  // Perform final cleanup.
  CompleteDecode();

  return LexerResult(HasError() ? TerminalState::FAILURE
                                : TerminalState::SUCCESS);
}

LexerResult
Decoder::TerminateFailure()
{
  PostError();

  // Perform final cleanup if need be.
  if (!mReachedTerminalState) {
    mReachedTerminalState = true;
    CompleteDecode();
  }

  return LexerResult(TerminalState::FAILURE);
}

bool
Decoder::ShouldSyncDecode(size_t aByteLimit)
{
  MOZ_ASSERT(aByteLimit > 0);
  MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");

  return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
}

void
Decoder::CompleteDecode()
{
  // Implementation-specific finalization.
  nsresult rv = BeforeFinishInternal();
  if (NS_FAILED(rv)) {
    PostError();
  }

  rv = HasError() ? FinishWithErrorInternal()
                  : FinishInternal();
  if (NS_FAILED(rv)) {
    PostError();
  }

  if (IsMetadataDecode()) {
    // If this was a metadata decode and we never got a size, the decode failed.
    if (!HasSize()) {
      PostError();
    }
    return;
  }

  // If the implementation left us mid-frame, finish that up. Note that it may
  // have left us transparent.
  if (mInFrame) {
    PostHasTransparency();
    PostFrameStop();
  }

  // If PostDecodeDone() has not been called, we may need to send teardown
  // notifications if it is unrecoverable.
  if (!mDecodeDone) {
    // We should always report an error to the console in this case.
    mShouldReportError = true;

    if (GetCompleteFrameCount() > 0) {
      // We're usable if we have at least one complete frame, so do exactly
      // what we should have when the decoder completed.
      PostHasTransparency();
      PostDecodeDone();
    } else {
      // We're not usable. Record some final progress indicating the error.
      mProgress |= FLAG_DECODE_COMPLETE | FLAG_HAS_ERROR;
    }
  }

  if (mDecodeDone) {
    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");

    // If this image wasn't animated and isn't a transient image, mark its frame
    // as optimizable. We don't support optimizing animated images and
    // optimizing transient images isn't worth it.
    if (!HasAnimation() &&
        !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
        mCurrentFrame) {
      mCurrentFrame->SetOptimizable();
    }
  }
}

void
Decoder::SetOutputSize(const gfx::IntSize& aSize)
{
  mOutputSize = Some(aSize);
  mHaveExplicitOutputSize = true;
}

Maybe<gfx::IntSize>
Decoder::ExplicitOutputSize() const
{
  MOZ_ASSERT_IF(mHaveExplicitOutputSize, mOutputSize);
  return mHaveExplicitOutputSize ? mOutputSize : Nothing();
}

Maybe<uint32_t>
Decoder::TakeCompleteFrameCount()
{
  const bool finishedNewFrame = mFinishedNewFrame;
  mFinishedNewFrame = false;
  return finishedNewFrame ? Some(GetCompleteFrameCount()) : Nothing();
}

DecoderFinalStatus
Decoder::FinalStatus() const
{
  return DecoderFinalStatus(IsMetadataDecode(),
                            GetDecodeDone(),
                            HasError(),
                            ShouldReportError());
}

DecoderTelemetry
Decoder::Telemetry() const
{
  MOZ_ASSERT(mIterator);
  return DecoderTelemetry(SpeedHistogram(),
                          mIterator->ByteCount(),
                          mIterator->ChunkCount(),
                          mDecodeTime);
}

nsresult
Decoder::AllocateFrame(uint32_t aFrameNum,
                       const gfx::IntSize& aOutputSize,
                       const gfx::IntRect& aFrameRect,
                       gfx::SurfaceFormat aFormat,
                       uint8_t aPaletteDepth)
{
  mCurrentFrame = AllocateFrameInternal(aFrameNum, aOutputSize, aFrameRect,
                                        aFormat, aPaletteDepth,
                                        mCurrentFrame.get());

  if (mCurrentFrame) {
    // Gather the raw pointers the decoders will use.
    mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
    mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);

    // We should now be on |aFrameNum|. (Note that we're comparing the frame
    // number, which is zero-based, with the frame count, which is one-based.)
    MOZ_ASSERT(aFrameNum + 1 == mFrameCount);

    // If we're past the first frame, PostIsAnimated() should've been called.
    MOZ_ASSERT_IF(mFrameCount > 1, HasAnimation());

    // Update our state to reflect the new frame.
    MOZ_ASSERT(!mInFrame, "Starting new frame but not done with old one!");
    mInFrame = true;
  }

  return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
}

RawAccessFrameRef
Decoder::AllocateFrameInternal(uint32_t aFrameNum,
                               const gfx::IntSize& aOutputSize,
                               const gfx::IntRect& aFrameRect,
                               SurfaceFormat aFormat,
                               uint8_t aPaletteDepth,
                               imgFrame* aPreviousFrame)
{
  if (HasError()) {
    return RawAccessFrameRef();
  }

  if (aFrameNum != mFrameCount) {
    MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
    return RawAccessFrameRef();
  }

  if (aOutputSize.width <= 0 || aOutputSize.height <= 0 ||
      aFrameRect.width <= 0 || aFrameRect.height <= 0) {
    NS_WARNING("Trying to add frame with zero or negative size");
    return RawAccessFrameRef();
  }

  NotNull<RefPtr<imgFrame>> frame = WrapNotNull(new imgFrame());
  bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
  if (NS_FAILED(frame->InitForDecoder(aOutputSize, aFrameRect, aFormat,
                                      aPaletteDepth, nonPremult))) {
    NS_WARNING("imgFrame::Init should succeed");
    return RawAccessFrameRef();
  }

  RawAccessFrameRef ref = frame->RawAccessRef();
  if (!ref) {
    frame->Abort();
    return RawAccessFrameRef();
  }

  if (aFrameNum == 1) {
    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
    aPreviousFrame->SetRawAccessOnly();

    // If we dispose of the first frame by clearing it, then the first frame's
    // refresh area is all of itself.
    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
    AnimationData previousFrameData = aPreviousFrame->GetAnimationData();
    if (previousFrameData.mDisposalMethod == DisposalMethod::CLEAR ||
        previousFrameData.mDisposalMethod == DisposalMethod::CLEAR_ALL ||
        previousFrameData.mDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
      mFirstFrameRefreshArea = previousFrameData.mRect;
    }
  }

  if (aFrameNum > 0) {
    ref->SetRawAccessOnly();

    // Some GIFs are huge but only have a small area that they animate. We only
    // need to refresh that small area when frame 0 comes around again.
    mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, frame->GetRect());
  }

  mFrameCount++;

  return ref;
}

/*
 * Hook stubs. Override these as necessary in decoder implementations.
 */

nsresult Decoder::InitInternal() { return NS_OK; }
nsresult Decoder::BeforeFinishInternal() { return NS_OK; }
nsresult Decoder::FinishInternal() { return NS_OK; }
nsresult Decoder::FinishWithErrorInternal() { return NS_OK; }

/*
 * Progress Notifications
 */

void
Decoder::PostSize(int32_t aWidth,
                  int32_t aHeight,
                  Orientation aOrientation /* = Orientation()*/)
{
  // Validate.
  MOZ_ASSERT(aWidth >= 0, "Width can't be negative!");
  MOZ_ASSERT(aHeight >= 0, "Height can't be negative!");

  // Set our intrinsic size.
  mImageMetadata.SetSize(aWidth, aHeight, aOrientation);

  // Set our output size if it's not already set.
  if (!mOutputSize) {
    mOutputSize = Some(IntSize(aWidth, aHeight));
  }

  MOZ_ASSERT(mOutputSize->width <= aWidth && mOutputSize->height <= aHeight,
             "Output size will result in upscaling");

  // Create a downscaler if we need to downscale. This is used by legacy
  // decoders that haven't been converted to use SurfacePipe yet.
  // XXX(seth): Obviously, we'll remove this once all decoders use SurfacePipe.
  if (mOutputSize->width < aWidth || mOutputSize->height < aHeight) {
    mDownscaler.emplace(*mOutputSize);
  }

  // Record this notification.
  mProgress |= FLAG_SIZE_AVAILABLE;
}

void
Decoder::PostHasTransparency()
{
  mProgress |= FLAG_HAS_TRANSPARENCY;
}

void
Decoder::PostIsAnimated(FrameTimeout aFirstFrameTimeout)
{
  mProgress |= FLAG_IS_ANIMATED;
  mImageMetadata.SetHasAnimation();
  mImageMetadata.SetFirstFrameTimeout(aFirstFrameTimeout);
}

void
Decoder::PostFrameStop(Opacity aFrameOpacity
                         /* = Opacity::SOME_TRANSPARENCY */,
                       DisposalMethod aDisposalMethod
                         /* = DisposalMethod::KEEP */,
                       FrameTimeout aTimeout /* = FrameTimeout::Forever() */,
                       BlendMethod aBlendMethod /* = BlendMethod::OVER */,
                       const Maybe<nsIntRect>& aBlendRect /* = Nothing() */)
{
  // We should be mid-frame
  MOZ_ASSERT(!IsMetadataDecode(), "Stopping frame during metadata decode");
  MOZ_ASSERT(mInFrame, "Stopping frame when we didn't start one");
  MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");

  // Update our state.
  mInFrame = false;
  mFinishedNewFrame = true;

  mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout,
                        aBlendMethod, aBlendRect);

  mProgress |= FLAG_FRAME_COMPLETE;

  mLoopLength += aTimeout;

  // If we're not sending partial invalidations, then we send an invalidation
  // here when the first frame is complete.
  if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
    mInvalidRect.UnionRect(mInvalidRect,
                           IntRect(IntPoint(), Size()));
  }
}

void
Decoder::PostInvalidation(const gfx::IntRect& aRect,
                          const Maybe<gfx::IntRect>& aRectAtOutputSize
                            /* = Nothing() */)
{
  // We should be mid-frame
  MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
  MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");

  // Record this invalidation, unless we're not sending partial invalidations
  // or we're past the first frame.
  if (ShouldSendPartialInvalidations() && mFrameCount == 1) {
    mInvalidRect.UnionRect(mInvalidRect, aRect);
    mCurrentFrame->ImageUpdated(aRectAtOutputSize.valueOr(aRect));
  }
}

void
Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
{
  MOZ_ASSERT(!IsMetadataDecode(), "Done with decoding in metadata decode");
  MOZ_ASSERT(!mInFrame, "Can't be done decoding if we're mid-frame!");
  MOZ_ASSERT(!mDecodeDone, "Decode already done!");
  mDecodeDone = true;

  mImageMetadata.SetLoopCount(aLoopCount);

  // Some metadata that we track should take into account every frame in the
  // image. If this is a first-frame-only decode, our accumulated loop length
  // and first frame refresh area only includes the first frame, so it's not
  // correct and we don't record it.
  if (!IsFirstFrameDecode()) {
    mImageMetadata.SetLoopLength(mLoopLength);
    mImageMetadata.SetFirstFrameRefreshArea(mFirstFrameRefreshArea);
  }

  mProgress |= FLAG_DECODE_COMPLETE;
}

void
Decoder::PostError()
{
  mError = true;

  if (mInFrame) {
    MOZ_ASSERT(mCurrentFrame);
    MOZ_ASSERT(mFrameCount > 0);
    mCurrentFrame->Abort();
    mInFrame = false;
    --mFrameCount;
  }
}

} // namespace image
} // namespace mozilla