/*
 * Mesa 3-D graphics library
 *
 * Copyright (C) 2014 Intel Corporation.  All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *    Jason Ekstrand <jason.ekstrand@intel.com>
 */

#include "context.h"
#include "glheader.h"
#include "errors.h"
#include "enums.h"
#include "copyimage.h"
#include "teximage.h"
#include "texobj.h"
#include "fbobject.h"
#include "textureview.h"
#include "glformats.h"

enum mesa_block_class {
   BLOCK_CLASS_128_BITS,
   BLOCK_CLASS_64_BITS
};

/**
 * Prepare the source or destination resource.  This involves error
 * checking and returning the relevant gl_texture_image or gl_renderbuffer.
 * Note that one of the resulting tex_image or renderbuffer pointers will be
 * NULL and the other will be non-null.
 *
 * \param name  the texture or renderbuffer name
 * \param target  One of GL_TEXTURE_x target or GL_RENDERBUFFER
 * \param level  mipmap level
 * \param z  src or dest Z
 * \param depth  number of slices/faces/layers to copy
 * \param tex_image  returns a pointer to a texture image
 * \param renderbuffer  returns a pointer to a renderbuffer
 * \return true if success, false if error
 */
static bool
prepare_target(struct gl_context *ctx, GLuint name, GLenum target,
               int level, int z, int depth,
               struct gl_texture_image **tex_image,
               struct gl_renderbuffer **renderbuffer,
               mesa_format *format,
               GLenum *internalFormat,
               GLuint *width,
               GLuint *height,
               GLuint *num_samples,
               const char *dbg_prefix)
{
   if (name == 0) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sName = %d)", dbg_prefix, name);
      return false;
   }

   /*
    * INVALID_ENUM is generated
    *  * if either <srcTarget> or <dstTarget>
    *   - is not RENDERBUFFER or a valid non-proxy texture target
    *   - is TEXTURE_BUFFER, or
    *   - is one of the cubemap face selectors described in table 3.17,
    */
   switch (target) {
   case GL_RENDERBUFFER:
      /* Not a texture target, but valid */
   case GL_TEXTURE_1D:
   case GL_TEXTURE_1D_ARRAY:
   case GL_TEXTURE_2D:
   case GL_TEXTURE_3D:
   case GL_TEXTURE_CUBE_MAP:
   case GL_TEXTURE_RECTANGLE:
   case GL_TEXTURE_2D_ARRAY:
   case GL_TEXTURE_CUBE_MAP_ARRAY:
   case GL_TEXTURE_2D_MULTISAMPLE:
   case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
      /* These are all valid */
      break;
   case GL_TEXTURE_EXTERNAL_OES:
      /* Only exists in ES */
   case GL_TEXTURE_BUFFER:
   default:
      _mesa_error(ctx, GL_INVALID_ENUM,
                  "glCopyImageSubData(%sTarget = %s)", dbg_prefix,
                  _mesa_enum_to_string(target));
      return false;
   }

   if (target == GL_RENDERBUFFER) {
      struct gl_renderbuffer *rb = _mesa_lookup_renderbuffer(ctx, name);

      if (!rb) {
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glCopyImageSubData(%sName = %u)", dbg_prefix, name);
         return false;
      }

      if (!rb->Name) {
         _mesa_error(ctx, GL_INVALID_OPERATION,
                     "glCopyImageSubData(%sName incomplete)", dbg_prefix);
         return false;
      }

      if (level != 0) {
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
         return false;
      }

      *renderbuffer = rb;
      *format = rb->Format;
      *internalFormat = rb->InternalFormat;
      *width = rb->Width;
      *height = rb->Height;
      *num_samples = rb->NumSamples;
      *tex_image = NULL;
   } else {
      struct gl_texture_object *texObj = _mesa_lookup_texture(ctx, name);

      if (!texObj) {
         /*
          * From GL_ARB_copy_image specification:
          * "INVALID_VALUE is generated if either <srcName> or <dstName> does
          * not correspond to a valid renderbuffer or texture object according
          * to the corresponding target parameter."
          */
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glCopyImageSubData(%sName = %u)", dbg_prefix, name);
         return false;
      }

      _mesa_test_texobj_completeness(ctx, texObj);
      if (!texObj->_BaseComplete ||
          (level != 0 && !texObj->_MipmapComplete)) {
         _mesa_error(ctx, GL_INVALID_OPERATION,
                     "glCopyImageSubData(%sName incomplete)", dbg_prefix);
         return false;
      }

      /* Note that target will not be a cube face name */
      if (texObj->Target != target) {
         /*
          * From GL_ARB_copy_image_specification:
          * "INVALID_ENUM is generated if the target does not match the type
          * of the object."
          */
         _mesa_error(ctx, GL_INVALID_ENUM,
                     "glCopyImageSubData(%sTarget = %s)", dbg_prefix,
                     _mesa_enum_to_string(target));
         return false;
      }

      if (level < 0 || level >= MAX_TEXTURE_LEVELS) {
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glCopyImageSubData(%sLevel = %d)", dbg_prefix, level);
         return false;
      }

      if (target == GL_TEXTURE_CUBE_MAP) {
         int i;

         assert(z < MAX_FACES);  /* should have been caught earlier */

         /* make sure all the cube faces are present */
         for (i = 0; i < depth; i++) {
            if (!texObj->Image[z+i][level]) {
               /* missing cube face */
               _mesa_error(ctx, GL_INVALID_VALUE,
                           "glCopyImageSubData(missing cube face)");
               return false;
            }
         }

         *tex_image = texObj->Image[z][level];
      }
      else {
         *tex_image = _mesa_select_tex_image(texObj, target, level);
      }

      if (!*tex_image) {
         _mesa_error(ctx, GL_INVALID_VALUE,
                     "glCopyImageSubData(%sLevel = %u)", dbg_prefix, level);
         return false;
      }

      *renderbuffer = NULL;
      *format = (*tex_image)->TexFormat;
      *internalFormat = (*tex_image)->InternalFormat;
      *width = (*tex_image)->Width;
      *height = (*tex_image)->Height;
      *num_samples = (*tex_image)->NumSamples;
   }

   return true;
}


