//
// 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"

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

#if defined (__linux__)
GLboolean
gluCheckExtension(const GLubyte *extension, const GLubyte *extensions)
{
  const GLubyte *start;
  GLubyte *where, *terminator;

  /* Extension names should not have spaces. */
  where = (GLubyte *) strchr((const char*)extension, ' ');
  if (where || *extension == '\0')
    return 0;
  /* It takes a bit of care to be fool-proof about parsing the
     OpenGL extensions string. Don't be fooled by sub-strings,
     etc. */
  start = extensions;
  for (;;) {
    where = (GLubyte *) strstr((const char *) start, (const char*) extension);
    if (!where)
      break;
    terminator = where + strlen((const char*) extension);
    if (where == start || *(where - 1) == ' ')
      if (*terminator == ' ' || *terminator == '\0')
        return 1;
    start = terminator;
  }
  return 0;
}
#endif


extern "C" { extern cl_uint gRandomSeed; };

// This is defined in the write common code:
extern int test_cl_image_write( cl_context context, cl_command_queue queue,
  GLenum target, cl_mem clImage, size_t width, size_t height, size_t depth,
  cl_image_format *outFormat, ExplicitType *outType, void **outSourceBuffer,
  MTdata d, bool supports_half );

extern int test_cl_image_read( cl_context context, cl_command_queue queue,
  GLenum gl_target, cl_mem image, size_t width, size_t height, size_t depth, size_t sampleNum,
  cl_image_format *outFormat, ExplicitType *outType, void **outResultBuffer );

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

static int test_attach_renderbuffer_read_image( cl_context context, cl_command_queue queue, GLenum glTarget, GLuint glRenderbuffer,
                    size_t imageWidth, size_t imageHeight, cl_image_format *outFormat, ExplicitType *outType, void **outResultBuffer )
{
    int error;

    // Create a CL image from the supplied GL renderbuffer
    cl_mem image = (*clCreateFromGLRenderbuffer_ptr)( context, CL_MEM_READ_ONLY, glRenderbuffer, &error );
    if( error != CL_SUCCESS )
    {
        print_error( error, "Unable to create CL image from GL renderbuffer" );
        return error;
    }

    return test_cl_image_read( context, queue, glTarget, image, imageWidth,
    imageHeight, 1, 1, outFormat, outType, outResultBuffer );
}

int test_renderbuffer_read_image( cl_context context, cl_command_queue queue,
                            GLsizei width, GLsizei height, GLenum attachment,
                            GLenum format, GLenum internalFormat,
                            GLenum glType, ExplicitType type, MTdata d )
{
    int error;

    if( type == kHalf )
        if( DetectFloatToHalfRoundingMode(queue) )
            return 1;

    // Create the GL renderbuffer
    glFramebufferWrapper glFramebuffer;
    glRenderbufferWrapper glRenderbuffer;
    void *tmp = CreateGLRenderbuffer( width, height, attachment, format, internalFormat, glType, type, &glFramebuffer, &glRenderbuffer, &error, d, true );
    BufferOwningPtr<char> inputBuffer(tmp);
    if( error != 0 )
    {
        if ((format == GL_RGBA_INTEGER_EXT) && (!CheckGLIntegerExtensionSupport()))
        {
            log_info("OpenGL version does not support GL_RGBA_INTEGER_EXT. Skipping test.\n");
            return 0;
        }
        else
        {
            return error;
        }
    }

    // Run and get the results
    cl_image_format clFormat;
    ExplicitType actualType;
    char *outBuffer;
    error = test_attach_renderbuffer_read_image( context, queue, attachment, glRenderbuffer, width, height, &clFormat, &actualType, (void **)&outBuffer );
    if( error != 0 )
        return error;
    BufferOwningPtr<char> actualResults(outBuffer);

    log_info( "- Read [%4d x %4d] : GL renderbuffer : %s : %s : %s => CL Image : %s : %s \n", width, height,
                    GetGLFormatName( format ), GetGLFormatName( internalFormat ), GetGLTypeName( glType),
                    GetChannelOrderName( clFormat.image_channel_order ), GetChannelTypeName( clFormat.image_channel_data_type ));

#ifdef DEBUG
    log_info("- start read GL data -- \n");
    DumpGLBuffer(glType, width, height, actualResults);
    log_info("- end read GL data -- \n");
#endif

    // We have to convert our input buffer to the returned type, so we can validate.
    BufferOwningPtr<char> convertedInput(convert_to_expected( inputBuffer, width * height, type, actualType, get_channel_order_channel_count(clFormat.image_channel_order) ));

#ifdef DEBUG
    log_info("- start input data -- \n");
    DumpGLBuffer(GetGLTypeForExplicitType(actualType), width, height, convertedInput);
    log_info("- end input data -- \n");
#endif

#ifdef DEBUG
    log_info("- start converted data -- \n");
    DumpGLBuffer(GetGLTypeForExplicitType(actualType), width, height, actualResults);
    log_info("- end converted data -- \n");
#endif

    // Now we validate
    int valid = 0;
    if(convertedInput) {
        if( actualType == kFloat )
            valid = validate_float_results( convertedInput, actualResults, width, height, 1, get_channel_order_channel_count(clFormat.image_channel_order) );
        else
            valid = validate_integer_results( convertedInput, actualResults, width, height, 1, get_explicit_type_size( actualType ) );
    }

    return valid;
}

