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


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