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

#include <algorithm>
#include <map>
#include <set>
#include <vector>

#include "mozilla/Assertions.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/LinkedList.h"
#include "nsWrapperCache.h"

#include "WebGLFramebufferAttachable.h"
#include "WebGLObjectModel.h"
#include "WebGLStrongTypes.h"
#include "WebGLTypes.h"

namespace mozilla {
class ErrorResult;
class WebGLContext;
struct FloatOrInt;
struct TexImageSource;

namespace dom {
class Element;
class HTMLVideoElement;
class ImageData;
class ArrayBufferViewOrSharedArrayBufferView;
} // namespace dom

namespace layers {
class Image;
} // namespace layers

namespace webgl {
struct DriverUnpackInfo;
struct FormatUsageInfo;
struct PackingInfo;
class TexUnpackBlob;
} // namespace webgl


bool
DoesTargetMatchDimensions(WebGLContext* webgl, TexImageTarget target, uint8_t dims,
                          const char* funcName);


// NOTE: When this class is switched to new DOM bindings, update the (then-slow)
// WrapObject calls in GetParameter and GetFramebufferAttachmentParameter.
class WebGLTexture final
    : public nsWrapperCache
    , public WebGLRefCountedObject<WebGLTexture>
    , public LinkedListElement<WebGLTexture>
{
    // Friends
    friend class WebGLContext;
    friend class WebGLFramebuffer;

    ////////////////////////////////////
    // Members
public:
    const GLuint mGLName;

protected:
    TexTarget mTarget;

    static const uint8_t kMaxFaceCount = 6;
    uint8_t mFaceCount; // 6 for cube maps, 1 otherwise.

    TexMinFilter mMinFilter;
    TexMagFilter mMagFilter;
    TexWrap mWrapS, mWrapT;

    bool mImmutable; // Set by texStorage*
    uint8_t mImmutableLevelCount;

    uint32_t mBaseMipmapLevel; // Set by texParameter (defaults to 0)
    uint32_t mMaxMipmapLevel;  // Set by texParameter (defaults to 1000)
    // You almost certainly don't want to query mMaxMipmapLevel.
    // You almost certainly want MaxEffectiveMipmapLevel().

    GLenum mTexCompareMode;

    // Resolvable optimizations:
    bool mIsResolved;
    FakeBlackType mResolved_FakeBlack;
    const GLint* mResolved_Swizzle; // nullptr means 'default swizzle'.

public:
    class ImageInfo;

    // numLevels = log2(size) + 1
    // numLevels(16k) = log2(16k) + 1 = 14 + 1 = 15
    // numLevels(1M) = log2(1M) + 1 = 19.9 + 1 ~= 21
    // Or we can just max this out to 31, which is the number of unsigned bits in GLsizei.
    static const uint8_t kMaxLevelCount = 31;

    // And in turn, it needs these forwards:
protected:
    // We need to forward these.
    void SetImageInfo(ImageInfo* target, const ImageInfo& newInfo);
    void SetImageInfosAtLevel(uint32_t level, const ImageInfo& newInfo);

public:
    // We store information about the various images that are part of this
    // texture. (cubemap faces, mipmap levels)
    class ImageInfo
    {
        friend void WebGLTexture::SetImageInfo(ImageInfo* target,
                                               const ImageInfo& newInfo);
        friend void WebGLTexture::SetImageInfosAtLevel(uint32_t level,
                                                       const ImageInfo& newInfo);

    public:
        static const ImageInfo kUndefined;

        // This is the "effective internal format" of the texture, an official
        // OpenGL spec concept, see OpenGL ES 3.0.3 spec, section 3.8.3, page
        // 126 and below.
        const webgl::FormatUsageInfo* const mFormat;

        const uint32_t mWidth;
        const uint32_t mHeight;
        const uint32_t mDepth;

    protected:
        bool mIsDataInitialized;

        std::set<WebGLFBAttachPoint*> mAttachPoints;

    public:
        ImageInfo()
            : mFormat(LOCAL_GL_NONE)
            , mWidth(0)
            , mHeight(0)
            , mDepth(0)
            , mIsDataInitialized(false)
        { }

        ImageInfo(const webgl::FormatUsageInfo* format, uint32_t width, uint32_t height,
                  uint32_t depth, bool isDataInitialized)
            : mFormat(format)
            , mWidth(width)
            , mHeight(height)
            , mDepth(depth)
            , mIsDataInitialized(isDataInitialized)
        {
            MOZ_ASSERT(mFormat);
        }

        void Clear();

        ~ImageInfo() {
            if (!IsDefined())
                Clear();
        }

    protected:
        ImageInfo& operator =(const ImageInfo& a);

    public:
        uint32_t PossibleMipmapLevels() const {
            // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
            const uint32_t largest = std::max(std::max(mWidth, mHeight), mDepth);
            MOZ_ASSERT(largest != 0);
            return FloorLog2Size(largest) + 1;
        }

        bool IsPowerOfTwo() const;

        void AddAttachPoint(WebGLFBAttachPoint* attachPoint);
        void RemoveAttachPoint(WebGLFBAttachPoint* attachPoint);
        void OnRespecify() const;

        size_t MemoryUsage() const;

        bool IsDefined() const {
            if (mFormat == LOCAL_GL_NONE) {
                MOZ_ASSERT(!mWidth && !mHeight && !mDepth);
                return false;
            }

            return true;
        }

        bool IsDataInitialized() const { return mIsDataInitialized; }

        void SetIsDataInitialized(bool isDataInitialized, WebGLTexture* tex);
    };

    ImageInfo mImageInfoArr[kMaxLevelCount * kMaxFaceCount];

    ////////////////////////////////////
public:
    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTexture)
    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTexture)

    WebGLTexture(WebGLContext* webgl, GLuint tex);

    void Delete();

    bool HasEverBeenBound() const { return mTarget != LOCAL_GL_NONE; }
    TexTarget Target() const { return mTarget; }

    WebGLContext* GetParentObject() const {
        return mContext;
    }

    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;

