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

#include "WebGLContext.h"
#include "WebGLContextUtils.h"
#include "WebGLBuffer.h"
#include "WebGLVertexAttribData.h"
#include "WebGLShader.h"
#include "WebGLProgram.h"
#include "WebGLUniformLocation.h"
#include "WebGLFramebuffer.h"
#include "WebGLRenderbuffer.h"
#include "WebGLShaderPrecisionFormat.h"
#include "WebGLTexture.h"
#include "WebGLExtensions.h"
#include "WebGLVertexArray.h"

#include "nsString.h"
#include "nsDebug.h"
#include "nsReadableUtils.h"

#include "gfxContext.h"
#include "gfxPlatform.h"
#include "GLContext.h"

#include "nsContentUtils.h"
#include "nsError.h"
#include "nsLayoutUtils.h"

#include "CanvasUtils.h"
#include "gfxUtils.h"

#include "jsfriendapi.h"

#include "WebGLTexelConversions.h"
#include "WebGLValidateStrings.h"
#include <algorithm>

// needed to check if current OS is lower than 10.7
#if defined(MOZ_WIDGET_COCOA)
#include "nsCocoaFeatures.h"
#endif

#include "mozilla/DebugOnly.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ImageData.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/EndianUtils.h"

namespace mozilla {

static bool
IsValidTexTarget(WebGLContext* webgl, uint8_t funcDims, GLenum rawTexTarget,
                 TexTarget* const out)
{
    uint8_t targetDims;

    switch (rawTexTarget) {
    case LOCAL_GL_TEXTURE_2D:
    case LOCAL_GL_TEXTURE_CUBE_MAP:
        targetDims = 2;
        break;

    case LOCAL_GL_TEXTURE_3D:
    case LOCAL_GL_TEXTURE_2D_ARRAY:
        if (!webgl->IsWebGL2())
            return false;

        targetDims = 3;
        break;

    default:
        return false;
    }

    // Some funcs (like GenerateMipmap) doesn't know the dimension, so don't check it.
    if (funcDims && targetDims != funcDims)
        return false;

    *out = rawTexTarget;
    return true;
}

static bool
IsValidTexImageTarget(WebGLContext* webgl, uint8_t funcDims, GLenum rawTexImageTarget,
                      TexImageTarget* const out)
{
    uint8_t targetDims;

    switch (rawTexImageTarget) {
    case LOCAL_GL_TEXTURE_2D:
    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:
        targetDims = 2;
        break;

    case LOCAL_GL_TEXTURE_3D:
    case LOCAL_GL_TEXTURE_2D_ARRAY:
        if (!webgl->IsWebGL2())
            return false;

        targetDims = 3;
        break;

    default:
        return false;
    }

    if (targetDims != funcDims)
        return false;

    *out = rawTexImageTarget;
    return true;
}

bool
ValidateTexTarget(WebGLContext* webgl, const char* funcName, uint8_t funcDims,
                  GLenum rawTexTarget, TexTarget* const out_texTarget,
                  WebGLTexture** const out_tex)
{
    if (webgl->IsContextLost())
        return false;

    TexTarget texTarget;
    if (!IsValidTexTarget(webgl, funcDims, rawTexTarget, &texTarget)) {
        webgl->ErrorInvalidEnum("%s: Invalid texTarget.", funcName);
        return false;
    }

    WebGLTexture* tex = webgl->ActiveBoundTextureForTarget(texTarget);
    if (!tex) {
        webgl->ErrorInvalidOperation("%s: No texture is bound to this target.", funcName);
        return false;
    }

    *out_texTarget = texTarget;
    *out_tex = tex;
    return true;
}

bool
ValidateTexImageTarget(WebGLContext* webgl, const char* funcName, uint8_t funcDims,
                       GLenum rawTexImageTarget, TexImageTarget* const out_texImageTarget,
                       WebGLTexture** const out_tex)
{
    if (webgl->IsContextLost())
        return false;

    TexImageTarget texImageTarget;
    if (!IsValidTexImageTarget(webgl, funcDims, rawTexImageTarget, &texImageTarget)) {
        webgl->ErrorInvalidEnum("%s: Invalid texImageTarget.", funcName);
        return false;
    }

    WebGLTexture* tex = webgl->ActiveBoundTextureForTexImageTarget(texImageTarget);
    if (!tex) {
        webgl->ErrorInvalidOperation("%s: No texture is bound to this target.", funcName);
        return false;
    }

    *out_texImageTarget = texImageTarget;
    *out_tex = tex;
    return true;
}

/*virtual*/ bool
WebGLContext::IsTexParamValid(GLenum pname) const
{
    switch (pname) {
    case LOCAL_GL_TEXTURE_MIN_FILTER:
    case LOCAL_GL_TEXTURE_MAG_FILTER:
    case LOCAL_GL_TEXTURE_WRAP_S:
    case LOCAL_GL_TEXTURE_WRAP_T:
        return true;

    case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
        return IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic);

    default:
        return false;
    }
}

