/* -*- 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/. */

#ifndef ROTATEDBUFFER_H_
#define ROTATEDBUFFER_H_

#include "gfxTypes.h"
#include <stdint.h>                     // for uint32_t
#include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
#include "mozilla/RefPtr.h"             // for RefPtr, already_AddRefed
#include "mozilla/gfx/2D.h"             // for DrawTarget, etc
#include "mozilla/gfx/MatrixFwd.h"      // for Matrix
#include "mozilla/layers/TextureClient.h" // for TextureClient
#include "mozilla/mozalloc.h"           // for operator delete
#include "nsCOMPtr.h"                   // for already_AddRefed
#include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
#include "nsRegion.h"                   // for nsIntRegion
#include "LayersTypes.h"

namespace mozilla {
namespace layers {

class PaintedLayer;
class CapturedBufferState;
class ContentClient;

// Mixin class for classes which need logic for loaning out a draw target.
// See comments on BorrowDrawTargetForQuadrantUpdate.
class BorrowDrawTarget
{
public:
  void ReturnDrawTarget(gfx::DrawTarget*& aReturned);

protected:
  // The draw target loaned by BorrowDrawTargetForQuadrantUpdate. It should not
  // be used, we just keep a reference to ensure it is kept alive and so we can
  // correctly restore state when it is returned.
  RefPtr<gfx::DrawTarget> mLoanedDrawTarget;
  gfx::Matrix mLoanedTransform;

  // This flag denotes whether or not a transform was already applied
  // to mLoanedDrawTarget and thus needs to be reset to mLoanedTransform
  // upon returning the drawtarget.
  bool mSetTransform;
};

/**
 * This is a cairo/Thebes surface, but with a literal twist. Scrolling
 * causes the layer's visible region to move. We want to keep
 * reusing the same surface if the region size hasn't changed, but we don't
 * want to keep moving the contents of the surface around in memory. So
 * we use a trick.
 * Consider just the vertical case, and suppose the buffer is H pixels
 * high and we're scrolling down by N pixels. Instead of copying the
 * buffer contents up by N pixels, we leave the buffer contents in place,
 * and paint content rows H to H+N-1 into rows 0 to N-1 of the buffer.
 * Then we can refresh the screen by painting rows N to H-1 of the buffer
 * at row 0 on the screen, and then painting rows 0 to N-1 of the buffer
 * at row H-N on the screen.
 * mBufferRotation.y would be N in this example.
 */
class RotatedBuffer : public BorrowDrawTarget
{
public:
  typedef gfxContentType ContentType;

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RotatedBuffer)

  RotatedBuffer(const gfx::IntRect& aBufferRect,
                const gfx::IntPoint& aBufferRotation)
    : mBufferRect(aBufferRect)
    , mBufferRotation(aBufferRotation)
    , mDidSelfCopy(false)
  { }
  RotatedBuffer()
    : mDidSelfCopy(false)
  { }

  /*
   * Which buffer should be drawn to/read from.
   */
  enum ContextSource {
    BUFFER_BLACK, // The normal buffer, or buffer with black background when using component alpha.
    BUFFER_WHITE, // The buffer with white background, only valid with component alpha.
    BUFFER_BOTH // The combined black/white buffers, only valid for writing operations, not reading.
  };

  /**
   * Draws the contents of this rotated buffer into the specified draw target.
   * It is the callers repsonsibility to ensure aTarget is flushed after calling
   * this method.
   */
  void DrawBufferWithRotation(gfx::DrawTarget* aTarget, ContextSource aSource,
                              float aOpacity = 1.0,
                              gfx::CompositionOp aOperator = gfx::CompositionOp::OP_OVER,
                              gfx::SourceSurface* aMask = nullptr,
                              const gfx::Matrix* aMaskTransform = nullptr) const;