int test_renderbuffer_read( cl_device_id device, cl_context context, cl_command_queue queue, int numElements )
{
    GLenum attachments[] = { GL_COLOR_ATTACHMENT0_EXT };

    struct {
        GLenum internal;
        GLenum format;
        GLenum datatype;
        ExplicitType type;

    } formats[] = {
        { GL_RGBA,         GL_BGRA,             GL_UNSIGNED_INT_8_8_8_8_REV, kUChar },
        { GL_RGBA,         GL_RGBA,             GL_UNSIGNED_INT_8_8_8_8_REV, kUChar },
        { GL_RGBA8,        GL_RGBA,             GL_UNSIGNED_BYTE,            kUChar },
        { GL_RGBA16,       GL_RGBA,             GL_UNSIGNED_SHORT,           kUShort },

// Renderbuffers with integer formats do not seem to work reliably across
// platforms/implementations. Disabling this in version 1.0 of CL conformance tests.

#ifdef TEST_INTEGER_FORMATS

        { GL_RGBA8I_EXT,   GL_RGBA_INTEGER_EXT, GL_BYTE,                     kChar },
        { GL_RGBA16I_EXT,  GL_RGBA_INTEGER_EXT, GL_SHORT,                    kShort },
        { GL_RGBA32I_EXT,  GL_RGBA_INTEGER_EXT, GL_INT,                      kInt },
        { GL_RGBA8UI_EXT,  GL_RGBA_INTEGER_EXT, GL_UNSIGNED_BYTE,            kUChar },
        { GL_RGBA16UI_EXT, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_SHORT,           kUShort },
        { GL_RGBA32UI_EXT, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_INT,             kUInt },
#endif
        { GL_RGBA32F_ARB,  GL_RGBA,             GL_FLOAT,                    kFloat },
        { GL_RGBA16F_ARB,  GL_RGBA,             GL_HALF_FLOAT,               kHalf }
    };

    size_t fmtIdx, attIdx;
    int error = 0;
#ifdef DEBUG
    size_t iter = 1;
#else
    size_t iter = 6;
#endif
    RandomSeed seed( gRandomSeed );

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

    if( !gluCheckExtension( (const GLubyte *)"GL_EXT_framebuffer_object", glGetString( GL_EXTENSIONS ) ) )
    {
        log_info( "Renderbuffers are not supported by this OpenGL implementation; skipping test\n" );
        return 0;
    }

    // Loop through a set of GL formats, testing a set of sizes against each one
    for( fmtIdx = 0; fmtIdx < sizeof( formats ) / sizeof( formats[ 0 ] ); fmtIdx++ )
    {
        for( attIdx = 0; attIdx < sizeof( attachments ) / sizeof( attachments[ 0 ] ); attIdx++ )
        {
            size_t i;

            log_info( "Testing renderbuffer read for %s : %s : %s : %s\n",
                GetGLAttachmentName( attachments[ attIdx ] ),
                GetGLFormatName( formats[ fmtIdx ].internal ),
                GetGLBaseFormatName( formats[ fmtIdx ].format ),
                GetGLTypeName( formats[ fmtIdx ].datatype ) );

            for( i = 0; i < iter; i++ )
            {
                GLsizei width = random_in_range( 16, 512, seed );
                GLsizei height = random_in_range( 16, 512, seed );
#ifdef DEBUG
                width = height = 4;
#endif

                if( test_renderbuffer_read_image( context, queue, width, height,
                                                  attachments[ attIdx ],
                                                  formats[ fmtIdx ].format,
                                                  formats[ fmtIdx ].internal,
                                                  formats[ fmtIdx ].datatype,
                                                  formats[ fmtIdx ].type, seed ) )

                {
                    log_error( "ERROR: Renderbuffer read test failed for %s : %s : %s : %s\n\n",
                                GetGLAttachmentName( attachments[ attIdx ] ),
                                GetGLFormatName( formats[ fmtIdx ].internal ),
                                GetGLBaseFormatName( formats[ fmtIdx ].format ),
                                GetGLTypeName( formats[ fmtIdx ].datatype ) );

                    error++;
                    break;    // Skip other sizes for this combination
                }
            }
            if( i == iter )
            {
                log_info( "passed: Renderbuffer read test passed for %s : %s : %s : %s\n\n",
                          GetGLAttachmentName( attachments[ attIdx ] ),
                          GetGLFormatName( formats[ fmtIdx ].internal ),
                          GetGLBaseFormatName( formats[ fmtIdx ].format ),
                          GetGLTypeName( formats[ fmtIdx ].datatype ) );
            }
        }
    }

    return error;
}