void
WebGLContext::InvalidateResolveCacheForTextureWithTexUnit(const GLuint texUnit)
{
    if (mBound2DTextures[texUnit])
        mBound2DTextures[texUnit]->InvalidateResolveCache();
    if (mBoundCubeMapTextures[texUnit])
        mBoundCubeMapTextures[texUnit]->InvalidateResolveCache();
    if (mBound3DTextures[texUnit])
        mBound3DTextures[texUnit]->InvalidateResolveCache();
    if (mBound2DArrayTextures[texUnit])
        mBound2DArrayTextures[texUnit]->InvalidateResolveCache();
}

//////////////////////////////////////////////////////////////////////////////////////////
// GL calls

void
WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex)
{
    if (IsContextLost())
        return;

     if (newTex && !ValidateObject("bindTexture", *newTex))
        return;

    // Need to check rawTarget first before comparing against newTex->Target() as
    // newTex->Target() returns a TexTarget, which will assert on invalid value.
    WebGLRefPtr<WebGLTexture>* currentTexPtr = nullptr;
    switch (rawTarget) {
    case LOCAL_GL_TEXTURE_2D:
        currentTexPtr = &mBound2DTextures[mActiveTexture];
        break;

   case LOCAL_GL_TEXTURE_CUBE_MAP:
        currentTexPtr = &mBoundCubeMapTextures[mActiveTexture];
        break;

   case LOCAL_GL_TEXTURE_3D:
        if (IsWebGL2())
            currentTexPtr = &mBound3DTextures[mActiveTexture];
        break;

   case LOCAL_GL_TEXTURE_2D_ARRAY:
        if (IsWebGL2())
            currentTexPtr = &mBound2DArrayTextures[mActiveTexture];
        break;
    }

    if (!currentTexPtr) {
        ErrorInvalidEnumInfo("bindTexture: target", rawTarget);
        return;
    }

    const TexTarget texTarget(rawTarget);

    MakeContextCurrent();

    if (newTex) {
        if (!newTex->BindTexture(texTarget))
            return;
    } else {
        gl->fBindTexture(texTarget.get(), 0);
    }

    *currentTexPtr = newTex;
}

void
WebGLContext::GenerateMipmap(GLenum rawTexTarget)
{
    const char funcName[] = "generateMipmap";
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &texTarget, &tex))
        return;

    tex->GenerateMipmap(texTarget);
}

JS::Value
WebGLContext::GetTexParameter(GLenum rawTexTarget, GLenum pname)
{
    const char funcName[] = "getTexParameter";
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &texTarget, &tex))
        return JS::NullValue();

    if (!IsTexParamValid(pname)) {
        ErrorInvalidEnumInfo("getTexParameter: pname", pname);
        return JS::NullValue();
    }

    return tex->GetTexParameter(texTarget, pname);
}

bool
WebGLContext::IsTexture(WebGLTexture* tex)
{
    if (!ValidateIsObject("isTexture", tex))
        return false;

    return tex->IsTexture();
}