/**
 * Check that the x,y,z,width,height,region is within the texture image
 * dimensions.
 * \return true if bounds OK, false if regions is out of bounds
 */
static bool
check_region_bounds(struct gl_context *ctx,
                    GLenum target,
                    const struct gl_texture_image *tex_image,
                    const struct gl_renderbuffer *renderbuffer,
                    int x, int y, int z, int width, int height, int depth,
                    const char *dbg_prefix)
{
   int surfWidth, surfHeight, surfDepth;

   if (width < 0 || height < 0 || depth < 0) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sWidth, %sHeight, or %sDepth is negative)",
                  dbg_prefix, dbg_prefix, dbg_prefix);
      return false;
   }

   if (x < 0 || y < 0 || z < 0) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sX, %sY, or %sZ is negative)",
                  dbg_prefix, dbg_prefix, dbg_prefix);
      return false;
   }

   /* Check X direction */
   if (target == GL_RENDERBUFFER) {
      surfWidth = renderbuffer->Width;
   }
   else {
      surfWidth = tex_image->Width;
   }

   if (x + width > surfWidth) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sX or %sWidth exceeds image bounds)",
                  dbg_prefix, dbg_prefix);
      return false;
   }

   /* Check Y direction */
   switch (target) {
   case GL_RENDERBUFFER:
      surfHeight = renderbuffer->Height;
      break;
   case GL_TEXTURE_1D:
   case GL_TEXTURE_1D_ARRAY:
      surfHeight = 1;
      break;
   default:
      surfHeight = tex_image->Height;
   }

   if (y + height > surfHeight) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sY or %sHeight exceeds image bounds)",
                  dbg_prefix, dbg_prefix);
      return false;
   }

   /* Check Z direction */
   switch (target) {
   case GL_RENDERBUFFER:
   case GL_TEXTURE_1D:
   case GL_TEXTURE_2D:
   case GL_TEXTURE_2D_MULTISAMPLE:
   case GL_TEXTURE_RECTANGLE:
      surfDepth = 1;
      break;
   case GL_TEXTURE_CUBE_MAP:
      surfDepth = 6;
      break;
   case GL_TEXTURE_1D_ARRAY:
      surfDepth = tex_image->Height;
      break;
   default:
      surfDepth = tex_image->Depth;
   }

   if (z < 0 || z + depth > surfDepth) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(%sZ or %sDepth exceeds image bounds)",
                  dbg_prefix, dbg_prefix);
      return false;
   }

   return true;
}