  /**
   * Complete the drawing operation. The region to draw must have been
   * drawn before this is called. The contents of the buffer are drawn
   * to aTarget.
   */
  void DrawTo(PaintedLayer* aLayer,
              gfx::DrawTarget* aTarget,
              float aOpacity,
              gfx::CompositionOp aOp,
              gfx::SourceSurface* aMask,
              const gfx::Matrix* aMaskTransform);

  /**
   * Update the specified region of this rotated buffer with the contents
   * of a source rotated buffer.
   */
  void UpdateDestinationFrom(const RotatedBuffer& aSource,
                             const gfx::IntRect& aUpdateRect);

  /**
   * A draw iterator is used to keep track of which quadrant of a rotated
   * buffer and region of that quadrant is being updated.
   */
  struct DrawIterator {
    friend class RotatedBuffer;
    DrawIterator()
      : mCount(0)
    {}

    nsIntRegion mDrawRegion;

  private:
    uint32_t mCount;
  };

  /**
   * Get a draw target at the specified resolution for updating |aBounds|,
   * which must be contained within a single quadrant.
   *
   * The result should only be held temporarily by the caller (it will be kept
   * alive by this). Once used it should be returned using ReturnDrawTarget.
   * BorrowDrawTargetForQuadrantUpdate may not be called more than once without
   * first calling ReturnDrawTarget.
   *
   * ReturnDrawTarget will by default restore the transform on the draw target.
   * But it is the callers responsibility to restore the clip.
   * The caller should flush the draw target, if necessary.
   * If aSetTransform is false, the required transform will be set in aOutTransform.
   */
  gfx::DrawTarget*
  BorrowDrawTargetForQuadrantUpdate(const gfx::IntRect& aBounds,
                                    ContextSource aSource,
                                    DrawIterator* aIter,
                                    bool aSetTransform = true,
                                    gfx::Matrix* aOutTransform = nullptr);

  struct Parameters {
    Parameters(const gfx::IntRect& aBufferRect,
               const gfx::IntPoint& aBufferRotation)
      : mBufferRect(aBufferRect)
      , mBufferRotation(aBufferRotation)
      , mDidSelfCopy(false)
    {
    }

    bool IsRotated() const;
    bool RectWrapsBuffer(const gfx::IntRect& aRect) const;

    void SetUnrotated();

    gfx::IntRect  mBufferRect;
    gfx::IntPoint mBufferRotation;
    bool mDidSelfCopy;
  };

  /**
   * Returns the new buffer parameters for rotating to a
   * destination buffer rect.
   */
  Parameters AdjustedParameters(const gfx::IntRect& aDestBufferRect) const;

  /**
   * Unrotates the pixels of the rotated buffer for the specified
   * new buffer parameters.
   */
  bool UnrotateBufferTo(const Parameters& aParameters);

  void SetParameters(const Parameters& aParameters);

  /**
   * |BufferRect()| is the rect of device pixels that this
   * RotatedBuffer covers.  That is what DrawBufferWithRotation()
   * will paint when it's called.
   */
  const gfx::IntRect& BufferRect() const { return mBufferRect; }
  const gfx::IntPoint& BufferRotation() const { return mBufferRotation; }

  /**
   * Overrides the current buffer rect with the specified rect.
   * Do not do this unless you know what you're doing.
   */
  void SetBufferRect(const gfx::IntRect& aBufferRect) {
    mBufferRect = aBufferRect;
  }

  /**
   * Overrides the current buffer rotation with the specified point.
   * Do not do this unless you know what you're doing.
   */
  void SetBufferRotation(const gfx::IntPoint& aBufferRotation) {
    mBufferRotation = aBufferRotation;
  }

  /**
   * Returns whether this buffer did a self copy when adjusting to
   * a new buffer rect. This is only used externally for syncing
   * rotated buffers.
   */
  bool DidSelfCopy() const { return mDidSelfCopy; }

  /**
   * Clears the self copy flag.
   */
  void ClearDidSelfCopy() { mDidSelfCopy = false; }

