/* -*- Mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40; -*- */
/* 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 GLTEXTUREIMAGE_H_
#define GLTEXTUREIMAGE_H_

#include "nsRegion.h"
#include "nsTArray.h"
#include "gfxTypes.h"
#include "GLContextTypes.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/RefPtr.h"

class gfxASurface;

namespace mozilla {
namespace gfx {
class DataSourceSurface;
class DrawTarget;
} // namespace gfx
} // namespace mozilla

namespace mozilla {
namespace gl {
class GLContext;

/**
 * A TextureImage provides a mechanism to synchronize data from a
 * surface to a texture in the server.  TextureImages are associated
 * with one and only one GLContext.
 */
class TextureImage
{
    NS_INLINE_DECL_REFCOUNTING(TextureImage)
public:
    enum TextureState
    {
      Created, // Texture created, but has not had glTexImage called to initialize it.
      Allocated,  // Texture memory exists, but contents are invalid.
      Valid  // Texture fully ready to use.
    };

    enum Flags {
        NoFlags          = 0x0,
        UseNearestFilter = 0x1,
        OriginBottomLeft = 0x2,
        DisallowBigImage = 0x4
    };

    typedef gfxContentType ContentType;
    typedef gfxImageFormat ImageFormat;

    static already_AddRefed<TextureImage> Create(
                       GLContext* gl,
                       const gfx::IntSize& aSize,
                       TextureImage::ContentType aContentType,
                       GLenum aWrapMode,
                       TextureImage::Flags aFlags = TextureImage::NoFlags);

    /**
     * The Image may contain several textures for different regions (tiles).
     * These functions iterate over each sub texture image tile.
     */
    virtual void BeginBigImageIteration() {
    }

    virtual bool NextTile() {
        return false;
    }

    // Function prototype for a tile iteration callback. Returning false will
    // cause iteration to be interrupted (i.e. the corresponding NextTile call
    // will return false).
    typedef bool (* BigImageIterationCallback)(TextureImage* aImage,
                                           int aTileNumber,
                                           void* aCallbackData);

    // Sets a callback to be called every time NextTile is called.
    virtual void SetIterationCallback(BigImageIterationCallback aCallback,
                                      void* aCallbackData) {
    }

    virtual gfx::IntRect GetTileRect();

    virtual GLuint GetTextureID() = 0;

    virtual uint32_t GetTileCount() {
        return 1;
    }

    /**
     * Set this TextureImage's size, and ensure a texture has been
     * allocated.
     * After a resize, the contents are undefined.
     */
    virtual void Resize(const gfx::IntSize& aSize) = 0;

    /**
     * Mark this texture as having valid contents. Call this after modifying
     * the texture contents externally.
     */
    virtual void MarkValid() {}

    /**
     * aSurf - the source surface to update from
     * aRegion - the region in this image to update
     * aFrom - offset in the source to update from
     */
    virtual bool DirectUpdate(gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, const gfx::IntPoint& aFrom = gfx::IntPoint(0,0)) = 0;
    bool UpdateFromDataSource(gfx::DataSourceSurface* aSurf,
                              const nsIntRegion* aDstRegion = nullptr,
                              const gfx::IntPoint* aSrcOffset = nullptr);

    virtual void BindTexture(GLenum aTextureUnit) = 0;

    /**
     * Returns the image format of the texture. Only valid after
     * DirectUpdate has been called.
     */
    virtual gfx::SurfaceFormat GetTextureFormat() {
        return mTextureFormat;
    }

    /** Can be called safely at any time. */

    /**
     * If this TextureImage has a permanent gfxASurface backing,
     * return it.  Otherwise return nullptr.
     */
    virtual already_AddRefed<gfxASurface> GetBackingSurface()
    { return nullptr; }


    gfx::IntSize GetSize() const;
    ContentType GetContentType() const { return mContentType; }
    GLenum GetWrapMode() const { return mWrapMode; }

    void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) {
      mSamplingFilter = aSamplingFilter;
    }

protected:
    friend class GLContext;

    void UpdateUploadSize(size_t amount);

    /**
     * After the ctor, the TextureImage is invalid.  Implementations
     * must allocate resources successfully before returning the new
     * TextureImage from GLContext::CreateTextureImage().  That is,
     * clients must not be given partially-constructed TextureImages.
     */
    TextureImage(const gfx::IntSize& aSize,
                 GLenum aWrapMode, ContentType aContentType,
                 Flags aFlags = NoFlags);

    // Protected destructor, to discourage deletion outside of Release():
    virtual ~TextureImage() {
        UpdateUploadSize(0);
    }

    virtual gfx::IntRect GetSrcTileRect();

    gfx::IntSize mSize;
    GLenum mWrapMode;
    ContentType mContentType;
    gfx::SurfaceFormat mTextureFormat;
    gfx::SamplingFilter mSamplingFilter;
    Flags mFlags;
    size_t mUploadSize;
};

