//
// Copyright (c) 2017 The Khronos Group Inc.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "testBase.h"
#include "common.h"

#if defined( __APPLE__ )
    #include <OpenGL/glu.h>
#else
    #include <GL/glu.h>
    #include <CL/cl_gl.h>
#endif

extern "C" { extern cl_uint gRandomSeed; };

extern int supportsHalf(cl_context context, bool* supports_half);

static int test_image_info( cl_context context, cl_command_queue queue,
  GLenum glTarget, GLuint glTexture, size_t imageWidth, size_t imageHeight,
  size_t imageDepth, cl_image_format *outFormat, ExplicitType *outType,
  void **outResultBuffer )
{
  clMemWrapper streams[ 2 ];

  int error;

  // Create a CL image from the supplied GL texture
  streams[ 0 ] = (*clCreateFromGLTexture_ptr)( context, CL_MEM_READ_ONLY,
    glTarget, 0, glTexture, &error );
  if( error != CL_SUCCESS )
  {
    print_error( error, "Unable to create CL image from GL texture" );
    GLint fmt;
    glGetTexLevelParameteriv( glTarget, 0, GL_TEXTURE_INTERNAL_FORMAT, &fmt );
    log_error( "    Supplied GL texture was format %s\n", GetGLFormatName( fmt ) );
    return error;
  }

  // Determine data type and format that CL came up with
  error = clGetImageInfo( streams[ 0 ], CL_IMAGE_FORMAT,
    sizeof( cl_image_format ), outFormat, NULL );
  test_error( error, "Unable to get CL image format" );

  cl_gl_object_type object_type;
  switch (glTarget) {
    case GL_TEXTURE_1D:
      object_type = CL_GL_OBJECT_TEXTURE1D;
      break;
    case GL_TEXTURE_BUFFER:
      object_type = CL_GL_OBJECT_TEXTURE_BUFFER;
      break;
    case GL_TEXTURE_1D_ARRAY:
      object_type = CL_GL_OBJECT_TEXTURE1D_ARRAY;
      break;
    case GL_TEXTURE_2D:
    case GL_TEXTURE_RECTANGLE_EXT:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
      object_type = CL_GL_OBJECT_TEXTURE2D;
      break;
    case GL_TEXTURE_2D_ARRAY:
      object_type = CL_GL_OBJECT_TEXTURE2D_ARRAY;
      break;
    case GL_TEXTURE_3D:
      object_type = CL_GL_OBJECT_TEXTURE3D;
      break;
    default:
      log_error("Unsupported texture target.");
      return 1;
  }

  return CheckGLObjectInfo(streams[0], object_type, glTexture, glTarget, 0);
}