  /**
   * Gets the content type for this buffer.
   */
  ContentType GetContentType() const;

  virtual bool IsLocked() = 0;
  virtual bool Lock(OpenMode aMode) = 0;
  virtual void Unlock() = 0;

  virtual bool HaveBuffer() const = 0;
  virtual bool HaveBufferOnWhite() const = 0;

  virtual gfx::SurfaceFormat GetFormat() const = 0;

  virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const = 0;

  virtual gfx::DrawTarget* GetDTBuffer() const = 0;
  virtual gfx::DrawTarget* GetDTBufferOnWhite() const = 0;

  virtual TextureClient* GetClient() const {
    return nullptr;
  }
  virtual TextureClient* GetClientOnWhite() const {
    return nullptr;
  }

  /**
   * Creates a shallow copy of the rotated buffer with the same underlying
   * texture clients and draw targets. Rotated buffers are not thread safe,
   * so a copy needs to be sent for off main thread painting.
   */
  virtual RefPtr<RotatedBuffer> ShallowCopy() const = 0;

protected:
  virtual ~RotatedBuffer() {}

  enum XSide {
    LEFT, RIGHT
  };
  enum YSide {
    TOP, BOTTOM
  };
  gfx::IntRect GetQuadrantRectangle(XSide aXSide, YSide aYSide) const;

  gfx::Rect GetSourceRectangle(XSide aXSide, YSide aYSide) const;

  /*
   * If aMask is non-null, then it is used as an alpha mask for rendering this
   * buffer. aMaskTransform must be non-null if aMask is non-null, and is used
   * to adjust the coordinate space of the mask.
   */
  void DrawBufferQuadrant(gfx::DrawTarget* aTarget, XSide aXSide, YSide aYSide,
                          ContextSource aSource,
                          float aOpacity,
                          gfx::CompositionOp aOperator,
                          gfx::SourceSurface* aMask,
                          const gfx::Matrix* aMaskTransform) const;

  /** The area of the PaintedLayer that is covered by the buffer as a whole */
  gfx::IntRect  mBufferRect;
  /**
   * The x and y rotation of the buffer. Conceptually the buffer
   * has its origin translated to mBufferRect.TopLeft() - mBufferRotation,
   * is tiled to fill the plane, and the result is clipped to mBufferRect.
   * So the pixel at mBufferRotation within the buffer is what gets painted at
   * mBufferRect.TopLeft().
   * This is "rotation" in the sense of rotating items in a linear buffer,
   * where items falling off the end of the buffer are returned to the
   * buffer at the other end, not 2D rotation!
   */
  gfx::IntPoint mBufferRotation;
  /**
   * When this is true it means that all pixels have moved inside the buffer.
   * It's not possible to sync with another buffer without a full copy.
   */
  bool          mDidSelfCopy;
};

/**
 * RemoteRotatedBuffer is a rotated buffer that is backed by texture
 * clients. Before you use this class you must successfully lock it with
 * an appropriate open mode, and then also unlock it when you're finished.
 * RemoteRotatedBuffer is used by ContentClientSingleBuffered and
 * ContentClientDoubleBuffered for the OMTC code path.
 */
class RemoteRotatedBuffer : public RotatedBuffer
{
public:
  RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
                      const gfx::IntRect& aBufferRect,
                      const gfx::IntPoint& aBufferRotation)
    : RotatedBuffer(aBufferRect, aBufferRotation)
    , mClient(aClient)
    , mClientOnWhite(aClientOnWhite)
  { }

  virtual bool IsLocked() override;
  virtual bool Lock(OpenMode aMode) override;
  virtual void Unlock() override;

  virtual bool HaveBuffer() const override { return !!mClient; }
  virtual bool HaveBufferOnWhite() const override { return !!mClientOnWhite; }

  virtual gfx::SurfaceFormat GetFormat() const override;

  virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;

  virtual gfx::DrawTarget* GetDTBuffer() const override;
  virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;