static bool
compressed_format_compatible(const struct gl_context *ctx,
                             GLenum compressedFormat, GLenum otherFormat)
{
   enum mesa_block_class compressedClass, otherClass;

   /* Two view-incompatible compressed formats are never compatible. */
   if (_mesa_is_compressed_format(ctx, otherFormat)) {
      return false;
   }

   /*
    * From ARB_copy_image spec:
    *    Table 4.X.1 (Compatible internal formats for copying between
    *                 compressed and uncompressed internal formats)
    *    ---------------------------------------------------------------------
    *    | Texel / | Uncompressed      |                                     |
    *    | Block   | internal format   | Compressed internal format          |
    *    | size    |                   |                                     |
    *    ---------------------------------------------------------------------
    *    | 128-bit | RGBA32UI,         | COMPRESSED_RGBA_S3TC_DXT3_EXT,      |
    *    |         | RGBA32I,          | COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT,|
    *    |         | RGBA32F           | COMPRESSED_RGBA_S3TC_DXT5_EXT,      |
    *    |         |                   | COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT,|
    *    |         |                   | COMPRESSED_RG_RGTC2,                |
    *    |         |                   | COMPRESSED_SIGNED_RG_RGTC2,         |
    *    |         |                   | COMPRESSED_RGBA_BPTC_UNORM,         |
    *    |         |                   | COMPRESSED_SRGB_ALPHA_BPTC_UNORM,   |
    *    |         |                   | COMPRESSED_RGB_BPTC_SIGNED_FLOAT,   |
    *    |         |                   | COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT  |
    *    ---------------------------------------------------------------------
    *    | 64-bit  | RGBA16F, RG32F,   | COMPRESSED_RGB_S3TC_DXT1_EXT,       |
    *    |         | RGBA16UI, RG32UI, | COMPRESSED_SRGB_S3TC_DXT1_EXT,      |
    *    |         | RGBA16I, RG32I,   | COMPRESSED_RGBA_S3TC_DXT1_EXT,      |
    *    |         | RGBA16,           | COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT,|
    *    |         | RGBA16_SNORM      | COMPRESSED_RED_RGTC1,               |
    *    |         |                   | COMPRESSED_SIGNED_RED_RGTC1         |
    *    ---------------------------------------------------------------------
    */

   switch (compressedFormat) {
      case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
      case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
      case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
      case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
      case GL_COMPRESSED_RG_RGTC2:
      case GL_COMPRESSED_SIGNED_RG_RGTC2:
      case GL_COMPRESSED_RGBA_BPTC_UNORM:
      case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
      case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
      case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
         compressedClass = BLOCK_CLASS_128_BITS;
         break;
      case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
      case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
      case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
      case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
      case GL_COMPRESSED_RED_RGTC1:
      case GL_COMPRESSED_SIGNED_RED_RGTC1:
         compressedClass = BLOCK_CLASS_64_BITS;
         break;
      case GL_COMPRESSED_RGBA8_ETC2_EAC:
      case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
      case GL_COMPRESSED_RG11_EAC:
      case GL_COMPRESSED_SIGNED_RG11_EAC:
         if (_mesa_is_gles(ctx))
            compressedClass = BLOCK_CLASS_128_BITS;
         else
            return false;
         break;
      case GL_COMPRESSED_RGB8_ETC2:
      case GL_COMPRESSED_SRGB8_ETC2:
      case GL_COMPRESSED_R11_EAC:
      case GL_COMPRESSED_SIGNED_R11_EAC:
      case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
      case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
         if (_mesa_is_gles(ctx))
            compressedClass = BLOCK_CLASS_64_BITS;
         else
            return false;
         break;
      default:
         if (_mesa_is_gles(ctx) && _mesa_is_astc_format(compressedFormat))
            compressedClass = BLOCK_CLASS_128_BITS;
         else
            return false;
         break;
   }

   switch (otherFormat) {
      case GL_RGBA32UI:
      case GL_RGBA32I:
      case GL_RGBA32F:
         otherClass = BLOCK_CLASS_128_BITS;
         break;
      case GL_RGBA16F:
      case GL_RG32F:
      case GL_RGBA16UI:
      case GL_RG32UI:
      case GL_RGBA16I:
      case GL_RG32I:
      case GL_RGBA16:
      case GL_RGBA16_SNORM:
         otherClass = BLOCK_CLASS_64_BITS;
         break;
      default:
         return false;
   }

   return compressedClass == otherClass;
}