protected:
    ~WebGLTexture() {
        DeleteOnce();
    }

public:
    ////////////////////////////////////
    // GL calls
    bool BindTexture(TexTarget texTarget);
    void GenerateMipmap(TexTarget texTarget);
    JS::Value GetTexParameter(TexTarget texTarget, GLenum pname);
    bool IsTexture() const;
    void TexParameter(TexTarget texTarget, GLenum pname, const FloatOrInt& param);

    ////////////////////////////////////
    // WebGLTextureUpload.cpp

protected:
    void TexOrSubImageBlob(bool isSubImage, const char* funcName, TexImageTarget target,
                           GLint level, GLenum internalFormat, GLint xOffset,
                           GLint yOffset, GLint zOffset,
                           const webgl::PackingInfo& pi,
                           const webgl::TexUnpackBlob* blob);

    bool ValidateTexImageSpecification(const char* funcName, TexImageTarget target,
                                       GLint level, uint32_t width, uint32_t height,
                                       uint32_t depth,
                                       WebGLTexture::ImageInfo** const out_imageInfo);
    bool ValidateTexImageSelection(const char* funcName, TexImageTarget target,
                                   GLint level, GLint xOffset, GLint yOffset,
                                   GLint zOffset, uint32_t width, uint32_t height,
                                   uint32_t depth,
                                   WebGLTexture::ImageInfo** const out_imageInfo);
    bool ValidateCopyTexImageForFeedback(const char* funcName, uint32_t level, GLint layer = 0) const;

    bool ValidateUnpack(const char* funcName, const webgl::TexUnpackBlob* blob,
                        bool isFunc3D, const webgl::PackingInfo& srcPI) const;
public:
    void TexStorage(const char* funcName, TexTarget target, GLsizei levels,
                    GLenum sizedFormat, GLsizei width, GLsizei height, GLsizei depth);
    void TexImage(const char* funcName, TexImageTarget target, GLint level,
                  GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth,
                  GLint border, const webgl::PackingInfo& pi, const TexImageSource& src);
    void TexSubImage(const char* funcName, TexImageTarget target, GLint level,
                     GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                     GLsizei height, GLsizei depth, const webgl::PackingInfo& pi,
                     const TexImageSource& src);
protected:
    void TexImage(const char* funcName, TexImageTarget target, GLint level,
                  GLenum internalFormat, const webgl::PackingInfo& pi,
                  const webgl::TexUnpackBlob* blob);
    void TexSubImage(const char* funcName, TexImageTarget target, GLint level,
                     GLint xOffset, GLint yOffset, GLint zOffset,
                     const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob);
public:
    void CompressedTexImage(const char* funcName, TexImageTarget target, GLint level,
                            GLenum internalFormat, GLsizei width, GLsizei height,
                            GLsizei depth, GLint border, const TexImageSource& src);
    void CompressedTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                               GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                               GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
                               const TexImageSource& src);

    void CopyTexImage2D(TexImageTarget target, GLint level, GLenum internalFormat,
                        GLint x, GLint y, GLsizei width, GLsizei height, GLint border);
    void CopyTexSubImage(const char* funcName, TexImageTarget target, GLint level,
                         GLint xOffset, GLint yOffset, GLint zOffset, GLint x, GLint y,
                         GLsizei width, GLsizei height);

    ////////////////////////////////////

