/* -*- 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 "ImageLogging.h" // Must appear first
#include "nsWebPDecoder.h"

#include "RasterImage.h"
#include "SurfacePipeFactory.h"

using namespace mozilla::gfx;

namespace mozilla {
namespace image {

static LazyLogModule sWebPLog("WebPDecoder");

nsWebPDecoder::nsWebPDecoder(RasterImage* aImage)
  : Decoder(aImage)
  , mLexer(Transition::ToUnbuffered(State::FINISHED_WEBP_DATA,
                                    State::WEBP_DATA,
                                    SIZE_MAX),
           Transition::TerminateSuccess())
  , mDecoder(nullptr)
  , mBlend(BlendMethod::OVER)
  , mDisposal(DisposalMethod::KEEP)
  , mTimeout(FrameTimeout::Forever())
  , mFormat(SurfaceFormat::B8G8R8X8)
  , mLastRow(0)
  , mCurrentFrame(0)
{
  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::nsWebPDecoder", this));
}

nsWebPDecoder::~nsWebPDecoder()
{
  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::~nsWebPDecoder", this));
  WebPIDelete(mDecoder);
}

LexerResult
nsWebPDecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
{
  MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");

  return mLexer.Lex(aIterator, aOnResume,
                    [=](State aState, const char* aData, size_t aLength) {
    switch (aState) {
      case State::WEBP_DATA:
        if (!HasSize()) {
          return ReadHeader(aData, aLength);
        }
        return ReadPayload(aData, aLength);
      case State::FINISHED_WEBP_DATA:
        return FinishedData();
    }
    MOZ_CRASH("Unknown State");
  });
}

nsresult
nsWebPDecoder::CreateFrame(const nsIntRect& aFrameRect)
{
  MOZ_ASSERT(HasSize());
  MOZ_ASSERT(!mDecoder);

  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::CreateFrame -- frame %u, %d x %d\n",
       this, mCurrentFrame, aFrameRect.width, aFrameRect.height));

  // If this is our first frame in an animation and it doesn't cover the
  // full frame, then we are transparent even if there is no alpha
  if (mCurrentFrame == 0 && !aFrameRect.IsEqualEdges(FullFrame())) {
    MOZ_ASSERT(HasAnimation());
    PostHasTransparency();
  }

  WebPInitDecBuffer(&mBuffer);
  mBuffer.colorspace = MODE_RGBA;

  mDecoder = WebPINewDecoder(&mBuffer);
  if (!mDecoder) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::CreateFrame -- create decoder error\n",
         this));
    return NS_ERROR_FAILURE;
  }

  Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(this,
      mCurrentFrame, Size(), OutputSize(), aFrameRect,
      mFormat, SurfacePipeFlags());
  if (!pipe) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::CreateFrame -- no pipe\n", this));
    return NS_ERROR_FAILURE;
  }

  mPipe = Move(*pipe);
  return NS_OK;
}

void
nsWebPDecoder::EndFrame()
{
  MOZ_ASSERT(HasSize());
  MOZ_ASSERT(mDecoder);

  auto opacity = mFormat == SurfaceFormat::B8G8R8A8
                 ? Opacity::SOME_TRANSPARENCY : Opacity::FULLY_OPAQUE;

  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::EndFrame -- frame %u, opacity %d, "
       "disposal %d, timeout %d, blend %d\n",
       this, mCurrentFrame, (int)opacity, (int)mDisposal,
       mTimeout.AsEncodedValueDeprecated(), (int)mBlend));

  PostFrameStop(opacity, mDisposal, mTimeout, mBlend);
  WebPIDelete(mDecoder);
  mDecoder = nullptr;
  mLastRow = 0;
  ++mCurrentFrame;
}

nsresult
nsWebPDecoder::GetDataBuffer(const uint8_t*& aData, size_t& aLength)
{
  if (!mData.empty() && mData.begin() != aData) {
    if (!mData.append(aData, aLength)) {
      MOZ_LOG(sWebPLog, LogLevel::Error,
          ("[this=%p] nsWebPDecoder::GetDataBuffer -- oom, append %zu on %zu\n",
           this, aLength, mData.length()));
      return NS_ERROR_OUT_OF_MEMORY;
    }
    aData = mData.begin();
    aLength = mData.length();
  }
  return NS_OK;
}

nsresult
nsWebPDecoder::SaveDataBuffer(const uint8_t* aData, size_t aLength)
{
  if (mData.empty() && !mData.append(aData, aLength)) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::SaveDataBuffer -- oom, append %zu on %zu\n",
         this, aLength, mData.length()));
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::ReadHeader(const char* aData, size_t aLength)
{
  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::ReadHeader -- %zu bytes\n", this, aLength));

  // XXX(aosmond): In an ideal world, we could request the lexer to do this
  // buffering for us (and in turn the underlying SourceBuffer). That way we
  // could avoid extra copies during the decode and just do
  // SourceBuffer::Compact on each iteration. For a typical WebP image we
  // can hope that we will get the full header in the first packet, but
  // for animated images we will end up buffering the whole stream if it
  // not already fully received and contiguous.
  auto data = (const uint8_t*)aData;
  size_t length = aLength;
  if (NS_FAILED(GetDataBuffer(data, length))) {
    return Transition::TerminateFailure();
  }

  WebPBitstreamFeatures features;
  VP8StatusCode status = WebPGetFeatures(data, length, &features);
  switch (status) {
    case VP8_STATUS_OK:
      break;
    case VP8_STATUS_NOT_ENOUGH_DATA:
      if (NS_FAILED(SaveDataBuffer(data, length))) {
        return Transition::TerminateFailure();
      }
      return Transition::ContinueUnbuffered(State::WEBP_DATA);
    default:
      MOZ_LOG(sWebPLog, LogLevel::Error,
          ("[this=%p] nsWebPDecoder::ReadHeader -- parse error %d\n",
           this, status));
      return Transition::TerminateFailure();
  }

  if (features.has_animation) {
    // A metadata decode expects to get the correct first frame timeout which
    // sadly is not provided by the normal WebP header parsing.
    WebPDemuxState state;
    WebPData fragment;
    fragment.bytes = data;
    fragment.size = length;
    WebPDemuxer* demuxer = WebPDemuxPartial(&fragment, &state);
    if (!demuxer || state == WEBP_DEMUX_PARSE_ERROR) {
      MOZ_LOG(sWebPLog, LogLevel::Error,
          ("[this=%p] nsWebPDecoder::ReadHeader -- demux parse error\n", this));
      WebPDemuxDelete(demuxer);
      return Transition::TerminateFailure();
    }

    WebPIterator iter;
    if (!WebPDemuxGetFrame(demuxer, 1, &iter)) {
      WebPDemuxDelete(demuxer);
      if (state == WEBP_DEMUX_DONE) {
        MOZ_LOG(sWebPLog, LogLevel::Error,
            ("[this=%p] nsWebPDecoder::ReadHeader -- demux parse error\n",
             this));
        return Transition::TerminateFailure();
      }
      if (NS_FAILED(SaveDataBuffer(data, length))) {
        return Transition::TerminateFailure();
      }
      return Transition::ContinueUnbuffered(State::WEBP_DATA);
    }

    PostIsAnimated(FrameTimeout::FromRawMilliseconds(iter.duration));
    WebPDemuxReleaseIterator(&iter);
    WebPDemuxDelete(demuxer);
  }

  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::ReadHeader -- %d x %d, alpha %d, "
       "animation %d, format %d, metadata decode %d, first frame decode %d\n",
       this, features.width, features.height, features.has_alpha,
       features.has_animation, features.format, IsMetadataDecode(),
       IsFirstFrameDecode()));

  PostSize(features.width, features.height);
  if (features.has_alpha) {
    mFormat = SurfaceFormat::B8G8R8A8;
    PostHasTransparency();
  }

  if (IsMetadataDecode()) {
    return Transition::TerminateSuccess();
  }

  auto transition = ReadPayload((const char*)data, length);
  if (!features.has_animation) {
    mData.clearAndFree();
  }
  return transition;
}

LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::ReadPayload(const char* aData, size_t aLength)
{
  auto data = (const uint8_t*)aData;
  if (!HasAnimation()) {
    auto rv = ReadSingle(data, aLength, true, FullFrame());
    if (rv.NextStateIsTerminal() &&
        rv.NextStateAsTerminal() == TerminalState::SUCCESS) {
      PostDecodeDone();
    }
    return rv;
  }
  return ReadMultiple(data, aLength);
}

LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::ReadSingle(const uint8_t* aData, size_t aLength, bool aAppend, const IntRect& aFrameRect)
{
  MOZ_ASSERT(!IsMetadataDecode());
  MOZ_ASSERT(aData);
  MOZ_ASSERT(aLength > 0);

  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::ReadSingle -- %zu bytes\n", this, aLength));

  if (!mDecoder && NS_FAILED(CreateFrame(aFrameRect))) {
    return Transition::TerminateFailure();
  }

  // XXX(aosmond): The demux API can be used for single images according to the
  // documentation. If WebPIAppend is not any more efficient in its buffering
  // than what we do for animated images, we should just combine the use cases.
  bool complete;
  VP8StatusCode status;
  if (aAppend) {
    status = WebPIAppend(mDecoder, aData, aLength);
  } else {
    status = WebPIUpdate(mDecoder, aData, aLength);
  }
  switch (status) {
    case VP8_STATUS_OK:
      complete = true;
      break;
    case VP8_STATUS_SUSPENDED:
      complete = false;
      break;
    default:
      MOZ_LOG(sWebPLog, LogLevel::Error,
          ("[this=%p] nsWebPDecoder::ReadSingle -- append error %d\n",
           this, status));
      return Transition::TerminateFailure();
  }

  int lastRow = -1;
  int width = 0;
  int height = 0;
  int stride = 0;
  const uint8_t* rowStart = WebPIDecGetRGB(mDecoder, &lastRow, &width, &height, &stride);
  if (!rowStart || lastRow == -1) {
    return Transition::ContinueUnbuffered(State::WEBP_DATA);
  }

  if (width <= 0 || height <= 0 || stride <= 0) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::ReadSingle -- bad (w,h,s) = (%d, %d, %d)\n",
         this, width, height, stride));
    return Transition::TerminateFailure();
  }

  for (int row = mLastRow; row < lastRow; row++) {
    const uint8_t* src = rowStart + row * stride;
    auto result = mPipe.WritePixelsToRow<uint32_t>([&]() -> NextPixel<uint32_t> {
      MOZ_ASSERT(mFormat == SurfaceFormat::B8G8R8A8 || src[3] == 0xFF);
      const uint32_t pixel = gfxPackedPixel(src[3], src[0], src[1], src[2]);
      src += 4;
      return AsVariant(pixel);
    });
    MOZ_ASSERT(result != WriteState::FAILURE);
    MOZ_ASSERT_IF(result == WriteState::FINISHED, complete && row == lastRow - 1);

    if (result == WriteState::FAILURE) {
      MOZ_LOG(sWebPLog, LogLevel::Error,
          ("[this=%p] nsWebPDecoder::ReadSingle -- write pixels error\n",
           this));
      return Transition::TerminateFailure();
    }
  }

  if (mLastRow != lastRow) {
    mLastRow = lastRow;

    Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
    if (invalidRect) {
      PostInvalidation(invalidRect->mInputSpaceRect,
          Some(invalidRect->mOutputSpaceRect));
    }
  }

  if (!complete) {
    return Transition::ContinueUnbuffered(State::WEBP_DATA);
  }

  EndFrame();
  return Transition::TerminateSuccess();
}

LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::ReadMultiple(const uint8_t* aData, size_t aLength)
{
  MOZ_ASSERT(!IsMetadataDecode());
  MOZ_ASSERT(aData);

  MOZ_LOG(sWebPLog, LogLevel::Debug,
      ("[this=%p] nsWebPDecoder::ReadMultiple -- %zu bytes\n", this, aLength));

  auto data = aData;
  size_t length = aLength;
  if (NS_FAILED(GetDataBuffer(data, length))) {
    return Transition::TerminateFailure();
  }

  WebPDemuxState state;
  WebPData fragment;
  fragment.bytes = data;
  fragment.size = length;
  WebPDemuxer* demuxer = WebPDemuxPartial(&fragment, &state);
  if (!demuxer) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::ReadMultiple -- create demuxer error\n",
         this));
    return Transition::TerminateFailure();
  }

  if (state == WEBP_DEMUX_PARSE_ERROR) {
    MOZ_LOG(sWebPLog, LogLevel::Error,
        ("[this=%p] nsWebPDecoder::ReadMultiple -- demuxer parse error\n",
         this));
    WebPDemuxDelete(demuxer);
    return Transition::TerminateFailure();
  }

  bool complete = false;
  WebPIterator iter;
  auto rv = Transition::ContinueUnbuffered(State::WEBP_DATA);
  if (WebPDemuxGetFrame(demuxer, mCurrentFrame + 1, &iter)) {
    switch (iter.blend_method) {
      case WEBP_MUX_BLEND:
        mBlend = BlendMethod::OVER;
        break;
      case WEBP_MUX_NO_BLEND:
        mBlend = BlendMethod::SOURCE;
        break;
      default:
        MOZ_ASSERT_UNREACHABLE("Unhandled blend method");
        break;
    }

    switch (iter.dispose_method) {
      case WEBP_MUX_DISPOSE_NONE:
        mDisposal = DisposalMethod::KEEP;
        break;
      case WEBP_MUX_DISPOSE_BACKGROUND:
        mDisposal = DisposalMethod::CLEAR;
        break;
      default:
        MOZ_ASSERT_UNREACHABLE("Unhandled dispose method");
        break;
    }

    mFormat = iter.has_alpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8;
    mTimeout = FrameTimeout::FromRawMilliseconds(iter.duration);
    nsIntRect frameRect(iter.x_offset, iter.y_offset, iter.width, iter.height);

    rv = ReadSingle(iter.fragment.bytes, iter.fragment.size, false, frameRect);
    complete = state == WEBP_DEMUX_DONE && !WebPDemuxNextFrame(&iter);
    WebPDemuxReleaseIterator(&iter);
  }

  if (rv.NextStateIsTerminal()) {
    if (rv.NextStateAsTerminal() == TerminalState::SUCCESS) {
      // If we extracted one frame, and it is not the last, we need to yield to
      // the lexer to allow the upper layers to acknowledge the frame.
      if (!complete && !IsFirstFrameDecode()) {
        // The resume point is determined by whether or not we had to buffer.
        // If we have yet to buffer, we want to resume at the same point,
        // otherwise our internal buffer has everything we need and we want
        // to resume having consumed all of the current fragment.
        rv = Transition::ContinueUnbufferedAfterYield(State::WEBP_DATA,
                 mData.empty() ? 0 : aLength);
      } else {
        uint32_t loopCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);

        MOZ_LOG(sWebPLog, LogLevel::Debug,
          ("[this=%p] nsWebPDecoder::ReadMultiple -- loop count %u\n",
           this, loopCount));
        PostDecodeDone(loopCount - 1);
      }
    }
  } else if (NS_FAILED(SaveDataBuffer(data, length))) {
    rv = Transition::TerminateFailure();
  }

  WebPDemuxDelete(demuxer);
  return rv;
}

LexerTransition<nsWebPDecoder::State>
nsWebPDecoder::FinishedData()
{
  // Since we set up an unbuffered read for SIZE_MAX bytes, if we actually read
  // all that data something is really wrong.
  MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
  return Transition::TerminateFailure();
}

} // namespace image
} // namespace mozilla