  virtual TextureClient* GetClient() const override { return mClient; }
  virtual TextureClient* GetClientOnWhite() const override { return mClientOnWhite; }

  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
    return new RemoteRotatedBuffer {
      mClient, mClientOnWhite,
      mTarget, mTargetOnWhite,
      mBufferRect, mBufferRotation
    };
  }

  void SyncWithObject(SyncObjectClient* aSyncObject);
  void Clear();

private:
  RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
                      gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
                      const gfx::IntRect& aBufferRect,
                      const gfx::IntPoint& aBufferRotation)
    : RotatedBuffer(aBufferRect, aBufferRotation)
    , mClient(aClient)
    , mClientOnWhite(aClientOnWhite)
    , mTarget(aTarget)
    , mTargetOnWhite(aTargetOnWhite)
  { }

  RefPtr<TextureClient> mClient;
  RefPtr<TextureClient> mClientOnWhite;

  RefPtr<gfx::DrawTarget> mTarget;
  RefPtr<gfx::DrawTarget> mTargetOnWhite;
};

/**
 * DrawTargetRotatedBuffer is a rotated buffer that is backed by draw targets,
 * and is used by ContentClientBasic for the on-mtc code path.
 */
class DrawTargetRotatedBuffer : public RotatedBuffer
{
public:
  DrawTargetRotatedBuffer(gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
                          const gfx::IntRect& aBufferRect,
                          const gfx::IntPoint& aBufferRotation)
    : RotatedBuffer(aBufferRect, aBufferRotation)
    , mTarget(aTarget)
    , mTargetOnWhite(aTargetOnWhite)
  { }

  virtual bool IsLocked() override { return false; }
  virtual bool Lock(OpenMode aMode) override { return true; }
  virtual void Unlock() override {}

  virtual bool HaveBuffer() const override { return !!mTarget; }
  virtual bool HaveBufferOnWhite() const override { return !!mTargetOnWhite; }

  virtual gfx::SurfaceFormat GetFormat() const override;

  virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;

  virtual gfx::DrawTarget* GetDTBuffer() const override;
  virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;

  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
    return new DrawTargetRotatedBuffer {
        mTarget, mTargetOnWhite,
        mBufferRect, mBufferRotation
      };
  }

private:
  RefPtr<gfx::DrawTarget> mTarget;
  RefPtr<gfx::DrawTarget> mTargetOnWhite;
};

/**
 * SourceRotatedBuffer is a rotated buffer that is backed by source surfaces,
 * and may only be used to draw into other buffers or be read directly.
 */
class SourceRotatedBuffer : public RotatedBuffer
{
public:
  SourceRotatedBuffer(gfx::SourceSurface* aSource, gfx::SourceSurface* aSourceOnWhite,
                      const gfx::IntRect& aBufferRect,
                      const gfx::IntPoint& aBufferRotation)
    : RotatedBuffer(aBufferRect, aBufferRotation)
    , mSource(aSource)
    , mSourceOnWhite(aSourceOnWhite)
  { }

  virtual bool IsLocked() override { return false; }
  virtual bool Lock(OpenMode aMode) override { return false; }
  virtual void Unlock() override {}

  virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;

  virtual gfx::SurfaceFormat GetFormat() const override;

  virtual bool HaveBuffer() const override { return !!mSource; }
  virtual bool HaveBufferOnWhite() const override { return !!mSourceOnWhite; }

  virtual gfx::DrawTarget* GetDTBuffer() const override { return nullptr; }
  virtual gfx::DrawTarget* GetDTBufferOnWhite() const override { return nullptr; }

  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
    return nullptr;
  }

private:
  RefPtr<gfx::SourceSurface> mSource;
  RefPtr<gfx::SourceSurface> mSourceOnWhite;
};

} // namespace layers
} // namespace mozilla

#endif /* ROTATEDBUFFER_H_ */