#pragma mark -------------------- Write tests -------------------------

int test_attach_renderbuffer_write_to_image( cl_context context, cl_command_queue queue, GLenum glTarget, GLuint glRenderbuffer,
                     size_t imageWidth, size_t imageHeight, cl_image_format *outFormat, ExplicitType *outType, MTdata d, void **outSourceBuffer, bool supports_half )
{
    int error;

    // Create a CL image from the supplied GL renderbuffer
    clMemWrapper image = (*clCreateFromGLRenderbuffer_ptr)( context, CL_MEM_WRITE_ONLY, glRenderbuffer, &error );
    if( error != CL_SUCCESS )
    {
        print_error( error, "Unable to create CL image from GL renderbuffer" );
        return error;
    }

    return test_cl_image_write( context, queue, glTarget, image, imageWidth,
    imageHeight, 1, outFormat, outType, outSourceBuffer, d, supports_half );
}

int test_renderbuffer_image_write( cl_context context, cl_command_queue queue,
                                   GLsizei width, GLsizei height, GLenum attachment,
                                   GLenum format, GLenum internalFormat,
                                     GLenum glType, ExplicitType type, MTdata d )
{
    int error;

    if( type == kHalf )
        if( DetectFloatToHalfRoundingMode(queue) )
            return 1;

    // Create the GL renderbuffer
    glFramebufferWrapper glFramebuffer;
    glRenderbufferWrapper glRenderbuffer;
    CreateGLRenderbuffer( width, height, attachment, format, internalFormat, glType, type, &glFramebuffer, &glRenderbuffer, &error, d, false );
    if( error != 0 )
    {
        if ((format == GL_RGBA_INTEGER_EXT) && (!CheckGLIntegerExtensionSupport()))
        {
            log_info("OpenGL version does not support GL_RGBA_INTEGER_EXT. Skipping test.\n");
            return 0;
        }
        else
        {
            return error;
        }
    }

    // Run and get the results
    cl_image_format clFormat;
    ExplicitType sourceType;
    ExplicitType validationType;
    void *outSourceBuffer;

    bool supports_half = false;
    error = supportsHalf(context, &supports_half);
    if( error != 0 )
        return error;

    error = test_attach_renderbuffer_write_to_image( context, queue, attachment, glRenderbuffer, width, height, &clFormat, &sourceType, d, (void **)&outSourceBuffer, supports_half );
    if( error != 0 || ((sourceType == kHalf ) && !supports_half))
        return error;

    // If actual source type was half, convert to float for validation.
    if( sourceType == kHalf )
        validationType = kFloat;
    else
        validationType = sourceType;

    BufferOwningPtr<char> validationSource( convert_to_expected( outSourceBuffer, width * height, sourceType, validationType, get_channel_order_channel_count(clFormat.image_channel_order) ) );

    log_info( "- Write [%4d x %4d] : GL Renderbuffer : %s : %s : %s => CL Image : %s : %s \n", width, height,
                    GetGLFormatName( format ), GetGLFormatName( internalFormat ), GetGLTypeName( glType),
                    GetChannelOrderName( clFormat.image_channel_order ), GetChannelTypeName( clFormat.image_channel_data_type ));

    // Now read the results from the GL renderbuffer
    BufferOwningPtr<char> resultData( ReadGLRenderbuffer( glFramebuffer, glRenderbuffer, attachment, format, internalFormat, glType, type, width, height ) );

#ifdef DEBUG
    log_info("- start result data -- \n");
    DumpGLBuffer(glType, width, height, resultData);
    log_info("- end result data -- \n");
#endif

    // We have to convert our input buffer to the returned type, so we can validate.
    BufferOwningPtr<char> convertedData( convert_to_expected( resultData, width * height, type, validationType, get_channel_order_channel_count(clFormat.image_channel_order) ) );

#ifdef DEBUG
    log_info("- start input data -- \n");
    DumpGLBuffer(GetGLTypeForExplicitType(validationType), width, height, validationSource);
    log_info("- end input data -- \n");
#endif

#ifdef DEBUG
    log_info("- start converted data -- \n");
    DumpGLBuffer(GetGLTypeForExplicitType(validationType), width, height, convertedData);
    log_info("- end converted data -- \n");
#endif

    // Now we validate
    int valid = 0;
    if(convertedData) {
        if( sourceType == kFloat || sourceType == kHalf )
            valid = validate_float_results( validationSource, convertedData, width, height, 1, get_channel_order_channel_count(clFormat.image_channel_order) );
        else
            valid = validate_integer_results( validationSource, convertedData, width, height, 1, get_explicit_type_size( type ) );
    }

    return valid;
}