protected:
    void ClampLevelBaseAndMax();

    void PopulateMipChain(uint32_t baseLevel, uint32_t maxLevel);

    bool MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const;

    static uint8_t FaceForTarget(TexImageTarget texImageTarget) {
        GLenum rawTexImageTarget = texImageTarget.get();
        switch (rawTexImageTarget) {
        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
            return rawTexImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;

        default:
            return 0;
        }
    }

    ImageInfo& ImageInfoAtFace(uint8_t face, uint32_t level) {
        MOZ_ASSERT(face < mFaceCount);
        MOZ_ASSERT(level < kMaxLevelCount);
        size_t pos = (level * mFaceCount) + face;
        return mImageInfoArr[pos];
    }

    const ImageInfo& ImageInfoAtFace(uint8_t face, uint32_t level) const {
        return const_cast<WebGLTexture*>(this)->ImageInfoAtFace(face, level);
    }

public:
    ImageInfo& ImageInfoAt(TexImageTarget texImageTarget, GLint level) {
        auto face = FaceForTarget(texImageTarget);
        return ImageInfoAtFace(face, level);
    }

    const ImageInfo& ImageInfoAt(TexImageTarget texImageTarget, GLint level) const {
        return const_cast<WebGLTexture*>(this)->ImageInfoAt(texImageTarget, level);
    }

    void SetImageInfoAt(TexImageTarget texImageTarget, GLint level,
                        const ImageInfo& val)
    {
        ImageInfo* target = &ImageInfoAt(texImageTarget, level);
        SetImageInfo(target, val);
    }

    const ImageInfo& BaseImageInfo() const {
        if (mBaseMipmapLevel >= kMaxLevelCount)
            return ImageInfo::kUndefined;

        return ImageInfoAtFace(0, mBaseMipmapLevel);
    }

    size_t MemoryUsage() const;

    bool InitializeImageData(const char* funcName, TexImageTarget target, uint32_t level);
protected:
    bool EnsureImageDataInitialized(const char* funcName, TexImageTarget target,
                                    uint32_t level);
    bool EnsureLevelInitialized(const char* funcName, uint32_t level);

    bool CheckFloatTextureFilterParams() const {
        // Without OES_texture_float_linear, only NEAREST and
        // NEAREST_MIMPAMP_NEAREST are supported.
        return mMagFilter == LOCAL_GL_NEAREST &&
               (mMinFilter == LOCAL_GL_NEAREST ||
                mMinFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST);
    }

    bool AreBothWrapModesClampToEdge() const {
        return mWrapS == LOCAL_GL_CLAMP_TO_EDGE &&
               mWrapT == LOCAL_GL_CLAMP_TO_EDGE;
    }

public:
    bool DoesMinFilterRequireMipmap() const {
        return !(mMinFilter == LOCAL_GL_NEAREST ||
                 mMinFilter == LOCAL_GL_LINEAR);
    }

    void SetGeneratedMipmap();

    void SetCustomMipmap();

    bool AreAllLevel0ImageInfosEqual() const;

    bool IsMipmapComplete(const char* funcName, uint32_t texUnit,
                          bool* const out_initFailed);

    bool IsCubeComplete() const;

    bool IsComplete(const char* funcName, uint32_t texUnit, const char** const out_reason,
                    bool* const out_initFailed);

    bool IsMipmapCubeComplete() const;

    bool IsCubeMap() const { return (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP); }

    // Resolve cache optimizations
protected:
    bool GetFakeBlackType(const char* funcName, uint32_t texUnit,
                          FakeBlackType* const out_fakeBlack);
public:
    bool IsFeedback(WebGLContext* webgl, const char* funcName, uint32_t texUnit,
                    const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const;

    bool ResolveForDraw(const char* funcName, uint32_t texUnit,
                        FakeBlackType* const out_fakeBlack);

    void InvalidateResolveCache() { mIsResolved = false; }
};

inline TexImageTarget
TexImageTargetForTargetAndFace(TexTarget target, uint8_t face)
{
    switch (target.get()) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_3D:
        MOZ_ASSERT(face == 0);
        return target.get();
    case LOCAL_GL_TEXTURE_CUBE_MAP:
        MOZ_ASSERT(face < 6);
        return LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
    default:
        MOZ_CRASH("GFX: TexImageTargetForTargetAndFace");
    }
}

already_AddRefed<mozilla::layers::Image>
ImageFromVideo(dom::HTMLVideoElement* elem);

bool
IsTarget3D(TexImageTarget target);

GLenum
DoTexImage(gl::GLContext* gl, TexImageTarget target, GLint level,
           const webgl::DriverUnpackInfo* dui, GLsizei width, GLsizei height,
           GLsizei depth, const void* data);
GLenum
DoTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level, GLint xOffset,
              GLint yOffset, GLint zOffset, GLsizei width, GLsizei height,
              GLsizei depth, const webgl::PackingInfo& pi, const void* data);
GLenum
DoCompressedTexSubImage(gl::GLContext* gl, TexImageTarget target, GLint level,
                        GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width,
                        GLsizei height, GLsizei depth, GLenum sizedUnpackFormat,
                        GLsizei dataSize, const void* data);

} // namespace mozilla

#endif // WEBGL_TEXTURE_H_