/**
 * BasicTextureImage is the baseline TextureImage implementation ---
 * it updates its texture by allocating a scratch buffer for the
 * client to draw into, then using glTexSubImage2D() to upload the new
 * pixels.  Platforms must provide the code to create a new surface
 * into which the updated pixels will be drawn, and the code to
 * convert the update surface's pixels into an image on which we can
 * glTexSubImage2D().
 */
class BasicTextureImage
    : public TextureImage
{
public:
    virtual ~BasicTextureImage();

    BasicTextureImage(GLuint aTexture,
                      const gfx::IntSize& aSize,
                      GLenum aWrapMode,
                      ContentType aContentType,
                      GLContext* aContext,
                      TextureImage::Flags aFlags = TextureImage::NoFlags);

    virtual void BindTexture(GLenum aTextureUnit);

    virtual bool DirectUpdate(gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, const gfx::IntPoint& aFrom = gfx::IntPoint(0,0));
    virtual GLuint GetTextureID() { return mTexture; }

    virtual void MarkValid() { mTextureState = Valid; }

    virtual void Resize(const gfx::IntSize& aSize);

protected:
    GLuint mTexture;
    TextureState mTextureState;
    RefPtr<GLContext> mGLContext;
};

/**
 * A container class that complements many sub TextureImages into a big TextureImage.
 * Aims to behave just like the real thing.
 */

class TiledTextureImage final
    : public TextureImage
{
public:
    TiledTextureImage(GLContext* aGL,
                      gfx::IntSize aSize,
                      TextureImage::ContentType,
                      TextureImage::Flags aFlags = TextureImage::NoFlags,
                      TextureImage::ImageFormat aImageFormat = gfx::SurfaceFormat::UNKNOWN);
    ~TiledTextureImage();
    void DumpDiv();
    virtual void Resize(const gfx::IntSize& aSize);
    virtual uint32_t GetTileCount();
    virtual void BeginBigImageIteration();
    virtual bool NextTile();
    virtual void SetIterationCallback(BigImageIterationCallback aCallback,
                                      void* aCallbackData);
    virtual gfx::IntRect GetTileRect();
    virtual GLuint GetTextureID() {
        return mImages[mCurrentImage]->GetTextureID();
    }
    virtual bool DirectUpdate(gfx::DataSourceSurface* aSurf, const nsIntRegion& aRegion, const gfx::IntPoint& aFrom = gfx::IntPoint(0,0));
    virtual void BindTexture(GLenum);

protected:
    virtual gfx::IntRect GetSrcTileRect();

    unsigned int mCurrentImage;
    BigImageIterationCallback mIterationCallback;
    void* mIterationCallbackData;
    nsTArray< RefPtr<TextureImage> > mImages;
    unsigned int mTileSize;
    unsigned int mRows, mColumns;
    GLContext* mGL;
    TextureState mTextureState;
    TextureImage::ImageFormat mImageFormat;
};

/**
 * Creates a TextureImage of the basic implementation, can be useful in cases
 * where we know we don't want to use platform-specific TextureImage.
 * In doubt, use GLContext::CreateTextureImage instead.
 */
already_AddRefed<TextureImage>
CreateBasicTextureImage(GLContext* aGL,
                        const gfx::IntSize& aSize,
                        TextureImage::ContentType aContentType,
                        GLenum aWrapMode,
                        TextureImage::Flags aFlags);

/**
 * Creates a TiledTextureImage backed by platform-specific or basic TextureImages.
 * In doubt, use GLContext::CreateTextureImage instead.
 */
already_AddRefed<TextureImage>
CreateTiledTextureImage(GLContext* aGL,
                        const gfx::IntSize& aSize,
                        TextureImage::ContentType aContentType,
                        TextureImage::Flags aFlags,
                        TextureImage::ImageFormat aImageFormat);

/**
  * Return a valid, allocated TextureImage of |aSize| with
  * |aContentType|.  If |aContentType| is COLOR, |aImageFormat| can be used
  * to hint at the preferred RGB format, however it is not necessarily
  * respected.  The TextureImage's texture is configured to use
  * |aWrapMode| (usually GL_CLAMP_TO_EDGE or GL_REPEAT) and by
  * default, GL_LINEAR filtering.  Specify
  * |aFlags=UseNearestFilter| for GL_NEAREST filtering. Specify
  * |aFlags=OriginBottomLeft| if the image is origin-bottom-left, instead of the
  * default origin-top-left. Return
  * nullptr if creating the TextureImage fails.
  *
  * The returned TextureImage may only be used with this GLContext.
  * Attempting to use the returned TextureImage after this
  * GLContext is destroyed will result in undefined (and likely
  * crashy) behavior.
  */
already_AddRefed<TextureImage>
CreateTextureImage(GLContext* gl,
                   const gfx::IntSize& aSize,
                   TextureImage::ContentType aContentType,
                   GLenum aWrapMode,
                   TextureImage::Flags aFlags = TextureImage::NoFlags,
                   TextureImage::ImageFormat aImageFormat = gfx::SurfaceFormat::UNKNOWN);

} // namespace gl
} // namespace mozilla

#endif /* GLTEXTUREIMAGE_H_ */