void
WebGLContext::TexParameter_base(GLenum rawTexTarget, GLenum pname,
                                const FloatOrInt& param)
{
    const char funcName[] = "texParameter";
    const uint8_t funcDims = 0;

    TexTarget texTarget;
    WebGLTexture* tex;
    if (!ValidateTexTarget(this, funcName, funcDims, rawTexTarget, &texTarget, &tex))
        return;

    tex->TexParameter(texTarget, pname, param);
}

//////////////////////////////////////////////////////////////////////////////////////////
// Uploads

void
WebGLContext::CompressedTexImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
                                 GLint level, GLenum internalFormat, GLsizei width,
                                 GLsizei height, GLsizei depth, GLint border,
                                 const TexImageSource& src)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    tex->CompressedTexImage(funcName, target, level, internalFormat, width, height, depth,
                            border, src);
}

void
WebGLContext::CompressedTexSubImage(const char* funcName, uint8_t funcDims,
                                    GLenum rawTarget, GLint level, GLint xOffset,
                                    GLint yOffset, GLint zOffset, GLsizei width,
                                    GLsizei height, GLsizei depth, GLenum unpackFormat,
                                    const TexImageSource& src)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    tex->CompressedTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width,
                               height, depth, unpackFormat, src);
}

////

void
WebGLContext::CopyTexImage2D(GLenum rawTarget, GLint level, GLenum internalFormat,
                             GLint x, GLint y, GLsizei width, GLsizei height,
                             GLint border)
{
    const char funcName[] = "copyTexImage2D";
    const uint8_t funcDims = 2;

    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    tex->CopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}

void
WebGLContext::CopyTexSubImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
                              GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
                              GLint x, GLint y, GLsizei width, GLsizei height)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    tex->CopyTexSubImage(funcName, target, level, xOffset, yOffset, zOffset, x, y, width,
                         height);
}

////

void
WebGLContext::TexImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
                       GLint level, GLenum internalFormat, GLsizei width, GLsizei height,
                       GLsizei depth, GLint border, GLenum unpackFormat,
                       GLenum unpackType, const TexImageSource& src)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    const bool isUploadFromPbo = bool(src.mPboOffset);
    const bool isPboBound = bool(mBoundPixelUnpackBuffer);
    if (isUploadFromPbo != isPboBound) {
      SynthesizeGLError(LOCAL_GL_INVALID_OPERATION,
                    "Tex upload from %s but PIXEL_UNPACK_BUFFER %s bound.",
                    isUploadFromPbo ? "PBO" : "non-PBO",
                    isPboBound ? "was" : "was not");
      return;
    }

    const webgl::PackingInfo pi = {unpackFormat, unpackType};
    tex->TexImage(funcName, target, level, internalFormat, width, height, depth, border,
                  pi, src);
}

void
WebGLContext::TexSubImage(const char* funcName, uint8_t funcDims, GLenum rawTarget,
                          GLint level, GLint xOffset, GLint yOffset, GLint zOffset,
                          GLsizei width, GLsizei height, GLsizei depth,
                          GLenum unpackFormat, GLenum unpackType,
                          const TexImageSource& src)
{
    TexImageTarget target;
    WebGLTexture* tex;
    if (!ValidateTexImageTarget(this, funcName, funcDims, rawTarget, &target, &tex))
        return;

    const bool isUploadFromPbo = bool(src.mPboOffset);
    const bool isPboBound = bool(mBoundPixelUnpackBuffer);
    if (isUploadFromPbo != isPboBound) {
      SynthesizeGLError(LOCAL_GL_INVALID_OPERATION,
                    "Tex upload from %s but PIXEL_UNPACK_BUFFER %s bound.",
                    isUploadFromPbo ? "PBO" : "non-PBO",
                    isPboBound ? "was" : "was not");
      return;
    }

    const webgl::PackingInfo pi = {unpackFormat, unpackType};
    tex->TexSubImage(funcName, target, level, xOffset, yOffset, zOffset, width, height,
                     depth, pi, src);
}

} // namespace mozilla
