/* -*- Mode: C++; tab-width: 20; 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 "mozilla/layers/ContentHost.h"
#include "LayersLogging.h"              // for AppendToString
#include "gfx2DGlue.h"                  // for ContentForFormat
#include "mozilla/gfx/Point.h"          // for IntSize
#include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
#include "mozilla/gfx/BaseRect.h"       // for BaseRect
#include "mozilla/layers/Compositor.h"  // for Compositor
#include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
#include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
#include "nsAString.h"
#include "nsPrintfCString.h"            // for nsPrintfCString
#include "nsString.h"                   // for nsAutoCString
#include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL

namespace mozilla {
using namespace gfx;

namespace layers {

ContentHostBase::ContentHostBase(const TextureInfo& aTextureInfo)
  : ContentHost(aTextureInfo)
  , mInitialised(false)
{}

ContentHostBase::~ContentHostBase()
{
}

void
ContentHostTexture::Composite(Compositor* aCompositor,
                              LayerComposite* aLayer,
                              EffectChain& aEffectChain,
                              float aOpacity,
                              const gfx::Matrix4x4& aTransform,
                              const SamplingFilter aSamplingFilter,
                              const IntRect& aClipRect,
                              const nsIntRegion* aVisibleRegion,
                              const Maybe<gfx::Polygon>& aGeometry)
{
  NS_ASSERTION(aVisibleRegion, "Requires a visible region");

  AutoLockCompositableHost lock(this);
  if (lock.Failed()) {
    return;
  }

  if (!mTextureHost->BindTextureSource(mTextureSource)) {
    return;
  }
  MOZ_ASSERT(mTextureSource.get());

  if (!mTextureHostOnWhite) {
    mTextureSourceOnWhite = nullptr;
  }
  if (mTextureHostOnWhite && !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) {
    return;
  }

  RefPtr<TexturedEffect> effect = CreateTexturedEffect(mTextureSource.get(),
                                                       mTextureSourceOnWhite.get(),
                                                       aSamplingFilter, true);
  if (!effect) {
    return;
  }

  aEffectChain.mPrimaryEffect = effect;

  nsIntRegion tmpRegion;
  const nsIntRegion* renderRegion;
#ifndef MOZ_IGNORE_PAINT_WILL_RESAMPLE
  if (PaintWillResample()) {
    // If we're resampling, then the texture image will contain exactly the
    // entire visible region's bounds, and we should draw it all in one quad
    // to avoid unexpected aliasing.
    tmpRegion = aVisibleRegion->GetBounds();
    renderRegion = &tmpRegion;
  } else {
    renderRegion = aVisibleRegion;
  }
#else
  renderRegion = aVisibleRegion;
#endif

  nsIntRegion region(*renderRegion);
  nsIntPoint origin = GetOriginOffset();
  // translate into TexImage space, buffer origin might not be at texture (0,0)
  region.MoveBy(-origin);

  // Figure out the intersecting draw region
  gfx::IntSize texSize = mTextureSource->GetSize();
  IntRect textureRect = IntRect(0, 0, texSize.width, texSize.height);
  textureRect.MoveBy(region.GetBounds().TopLeft());
  nsIntRegion subregion;
  subregion.And(region, textureRect);
  if (subregion.IsEmpty()) {
    // Region is empty, nothing to draw
    return;
  }

  nsIntRegion screenRects;
  nsIntRegion regionRects;

  // Collect texture/screen coordinates for drawing
  for (auto iter = subregion.RectIter(); !iter.Done(); iter.Next()) {
    IntRect regionRect = iter.Get();
    IntRect screenRect = iter.Get();
    screenRect.MoveBy(origin);

    screenRects.Or(screenRects, screenRect);
    regionRects.Or(regionRects, regionRect);
  }

  BigImageIterator* bigImgIter = mTextureSource->AsBigImageIterator();
  BigImageIterator* iterOnWhite = nullptr;
  if (bigImgIter) {
    bigImgIter->BeginBigImageIteration();
  }

  if (mTextureSourceOnWhite) {
    iterOnWhite = mTextureSourceOnWhite->AsBigImageIterator();
    MOZ_ASSERT(!bigImgIter || bigImgIter->GetTileCount() == iterOnWhite->GetTileCount(),
               "Tile count mismatch on component alpha texture");
    if (iterOnWhite) {
      iterOnWhite->BeginBigImageIteration();
    }
  }

  bool usingTiles = (bigImgIter && bigImgIter->GetTileCount() > 1);
  do {
    if (iterOnWhite && bigImgIter) {
      MOZ_ASSERT(iterOnWhite->GetTileRect() == bigImgIter->GetTileRect(),
                 "component alpha textures should be the same size.");
    }

    IntRect texRect = bigImgIter ? bigImgIter->GetTileRect()
                                 : IntRect(0, 0,
                                             texSize.width,
                                             texSize.height);

    // Draw texture. If we're using tiles, we do repeating manually, as texture
    // repeat would cause each individual tile to repeat instead of the
    // compound texture as a whole. This involves drawing at most 4 sections,
    // 2 for each axis that has texture repeat.
    for (int y = 0; y < (usingTiles ? 2 : 1); y++) {
      for (int x = 0; x < (usingTiles ? 2 : 1); x++) {
        IntRect currentTileRect(texRect);
        currentTileRect.MoveBy(x * texSize.width, y * texSize.height);

        for (auto screenIter = screenRects.RectIter(),
                  regionIter = regionRects.RectIter();
             !screenIter.Done() && !regionIter.Done();
             screenIter.Next(), regionIter.Next()) {
          const IntRect& screenRect = screenIter.Get();
          const IntRect& regionRect = regionIter.Get();
          IntRect tileScreenRect(screenRect);
          IntRect tileRegionRect(regionRect);

          // When we're using tiles, find the intersection between the tile
          // rect and this region rect. Tiling is then handled by the
          // outer for-loops and modifying the tile rect.
          if (usingTiles) {
            tileScreenRect.MoveBy(-origin);
            tileScreenRect = tileScreenRect.Intersect(currentTileRect);
            tileScreenRect.MoveBy(origin);

            if (tileScreenRect.IsEmpty())
              continue;

            tileRegionRect = regionRect.Intersect(currentTileRect);
            tileRegionRect.MoveBy(-currentTileRect.TopLeft());
          }
          gfx::Rect rect(tileScreenRect.x, tileScreenRect.y,
                         tileScreenRect.Width(), tileScreenRect.Height());

          effect->mTextureCoords = Rect(Float(tileRegionRect.x) / texRect.Width(),
                                        Float(tileRegionRect.y) / texRect.Height(),
                                        Float(tileRegionRect.Width()) / texRect.Width(),
                                        Float(tileRegionRect.Height()) / texRect.Height());

          aCompositor->DrawGeometry(rect, aClipRect, aEffectChain,
                                    aOpacity, aTransform, aGeometry);

          if (usingTiles) {
            DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT | DiagnosticFlags::BIGIMAGE;
            if (iterOnWhite) {
              diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
            }
            aCompositor->DrawDiagnostics(diagnostics, rect, aClipRect,
                                         aTransform, mFlashCounter);
          }
        }
      }
    }

    if (iterOnWhite) {
      iterOnWhite->NextTile();
    }
  } while (usingTiles && bigImgIter->NextTile());

  if (bigImgIter) {
    bigImgIter->EndBigImageIteration();
  }
  if (iterOnWhite) {
    iterOnWhite->EndBigImageIteration();
  }

  DiagnosticFlags diagnostics = DiagnosticFlags::CONTENT;
  if (iterOnWhite) {
    diagnostics |= DiagnosticFlags::COMPONENT_ALPHA;
  }
  aCompositor->DrawDiagnostics(diagnostics, nsIntRegion(mBufferRect), aClipRect,
                               aTransform, mFlashCounter);
}

RefPtr<TextureSource>
ContentHostTexture::AcquireTextureSource()
{
  if (!mTextureHost || !mTextureHost->AcquireTextureSource(mTextureSource)) {
    return nullptr;
  }
  return mTextureSource.get();
}

RefPtr<TextureSource>
ContentHostTexture::AcquireTextureSourceOnWhite()
{
  if (!mTextureHostOnWhite ||
      !mTextureHostOnWhite->AcquireTextureSource(mTextureSourceOnWhite))
  {
    return nullptr;
  }
  return mTextureSourceOnWhite.get();
}

void
ContentHostTexture::UseTextureHost(const nsTArray<TimedTexture>& aTextures)
{
  ContentHostBase::UseTextureHost(aTextures);
  MOZ_ASSERT(aTextures.Length() == 1);
  const TimedTexture& t = aTextures[0];
  MOZ_ASSERT(t.mPictureRect.IsEqualInterior(
      nsIntRect(nsIntPoint(0, 0), nsIntSize(t.mTexture->GetSize()))),
      "Only default picture rect supported");

  if (t.mTexture != mTextureHost) {
    mReceivedNewHost = true;
  }

  mTextureHost = t.mTexture;
  mTextureHostOnWhite = nullptr;
  mTextureSourceOnWhite = nullptr;
  if (mTextureHost) {
    mTextureHost->PrepareTextureSource(mTextureSource);
  }
}

void
ContentHostTexture::UseComponentAlphaTextures(TextureHost* aTextureOnBlack,
                                              TextureHost* aTextureOnWhite)
{
  ContentHostBase::UseComponentAlphaTextures(aTextureOnBlack, aTextureOnWhite);
  mTextureHost = aTextureOnBlack;
  mTextureHostOnWhite = aTextureOnWhite;
  if (mTextureHost) {
    mTextureHost->PrepareTextureSource(mTextureSource);
  }
  if (mTextureHostOnWhite) {
    mTextureHostOnWhite->PrepareTextureSource(mTextureSourceOnWhite);
  }
}

void
ContentHostTexture::SetTextureSourceProvider(TextureSourceProvider* aProvider)
{
  ContentHostBase::SetTextureSourceProvider(aProvider);
  if (mTextureHost) {
    mTextureHost->SetTextureSourceProvider(aProvider);
  }
  if (mTextureHostOnWhite) {
    mTextureHostOnWhite->SetTextureSourceProvider(aProvider);
  }
}

void
ContentHostTexture::Dump(std::stringstream& aStream,
                         const char* aPrefix,
                         bool aDumpHtml)
{
#ifdef MOZ_DUMP_PAINTING
  if (aDumpHtml) {
    aStream << "<ul>";
  }
  if (mTextureHost) {
    aStream << aPrefix;
    if (aDumpHtml) {
      aStream << "<li> <a href=";
    } else {
      aStream << "Front buffer: ";
    }
    DumpTextureHost(aStream, mTextureHost);
    if (aDumpHtml) {
      aStream << "> Front buffer </a></li> ";
    } else {
      aStream << "\n";
    }
  }
  if (mTextureHostOnWhite) {
    aStream << aPrefix;
    if (aDumpHtml) {
      aStream << "<li> <a href=";
    } else {
      aStream << "Front buffer on white: ";
    }
    DumpTextureHost(aStream, mTextureHostOnWhite);
    if (aDumpHtml) {
      aStream << "> Front buffer on white </a> </li> ";
    } else {
      aStream << "\n";
    }
  }
  if (aDumpHtml) {
    aStream << "</ul>";
  }
#endif
}

static inline void
AddWrappedRegion(const nsIntRegion& aInput, nsIntRegion& aOutput,
                 const IntSize& aSize, const nsIntPoint& aShift)
{
  nsIntRegion tempRegion;
  tempRegion.And(IntRect(aShift, aSize), aInput);
  tempRegion.MoveBy(-aShift);
  aOutput.Or(aOutput, tempRegion);
}

bool
ContentHostSingleBuffered::UpdateThebes(const ThebesBufferData& aData,
                                        const nsIntRegion& aUpdated,
                                        const nsIntRegion& aOldValidRegionBack)
{
  if (!mTextureHost) {
    mInitialised = false;
    return true; // FIXME should we return false? Returning true for now
  }              // to preserve existing behavior of NOT causing IPC errors.

  // updated is in screen coordinates. Convert it to buffer coordinates.
  nsIntRegion destRegion(aUpdated);

  if (mReceivedNewHost) {
    destRegion.Or(destRegion, aOldValidRegionBack);
    mReceivedNewHost = false;
  }
  destRegion.MoveBy(-aData.rect().TopLeft());

  if (!aData.rect().Contains(aUpdated.GetBounds()) ||
      aData.rotation().x > aData.rect().Width() ||
      aData.rotation().y > aData.rect().Height()) {
    NS_ERROR("Invalid update data");
    return false;
  }

  // destRegion is now in logical coordinates relative to the buffer, but we
  // need to account for rotation. We do that by moving the region to the
  // rotation offset and then wrapping any pixels that extend off the
  // bottom/right edges.

  // Shift to the rotation point
  destRegion.MoveBy(aData.rotation());

  IntSize bufferSize = aData.rect().Size();

  // Select only the pixels that are still within the buffer.
  nsIntRegion finalRegion;
  finalRegion.And(IntRect(IntPoint(), bufferSize), destRegion);

  // For each of the overlap areas (right, bottom-right, bottom), select those
  // pixels and wrap them around to the opposite edge of the buffer rect.
  AddWrappedRegion(destRegion, finalRegion, bufferSize, nsIntPoint(aData.rect().Width(), 0));
  AddWrappedRegion(destRegion, finalRegion, bufferSize, nsIntPoint(aData.rect().Width(), aData.rect().Height()));
  AddWrappedRegion(destRegion, finalRegion, bufferSize, nsIntPoint(0, aData.rect().Height()));

  MOZ_ASSERT(IntRect(0, 0, aData.rect().Width(), aData.rect().Height()).Contains(finalRegion.GetBounds()));

  mTextureHost->Updated(&finalRegion);
  if (mTextureHostOnWhite) {
    mTextureHostOnWhite->Updated(&finalRegion);
  }
  mInitialised = true;

  mBufferRect = aData.rect();
  mBufferRotation = aData.rotation();

  return true;
}

bool
ContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData,
                                        const nsIntRegion& aUpdated,
                                        const nsIntRegion& aOldValidRegionBack)
{
  if (!mTextureHost) {
    mInitialised = false;
    return true;
  }

  // We don't need to calculate an update region because we assume that if we
  // are using double buffering then we have render-to-texture and thus no
  // upload to do.
  mTextureHost->Updated();
  if (mTextureHostOnWhite) {
    mTextureHostOnWhite->Updated();
  }
  mInitialised = true;

  mBufferRect = aData.rect();
  mBufferRotation = aData.rotation();

  // Save the current valid region of our front buffer, because if
  // we're double buffering, it's going to be the valid region for the
  // next back buffer sent back to the renderer.
  //
  // NB: we rely here on the fact that mValidRegion is initialized to
  // empty, and that the first time Swap() is called we don't have a
  // valid front buffer that we're going to return to content.
  mValidRegionForNextBackBuffer = aOldValidRegionBack;

  return true;
}

void
ContentHostTexture::PrintInfo(std::stringstream& aStream, const char* aPrefix)
{
  aStream << aPrefix;
  aStream << nsPrintfCString("ContentHost (0x%p)", this).get();

  AppendToString(aStream, mBufferRect, " [buffer-rect=", "]");
  AppendToString(aStream, mBufferRotation, " [buffer-rotation=", "]");
  if (PaintWillResample()) {
    aStream << " [paint-will-resample]";
  }

  if (mTextureHost) {
    nsAutoCString pfx(aPrefix);
    pfx += "  ";

    aStream << "\n";
    mTextureHost->PrintInfo(aStream, pfx.get());
  }
}


already_AddRefed<TexturedEffect>
ContentHostTexture::GenEffect(const gfx::SamplingFilter aSamplingFilter)
{
  if (!mTextureHost) {
    return nullptr;
  }
  if (!mTextureHost->BindTextureSource(mTextureSource)) {
    return nullptr;
  }
  if (!mTextureHostOnWhite) {
    mTextureSourceOnWhite = nullptr;
  }
  if (mTextureHostOnWhite && !mTextureHostOnWhite->BindTextureSource(mTextureSourceOnWhite)) {
    return nullptr;
  }
  return CreateTexturedEffect(mTextureSource.get(),
                              mTextureSourceOnWhite.get(),
                              aSamplingFilter, true);
}

already_AddRefed<gfx::DataSourceSurface>
ContentHostTexture::GetAsSurface()
{
  if (!mTextureHost) {
    return nullptr;
  }

  return mTextureHost->GetAsSurface();
}


} // namespace layers
} // namespace mozilla
