/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "WebRenderUserData.h"

#include "mozilla/layers/ImageClient.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/WebRenderMessages.h"
#include "mozilla/layers/IpcResourceUpdateQueue.h"
#include "mozilla/layers/SharedSurfacesChild.h"
#include "nsDisplayListInvalidation.h"
#include "nsIFrame.h"
#include "WebRenderCanvasRenderer.h"

namespace mozilla {
namespace layers {

/* static */ bool
WebRenderUserData::SupportsAsyncUpdate(nsIFrame* aFrame)
{
  if (!aFrame ||
      !aFrame->HasProperty(nsIFrame::WebRenderUserDataProperty())) {
    return false;
  }
  RefPtr<WebRenderUserData> data;
  nsIFrame::WebRenderUserDataTable* userDataTable =
    aFrame->GetProperty(nsIFrame::WebRenderUserDataProperty());

  userDataTable->Get(static_cast<uint32_t>(DisplayItemType::TYPE_VIDEO), getter_AddRefs(data));
  if (data && data->AsImageData()) {
    return data->AsImageData()->IsAsync();
  }

  return false;
}

WebRenderUserData::WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
  : mWRManager(aWRManager)
  , mFrame(aItem->Frame())
  , mDisplayItemKey(aItem->GetPerFrameKey())
  , mTable(aWRManager->GetWebRenderUserDataTable())
  , mUsed(false)
{
}

WebRenderUserData::~WebRenderUserData()
{
}

bool
WebRenderUserData::IsDataValid(WebRenderLayerManager* aManager)
{
  return aManager == mWRManager;
}

void
WebRenderUserData::RemoveFromTable()
{
  mTable->RemoveEntry(this);
}

WebRenderBridgeChild*
WebRenderUserData::WrBridge() const
{
  return mWRManager->WrBridge();
}

WebRenderImageData::WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
  : WebRenderUserData(aWRManager, aItem)
  , mOwnsKey(false)
{
}

WebRenderImageData::~WebRenderImageData()
{
  DoClearCachedResources();
}

void
WebRenderImageData::ClearImageKey()
{
  if (mKey) {
    // If we don't own the key, then the owner is responsible for discarding the
    // key when appropriate.
    if (mOwnsKey) {
      mWRManager->AddImageKeyForDiscard(mKey.value());
    }
    mKey.reset();
  }
  mOwnsKey = false;
}

void
WebRenderImageData::ClearCachedResources()
{
  DoClearCachedResources();
}

void
WebRenderImageData::DoClearCachedResources()
{
  ClearImageKey();

  if (mExternalImageId) {
    WrBridge()->DeallocExternalImageId(mExternalImageId.ref());
    mExternalImageId.reset();
  }

  if (mPipelineId) {
    WrBridge()->RemovePipelineIdForCompositable(mPipelineId.ref());
    mPipelineId.reset();
  }

  if (mImageClient) {
    mImageClient = nullptr;
  }
}

Maybe<wr::ImageKey>
WebRenderImageData::UpdateImageKey(ImageContainer* aContainer,
                                   wr::IpcResourceUpdateQueue& aResources,
                                   bool aFallback)
{
  MOZ_ASSERT(aContainer);

  if (mContainer != aContainer) {
    mContainer = aContainer;
  }

  wr::WrImageKey key;
  if (!aFallback) {
    nsresult rv = SharedSurfacesChild::Share(aContainer, mWRManager, aResources, key);
    if (NS_SUCCEEDED(rv)) {
      // Ensure that any previously owned keys are released before replacing. We
      // don't own this key, the surface itself owns it, so that it can be shared
      // across multiple elements.
      ClearImageKey();
      mKey = Some(key);
      return mKey;
    }

    if (rv != NS_ERROR_NOT_IMPLEMENTED) {
      // We should be using the shared surface but somehow sharing it failed.
      ClearImageKey();
      return Nothing();
    }
  }

  CreateImageClientIfNeeded();
  CreateExternalImageIfNeeded();

  if (!mImageClient || !mExternalImageId) {
    return Nothing();
  }

  MOZ_ASSERT(mImageClient->AsImageClientSingle());

  ImageClientSingle* imageClient = mImageClient->AsImageClientSingle();
  uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter();

  bool ret = imageClient->UpdateImage(aContainer, /* unused */0);
  if (!ret || imageClient->IsEmpty()) {
    // Delete old key
    ClearImageKey();
    return Nothing();
  }

  // Reuse old key if generation is not updated.
  if (!aFallback && oldCounter == imageClient->GetLastUpdateGenerationCounter() && mKey) {
    return mKey;
  }

  // Delete old key, we are generating a new key.
  // TODO(nical): noooo... we need to reuse image keys.
  ClearImageKey();

  key = WrBridge()->GetNextImageKey();
  aResources.AddExternalImage(mExternalImageId.value(), key);
  mKey = Some(key);
  mOwnsKey = true;

  return mKey;
}

void
WebRenderImageData::SetKey(const wr::ImageKey& aKey)
{
  MOZ_ASSERT_IF(mKey, mKey.value() != aKey);
  ClearImageKey();
  mKey = Some(aKey);
  mOwnsKey = true;
}

already_AddRefed<ImageClient>
WebRenderImageData::GetImageClient()
{
  RefPtr<ImageClient> imageClient = mImageClient;
  return imageClient.forget();
}

void
WebRenderImageData::CreateAsyncImageWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
                                                      ImageContainer* aContainer,
                                                      const StackingContextHelper& aSc,
                                                      const LayoutDeviceRect& aBounds,
                                                      const LayoutDeviceRect& aSCBounds,
                                                      const gfx::Matrix4x4& aSCTransform,
                                                      const gfx::MaybeIntSize& aScaleToSize,
                                                      const wr::ImageRendering& aFilter,
                                                      const wr::MixBlendMode& aMixBlendMode,
                                                      bool aIsBackfaceVisible)
{
  MOZ_ASSERT(aContainer->IsAsync());

  if (mPipelineId.isSome() && mContainer != aContainer) {
    // In this case, we need to remove the existed pipeline and create new one
    // because the ImageContainer is changed.
    WrBridge()->RemovePipelineIdForCompositable(mPipelineId.ref());
    mPipelineId.reset();
  }

  if (!mPipelineId) {
    // Alloc async image pipeline id.
    mPipelineId = Some(WrBridge()->GetCompositorBridgeChild()->GetNextPipelineId());
    WrBridge()->AddPipelineIdForAsyncCompositable(mPipelineId.ref(),
                                                  aContainer->GetAsyncContainerHandle());
    mContainer = aContainer;
  }
  MOZ_ASSERT(!mImageClient);
  MOZ_ASSERT(!mExternalImageId);

  // Push IFrame for async image pipeline.
  //
  // We don't push a stacking context for this async image pipeline here.
  // Instead, we do it inside the iframe that hosts the image. As a result,
  // a bunch of the calculations normally done as part of that stacking
  // context need to be done manually and pushed over to the parent side,
  // where it will be done when we build the display list for the iframe.
  // That happens in AsyncImagePipelineManager.
  wr::LayoutRect r = aSc.ToRelativeLayoutRect(aBounds);
  aBuilder.PushIFrame(r, aIsBackfaceVisible, mPipelineId.ref());

  WrBridge()->AddWebRenderParentCommand(OpUpdateAsyncImagePipeline(mPipelineId.value(),
                                                                   aSCBounds,
                                                                   aSCTransform,
                                                                   aScaleToSize,
                                                                   aFilter,
                                                                   aMixBlendMode));
}

void
WebRenderImageData::CreateImageClientIfNeeded()
{
  if (!mImageClient) {
    mImageClient = ImageClient::CreateImageClient(CompositableType::IMAGE,
                                                  WrBridge(),
                                                  TextureFlags::DEFAULT);
    if (!mImageClient) {
      return;
    }

    mImageClient->Connect();
  }
}

void
WebRenderImageData::CreateExternalImageIfNeeded()
{
  if (!mExternalImageId)  {
    mExternalImageId = Some(WrBridge()->AllocExternalImageIdForCompositable(mImageClient));
  }
}

WebRenderFallbackData::WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
  : WebRenderImageData(aWRManager, aItem)
  , mInvalid(false)
{
}

WebRenderFallbackData::~WebRenderFallbackData()
{
}

void
WebRenderFallbackData::ClearCachedResources()
{
  WebRenderImageData::ClearCachedResources();
  mBasicLayerManager = nullptr;
  mInvalid = true;
}

nsDisplayItemGeometry*
WebRenderFallbackData::GetGeometry()
{
  return mGeometry.get();
}

void
WebRenderFallbackData::SetGeometry(nsAutoPtr<nsDisplayItemGeometry> aGeometry)
{
  mGeometry = aGeometry;
}

WebRenderAnimationData::WebRenderAnimationData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
  : WebRenderUserData(aWRManager, aItem)
  , mAnimationInfo(aWRManager)
{
}

WebRenderAnimationData::~WebRenderAnimationData()
{
  // It may be the case that nsDisplayItem that created this WebRenderUserData
  // gets destroyed without getting a chance to discard the compositor animation
  // id, so we should do it as part of cleanup here.
  uint64_t animationId = mAnimationInfo.GetCompositorAnimationsId();
  // animationId might be 0 if mAnimationInfo never held any active animations.
  if (animationId) {
    mWRManager->AddCompositorAnimationsIdForDiscard(animationId);
  }
}

WebRenderCanvasData::WebRenderCanvasData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
  : WebRenderUserData(aWRManager, aItem)
{
}

WebRenderCanvasData::~WebRenderCanvasData()
{
  DoClearCachedResources();
}

void
WebRenderCanvasData::ClearCachedResources()
{
  DoClearCachedResources();
}

void
WebRenderCanvasData::DoClearCachedResources()
{
  if (mCanvasRenderer) {
    mCanvasRenderer->ClearCachedResources();
  }
}

void
WebRenderCanvasData::ClearCanvasRenderer()
{
  mCanvasRenderer = nullptr;
}

WebRenderCanvasRendererAsync*
WebRenderCanvasData::GetCanvasRenderer()
{
  return mCanvasRenderer.get();
}

WebRenderCanvasRendererAsync*
WebRenderCanvasData::CreateCanvasRenderer()
{
  mCanvasRenderer = MakeUnique<WebRenderCanvasRendererAsync>(mWRManager);
  return mCanvasRenderer.get();
}

} // namespace layers
} // namespace mozilla