static bool
copy_format_compatible(const struct gl_context *ctx,
                       GLenum srcFormat, GLenum dstFormat)
{
   /*
    * From ARB_copy_image spec:
    *    For the purposes of CopyImageSubData, two internal formats
    *    are considered compatible if any of the following conditions are
    *    met:
    *    * the formats are the same,
    *    * the formats are considered compatible according to the
    *      compatibility rules used for texture views as defined in
    *      section 3.9.X. In particular, if both internal formats are listed
    *      in the same entry of Table 3.X.2, they are considered compatible, or
    *    * one format is compressed and the other is uncompressed and
    *      Table 4.X.1 lists the two formats in the same row.
    */

   if (_mesa_texture_view_compatible_format(ctx, srcFormat, dstFormat)) {
      /* Also checks if formats are equal. */
      return true;
   } else if (_mesa_is_compressed_format(ctx, srcFormat)) {
      return compressed_format_compatible(ctx, srcFormat, dstFormat);
   } else if (_mesa_is_compressed_format(ctx, dstFormat)) {
      return compressed_format_compatible(ctx, dstFormat, srcFormat);
   }

   return false;
}

void GLAPIENTRY
_mesa_CopyImageSubData(GLuint srcName, GLenum srcTarget, GLint srcLevel,
                       GLint srcX, GLint srcY, GLint srcZ,
                       GLuint dstName, GLenum dstTarget, GLint dstLevel,
                       GLint dstX, GLint dstY, GLint dstZ,
                       GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth)
{
   GET_CURRENT_CONTEXT(ctx);
   struct gl_texture_image *srcTexImage, *dstTexImage;
   struct gl_renderbuffer *srcRenderbuffer, *dstRenderbuffer;
   mesa_format srcFormat, dstFormat;
   GLenum srcIntFormat, dstIntFormat;
   GLuint src_w, src_h, dst_w, dst_h;
   GLuint src_bw, src_bh, dst_bw, dst_bh;
   GLuint src_num_samples, dst_num_samples;
   int dstWidth, dstHeight, dstDepth;
   int i;

   if (MESA_VERBOSE & VERBOSE_API)
      _mesa_debug(ctx, "glCopyImageSubData(%u, %s, %d, %d, %d, %d, "
                                          "%u, %s, %d, %d, %d, %d, "
                                          "%d, %d, %d)\n",
                  srcName, _mesa_enum_to_string(srcTarget), srcLevel,
                  srcX, srcY, srcZ,
                  dstName, _mesa_enum_to_string(dstTarget), dstLevel,
                  dstX, dstY, dstZ,
                  srcWidth, srcHeight, srcDepth);

   if (!ctx->Extensions.ARB_copy_image) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glCopyImageSubData(extension not available)");
      return;
   }

   if (!prepare_target(ctx, srcName, srcTarget, srcLevel, srcZ, srcDepth,
                       &srcTexImage, &srcRenderbuffer, &srcFormat,
                       &srcIntFormat, &src_w, &src_h, &src_num_samples, "src"))
      return;

   if (!prepare_target(ctx, dstName, dstTarget, dstLevel, dstZ, srcDepth,
                       &dstTexImage, &dstRenderbuffer, &dstFormat,
                       &dstIntFormat, &dst_w, &dst_h, &dst_num_samples, "dst"))
      return;

   _mesa_get_format_block_size(srcFormat, &src_bw, &src_bh);

   /* Section 18.3.2 (Copying Between Images) of the OpenGL 4.5 Core Profile
    * spec says:
    *
    *    An INVALID_VALUE error is generated if the dimensions of either
    *    subregion exceeds the boundaries of the corresponding image object,
    *    or if the image format is compressed and the dimensions of the
    *    subregion fail to meet the alignment constraints of the format.
    *
    * and Section 8.7 (Compressed Texture Images) says:
    *
    *    An INVALID_OPERATION error is generated if any of the following
    *    conditions occurs:
    *
    *      * width is not a multiple of four, and width + xoffset is not
    *        equal to the value of TEXTURE_WIDTH.
    *      * height is not a multiple of four, and height + yoffset is not
    *        equal to the value of TEXTURE_HEIGHT.
    *
    * so we take that to mean that you can copy the "last" block of a
    * compressed texture image even if it's smaller than the minimum block
    * dimensions.
    */
   if ((srcX % src_bw != 0) || (srcY % src_bh != 0) ||
       (srcWidth % src_bw != 0 && (srcX + srcWidth) != src_w) ||
       (srcHeight % src_bh != 0 && (srcY + srcHeight) != src_h)) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(unaligned src rectangle)");
      return;
   }

   _mesa_get_format_block_size(dstFormat, &dst_bw, &dst_bh);
   if ((dstX % dst_bw != 0) || (dstY % dst_bh != 0)) {
      _mesa_error(ctx, GL_INVALID_VALUE,
                  "glCopyImageSubData(unaligned dst rectangle)");
      return;
   }

   /* From the GL_ARB_copy_image spec:
    *
    * "The dimensions are always specified in texels, even for compressed
    * texture formats. But it should be noted that if only one of the
    * source and destination textures is compressed then the number of
    * texels touched in the compressed image will be a factor of the
    * block size larger than in the uncompressed image."
    *
    * So, if copying from compressed to uncompressed, the dest region is
    * shrunk by the src block size factor.  If copying from uncompressed
    * to compressed, the dest region is grown by the dest block size factor.
    * Note that we're passed the _source_ width, height, depth and those
    * dimensions are never changed.
    */
   dstWidth = srcWidth * dst_bw / src_bw;
   dstHeight = srcHeight * dst_bh / src_bh;
   dstDepth = srcDepth;

   if (!check_region_bounds(ctx, srcTarget, srcTexImage, srcRenderbuffer,
                            srcX, srcY, srcZ, srcWidth, srcHeight, srcDepth,
                            "src"))
      return;

   if (!check_region_bounds(ctx, dstTarget, dstTexImage, dstRenderbuffer,
                            dstX, dstY, dstZ, dstWidth, dstHeight, dstDepth,
                            "dst"))
      return;

   /* Section 18.3.2 (Copying Between Images) of the OpenGL 4.5 Core Profile
    * spec says:
    *
    *    An INVALID_OPERATION error is generated if either object is a texture
    *    and the texture is not complete, if the source and destination internal
    *    formats are not compatible, or if the number of samples do not match.
    */
   if (!copy_format_compatible(ctx, srcIntFormat, dstIntFormat)) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glCopyImageSubData(internalFormat mismatch)");
      return;
   }

   if (src_num_samples != dst_num_samples) {
      _mesa_error(ctx, GL_INVALID_OPERATION,
                  "glCopyImageSubData(number of samples mismatch)");
      return;
   }

   /* loop over 2D slices/faces/layers */
   for (i = 0; i < srcDepth; ++i) {
      int newSrcZ = srcZ + i;
      int newDstZ = dstZ + i;

      if (srcTexImage &&
          srcTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
         /* need to update srcTexImage pointer for the cube face */
         assert(srcZ + i < MAX_FACES);
         srcTexImage = srcTexImage->TexObject->Image[srcZ + i][srcLevel];
         assert(srcTexImage);
         newSrcZ = 0;
      }

      if (dstTexImage &&
          dstTexImage->TexObject->Target == GL_TEXTURE_CUBE_MAP) {
         /* need to update dstTexImage pointer for the cube face */
         assert(dstZ + i < MAX_FACES);
         dstTexImage = dstTexImage->TexObject->Image[dstZ + i][dstLevel];
         assert(dstTexImage);
         newDstZ = 0;
      }

      ctx->Driver.CopyImageSubData(ctx,
                                   srcTexImage, srcRenderbuffer,
                                   srcX, srcY, newSrcZ,
                                   dstTexImage, dstRenderbuffer,
                                   dstX, dstY, newDstZ,
                                   srcWidth, srcHeight);
   }
}