int test_renderbuffer_write( cl_device_id device, cl_context context, cl_command_queue queue, int numElements )
{
    GLenum attachments[] = { GL_COLOR_ATTACHMENT0_EXT };

    struct {
        GLenum internal;
        GLenum format;
        GLenum datatype;
        ExplicitType type;

    } formats[] = {
        { GL_RGBA,         GL_BGRA,             GL_UNSIGNED_INT_8_8_8_8_REV, kUChar },
        { GL_RGBA,         GL_RGBA,             GL_UNSIGNED_INT_8_8_8_8_REV, kUChar },
        { GL_RGBA8,        GL_RGBA,             GL_UNSIGNED_BYTE,            kUChar },
        { GL_RGBA16,       GL_RGBA,             GL_UNSIGNED_SHORT,           kUShort },

// Renderbuffers with integer formats do not seem to work reliably across
// platforms/implementations. Disabling this in version 1.0 of CL conformance tests.

#ifdef TEST_INTEGER_FORMATS

        { GL_RGBA8I_EXT,   GL_RGBA_INTEGER_EXT, GL_BYTE,                     kChar },
        { GL_RGBA16I_EXT,  GL_RGBA_INTEGER_EXT, GL_SHORT,                    kShort },
        { GL_RGBA32I_EXT,  GL_RGBA_INTEGER_EXT, GL_INT,                      kInt },
        { GL_RGBA8UI_EXT,  GL_RGBA_INTEGER_EXT, GL_UNSIGNED_BYTE,            kUChar },
        { GL_RGBA16UI_EXT, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_SHORT,           kUShort },
        { GL_RGBA32UI_EXT, GL_RGBA_INTEGER_EXT, GL_UNSIGNED_INT,             kUInt },
#endif
        { GL_RGBA32F_ARB,  GL_RGBA,             GL_FLOAT,                    kFloat },
        { GL_RGBA16F_ARB,  GL_RGBA,             GL_HALF_FLOAT,               kHalf }
    };

    size_t fmtIdx, attIdx;
    int error = 0;
    size_t iter = 6;
#ifdef DEBUG
    iter = 1;
#endif
    RandomSeed seed( gRandomSeed );

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

    if( !gluCheckExtension( (const GLubyte *)"GL_EXT_framebuffer_object", glGetString( GL_EXTENSIONS ) ) )
    {
        log_info( "Renderbuffers are not supported by this OpenGL implementation; skipping test\n" );
        return 0;
    }

    // Loop through a set of GL formats, testing a set of sizes against each one
    for( fmtIdx = 0; fmtIdx < sizeof( formats ) / sizeof( formats[ 0 ] ); fmtIdx++ )
    {
        for( attIdx = 0; attIdx < sizeof( attachments ) / sizeof( attachments[ 0 ] ); attIdx++ )
        {
            log_info( "Testing Renderbuffer write test for %s : %s : %s : %s\n",
                GetGLAttachmentName( attachments[ attIdx ] ),
                GetGLFormatName( formats[ fmtIdx ].internal ),
                GetGLBaseFormatName( formats[ fmtIdx ].format ),
                GetGLTypeName( formats[ fmtIdx ].datatype ) );

            size_t i;
            for( i = 0; i < iter; i++ )
            {
                GLsizei width = random_in_range( 16, 512, seed );
                GLsizei height = random_in_range( 16, 512, seed );
#ifdef DEBUG
                width = height = 4;
#endif

                if( test_renderbuffer_image_write( context, queue, width, height,
                                                   attachments[ attIdx ],
                                                   formats[ fmtIdx ].format,
                                                   formats[ fmtIdx ].internal,
                                                   formats[ fmtIdx ].datatype,
                                                   formats[ fmtIdx ].type, seed ) )
                {
                    log_error( "ERROR: Renderbuffer write test failed for %s : %s : %s : %s\n\n",
                          GetGLAttachmentName( attachments[ attIdx ] ),
                          GetGLFormatName( formats[ fmtIdx ].internal ),
                          GetGLBaseFormatName( formats[ fmtIdx ].format ),
                          GetGLTypeName( formats[ fmtIdx ].datatype ) );

                    error++;
                    break;    // Skip other sizes for this combination
                }
            }
            if( i == iter )
            {
                log_info( "passed: Renderbuffer write test passed for %s : %s : %s : %s\n\n",
                          GetGLAttachmentName( attachments[ attIdx ] ),
                          GetGLFormatName( formats[ fmtIdx ].internal ),
                          GetGLBaseFormatName( formats[ fmtIdx ].format ),
                          GetGLTypeName( formats[ fmtIdx ].datatype ) );
            }
        }
    }

    return error;
}