static int test_image_format_get_info(
    cl_context context, cl_command_queue queue,
    size_t width, size_t height, size_t depth,
    GLenum target, struct format* fmt, MTdata data)
{
  int error = 0;

  // If we're testing a half float format, then we need to determine the
  // rounding mode of this machine.  Punt if we fail to do so.

  if( fmt->type == kHalf )
  {
    if( DetectFloatToHalfRoundingMode(queue) )
      return 0;
    bool supports_half = false;
    error = supportsHalf(context, &supports_half);
    if( error != 0 )
      return error;
    if (!supports_half) return 0;
  }

  size_t w = width, h = height, d = depth;

  // Unpack the format and use it, along with the target, to create an
  // appropriate GL texture.

  GLenum gl_fmt          = fmt->formattype;
  GLenum gl_internal_fmt = fmt->internal;
  GLenum gl_type         = fmt->datatype;
  ExplicitType type      = fmt->type;

  glTextureWrapper texture;
  glBufferWrapper glbuf;

  // If we're testing a half float format, then we need to determine the
  // rounding mode of this machine.  Punt if we fail to do so.

  if( fmt->type == kHalf )
    if( DetectFloatToHalfRoundingMode(queue) )
      return 1;

  // Use the correct texture creation function depending on the target, and
  // adjust width, height, depth as appropriate so subsequent size calculations
  // succeed.

  switch (target) {
    case GL_TEXTURE_1D:
      h = 1; d = 1;
      CreateGLTexture1D( width, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &error, false, data );
      break;
    case GL_TEXTURE_BUFFER:
      h = 1; d = 1;
      CreateGLTextureBuffer( width, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &glbuf, &error, false, data );
      break;
    case GL_TEXTURE_1D_ARRAY:
      d = 1;
      CreateGLTexture1DArray( width, height, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &error, false, data );
      break;
    case GL_TEXTURE_RECTANGLE_EXT:
    case GL_TEXTURE_2D:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
    case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
    case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
      d = 1;
      CreateGLTexture2D( width, height, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &error, false, data );
      break;
    case GL_TEXTURE_2D_ARRAY:
      CreateGLTexture2DArray( width, height, depth, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &error, false, data );
      break;
    case GL_TEXTURE_3D:
      d = 1;
      CreateGLTexture3D( width, height, depth, target, gl_fmt,
        gl_internal_fmt, gl_type, type, &texture, &error, data, false );
      break;
    default:
      log_error("Unsupported texture target.\n");
      return 1;
  }

  if ( error == -2 ) {
    log_info("OpenGL texture couldn't be created, because a texture is too big. Skipping test.\n");
    return 0;
  }

  if ( error != 0 ) {
    if ((gl_fmt == GL_RGBA_INTEGER_EXT) && (!CheckGLIntegerExtensionSupport())) {
      log_info("OpenGL version does not support GL_RGBA_INTEGER_EXT. "
        "Skipping test.\n");
      return 0;
    } else {
      return error;
    }
  }

  cl_image_format clFormat;
  ExplicitType actualType;
  char *outBuffer;

  // Perform the info check:
  return test_image_info( context, queue, target, texture, w, h, d, &clFormat,
    &actualType, (void **)&outBuffer );
}

int test_images_get_info_common( cl_device_id device, cl_context context,
  cl_command_queue queue, struct format* formats, size_t nformats,
  GLenum *targets, size_t ntargets, sizevec_t *sizes, size_t nsizes )
{
  int error = 0;
  RandomSeed seed(gRandomSeed);

  // First, ensure this device supports images.

  if (checkForImageSupport(device)) {
    log_info("Device does not support images.  Skipping test.\n");
    return 0;
  }

  size_t fidx, tidx, sidx;

  // Test each format on every target, every size.

  for ( fidx = 0; fidx < nformats; fidx++ ) {
    for ( tidx = 0; tidx < ntargets; tidx++ ) {

      if ( formats[ fidx ].datatype == GL_UNSIGNED_INT_2_10_10_10_REV )
      {
        // Check if the RGB 101010 format is supported
        if ( is_rgb_101010_supported( context, targets[ tidx ] ) == 0 )
          break; // skip
      }

      log_info( "Testing image info for GL format %s : %s : %s : %s\n",
        GetGLTargetName( targets[ tidx ] ),
        GetGLFormatName( formats[ fidx ].internal ),
        GetGLBaseFormatName( formats[ fidx ].formattype ),
        GetGLTypeName( formats[ fidx ].datatype ) );

      for ( sidx = 0; sidx < nsizes; sidx++ ) {

        // Test this format + size:

        if ( test_image_format_get_info(context, queue,
                                        sizes[sidx].width, sizes[sidx].height, sizes[sidx].depth,
                                        targets[tidx], &formats[fidx], seed) )
        {
          // We land here in the event of test failure.

          log_error( "ERROR: Image info test failed for %s : %s : %s : %s\n\n",
            GetGLTargetName( targets[ tidx ] ),
            GetGLFormatName( formats[ fidx ].internal ),
            GetGLBaseFormatName( formats[ fidx ].formattype ),
            GetGLTypeName( formats[ fidx ].datatype ) );
          error++;

          // Skip the other sizes for this format.

          break;
        }
      }
    }
  }

  return error;
}
