/* -*- Mode: C++; tab-width: 4; 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 "WebGLContext.h"
#include "WebGLContextUtils.h"
#include "WebGLBuffer.h"
#include "WebGLShader.h"
#include "WebGLProgram.h"
#include "WebGLFramebuffer.h"
#include "WebGLRenderbuffer.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/EndianUtils.h"

namespace mozilla {

/*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;
  }
}

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

void WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex) {
  FuncScope funcScope(*this, "bindTexture");
  if (IsContextLost()) return;
  funcScope.mBindFailureGuard = true;

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

  // Need to check rawTarget first before comparing against newTex->Target() as
  // newTex->Target() returns a TexTarget, which will assert on invalid value.
  RefPtr<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("target", rawTarget);
    return;
  }

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

  *currentTexPtr = newTex;

  funcScope.mBindFailureGuard = false;
}

void WebGLContext::GenerateMipmap(GLenum texTarget) {
  const FuncScope funcScope(*this, "generateMipmap");

  const auto& tex = GetActiveTex(texTarget);
  if (!tex) return;
  tex->GenerateMipmap();
}

Maybe<double> WebGLContext::GetTexParameter(const WebGLTexture& tex,
                                            GLenum pname) const {
  const FuncScope funcScope(*this, "getTexParameter");

  if (!IsTexParamValid(pname)) {
    ErrorInvalidEnumInfo("pname", pname);
    return Nothing();
  }

  return tex.GetTexParameter(pname);
}

void WebGLContext::TexParameter_base(GLenum texTarget, GLenum pname,
                                     const FloatOrInt& param) {
  const FuncScope funcScope(*this, "texParameter");

  const auto& tex = GetActiveTex(texTarget);
  if (!tex) return;
  tex->TexParameter(texTarget, pname, param);
}

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

WebGLTexture* WebGLContext::GetActiveTex(const GLenum texTarget) const {
  const auto* list = &mBound2DTextures;
  switch (texTarget) {
    case LOCAL_GL_TEXTURE_2D:
      break;
    case LOCAL_GL_TEXTURE_CUBE_MAP:
      list = &mBoundCubeMapTextures;
      break;
    case LOCAL_GL_TEXTURE_3D:
      list = IsWebGL2() ? &mBound3DTextures : nullptr;
      break;
    case LOCAL_GL_TEXTURE_2D_ARRAY:
      list = IsWebGL2() ? &mBound2DArrayTextures : nullptr;
      break;
    default:
      list = nullptr;
  }
  if (!list) {
    ErrorInvalidEnumArg("target", texTarget);
    return nullptr;
  }
  const auto& tex = (*list)[mActiveTexture];
  if (!tex) {
    GenerateError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
                  EnumString(texTarget).c_str(), mActiveTexture);
    return nullptr;
  }
  return tex;
}

void WebGLContext::TexStorage(GLenum texTarget, uint32_t levels,
                              GLenum internalFormat, uvec3 size) const {
  const FuncScope funcScope(*this, "texStorage(Multisample)?");
  if (!IsTexTarget3D(texTarget)) {
    size.z = 1;
  }
  const auto tex = GetActiveTex(texTarget);
  if (!tex) return;
  tex->TexStorage(texTarget, levels, internalFormat, size);
}

void WebGLContext::TexImage(uint32_t level, GLenum respecFormat, uvec3 offset,
                            const webgl::PackingInfo& pi,
                            const webgl::TexUnpackBlobDesc& src) const {
  const WebGLContext::FuncScope funcScope(
      *this, bool(respecFormat) ? "texImage" : "texSubImage");

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

  if (respecFormat) {
    offset = {0, 0, 0};
  }
  const auto texTarget = ImageToTexTarget(src.imageTarget);
  const auto tex = GetActiveTex(texTarget);
  if (!tex) return;
  tex->TexImage(level, respecFormat, offset, pi, src);
}

void WebGLContext::CompressedTexImage(bool sub, GLenum imageTarget,
                                      uint32_t level, GLenum format,
                                      uvec3 offset, uvec3 size,
                                      const Range<const uint8_t>& src,
                                      const uint32_t pboImageSize,
                                      const Maybe<uint64_t>& pboOffset) const {
  const WebGLContext::FuncScope funcScope(
      *this, !sub ? "compressedTexImage" : "compressedTexSubImage");

  if (!sub) {
    offset = {0, 0, 0};
  }
  const auto texTarget = ImageToTexTarget(imageTarget);
  if (!IsTexTarget3D(texTarget)) {
    size.z = 1;
  }
  const auto tex = GetActiveTex(texTarget);
  if (!tex) return;
  tex->CompressedTexImage(sub, imageTarget, level, format, offset, size, src,
                          pboImageSize, pboOffset);
}

void WebGLContext::CopyTexImage(GLenum imageTarget, uint32_t level,
                                GLenum respecFormat, uvec3 dstOffset,
                                const ivec2& srcOffset,
                                const uvec2& size) const {
  const WebGLContext::FuncScope funcScope(
      *this, bool(respecFormat) ? "copyTexImage" : "copyTexSubImage");

  if (respecFormat) {
    dstOffset = {0, 0, 0};
  }
  const auto tex = GetActiveTex(ImageToTexTarget(imageTarget));
  if (!tex) return;
  tex->CopyTexImage(imageTarget, level, respecFormat, dstOffset, srcOffset,
                    size);
}

}  // namespace mozilla
