//
// 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 "../../test_common/harness/typeWrappers.h"
#include "../../test_common/harness/testHarness.h"

extern cl_uint gRandomSeed;


#define TEST_MEM_OBJECT_PARAM( mem, paramName, val, expected, name, type, cast )    \
error = clGetMemObjectInfo( mem, paramName, sizeof( val ), &val, &size );   \
test_error( error, "Unable to get mem object " name );  \
if( val != expected )   \
{   \
log_error( "ERROR: Mem object " name " did not validate! (expected " type ", got " type " from %s:%d)\n",   \
expected, (cast)val, __FILE__, __LINE__ );   \
return -1;  \
}   \
if( size != sizeof( val ) ) \
{   \
log_error( "ERROR: Returned size of mem object " name " does not validate! (expected %d, got %d from %s:%d)\n", \
(int)sizeof( val ), (int)size , __FILE__, __LINE__ );   \
return -1;  \
}

static void CL_CALLBACK mem_obj_destructor_callback( cl_mem, void * data )
{
    free( data );
}

static unsigned int
get_image_dim(MTdata *d, unsigned int mod)
{
    unsigned int val = 0;

    do
    {
        val = (unsigned int)genrand_int32(*d) % mod;
    } while (val == 0);

    return val;
}


int test_get_buffer_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    int error;
    size_t size;
    void * buffer = NULL;

    clMemWrapper bufferObject;
    clMemWrapper subBufferObject;

    cl_mem_flags bufferFlags[] = {
        CL_MEM_READ_WRITE,
        CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_READ_ONLY,
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_WRITE_ONLY,
        CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
    };

    cl_mem_flags subBufferFlags[] = {
        CL_MEM_READ_WRITE,
        CL_MEM_READ_ONLY,
        CL_MEM_WRITE_ONLY,
        0,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_READ_ONLY | 0,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_WRITE_ONLY | 0,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_NO_ACCESS | 0,
    };


    // Get the address alignment, so we can make sure the sub-buffer test later works properly.
    cl_uint addressAlignBits;
    error = clGetDeviceInfo( deviceID, CL_DEVICE_MEM_BASE_ADDR_ALIGN, sizeof(addressAlignBits), &addressAlignBits, NULL );

    size_t addressAlign = addressAlignBits/8;
    if ( addressAlign < 128 )
    {
        addressAlign = 128;
    }

    for ( unsigned int i = 0; i < sizeof(bufferFlags) / sizeof(cl_mem_flags); ++i )
    {
        //printf("@@@ bufferFlags[%u]=0x%x\n", i, bufferFlags[ i ]);
        if ( bufferFlags[ i ] & CL_MEM_USE_HOST_PTR )
        {
            // Create a buffer object to test against.
            buffer = malloc( addressAlign * 4 );
            bufferObject = clCreateBuffer( context, bufferFlags[ i ], addressAlign * 4, buffer, &error );
            if ( error )
            {
                free( buffer );
                test_error( error, "Unable to create buffer (CL_MEM_USE_HOST_PTR) to test with" );
            }

            // Make sure buffer is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( bufferObject, mem_obj_destructor_callback, buffer );
            test_error( error, "Unable to set mem object destructor callback" );

            void * ptr;
            TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_HOST_PTR, ptr, buffer, "host pointer", "%p", void * )
        }
        else if ( (bufferFlags[ i ] & CL_MEM_ALLOC_HOST_PTR) && (bufferFlags[ i ] & CL_MEM_COPY_HOST_PTR) )
        {
            // Create a buffer object to test against.
            buffer = malloc( addressAlign * 4 );
            bufferObject = clCreateBuffer( context, bufferFlags[ i ], addressAlign * 4, buffer, &error );
            if ( error )
            {
                free( buffer );
                test_error( error, "Unable to create buffer (CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure buffer is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( bufferObject, mem_obj_destructor_callback, buffer );
            test_error( error, "Unable to set mem object destructor callback" );
        }
        else if ( bufferFlags[ i ] & CL_MEM_ALLOC_HOST_PTR )
        {
            // Create a buffer object to test against.
            bufferObject = clCreateBuffer( context, bufferFlags[ i ], addressAlign * 4, NULL, &error );
            test_error( error, "Unable to create buffer (CL_MEM_ALLOC_HOST_PTR) to test with" );
        }
        else if ( bufferFlags[ i ] & CL_MEM_COPY_HOST_PTR )
        {
            // Create a buffer object to test against.
            buffer = malloc( addressAlign * 4 );
            bufferObject = clCreateBuffer( context, bufferFlags[ i ], addressAlign * 4, buffer, &error );
            if ( error )
            {
                free( buffer );
                test_error( error, "Unable to create buffer (CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure buffer is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( bufferObject, mem_obj_destructor_callback, buffer );
            test_error( error, "Unable to set mem object destructor callback" );
        }
        else
        {
            // Create a buffer object to test against.
            bufferObject = clCreateBuffer( context, bufferFlags[ i ], addressAlign * 4, NULL, &error );
            test_error( error, "Unable to create buffer to test with" );
        }

        // Perform buffer object queries.
        cl_mem_object_type type;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_TYPE, type, CL_MEM_OBJECT_BUFFER, "type", "%d", int )

        cl_mem_flags flags;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_FLAGS, flags, (unsigned int)bufferFlags[ i ], "flags", "%d", unsigned int )

        size_t sz;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_SIZE, sz, (size_t)( addressAlign * 4 ), "size", "%ld", size_t )

        cl_uint mapCount;
        error = clGetMemObjectInfo( bufferObject, CL_MEM_MAP_COUNT, sizeof( mapCount ), &mapCount, &size );
        test_error( error, "Unable to get mem object map count" );
        if( size != sizeof( mapCount ) )
        {
            log_error( "ERROR: Returned size of mem object map count does not validate! (expected %d, got %d from %s:%d)\n",
                      (int)sizeof( mapCount ), (int)size, __FILE__, __LINE__ );
            return -1;
        }

        cl_uint refCount;
        error = clGetMemObjectInfo( bufferObject, CL_MEM_REFERENCE_COUNT, sizeof( refCount ), &refCount, &size );
        test_error( error, "Unable to get mem object reference count" );
        if( size != sizeof( refCount ) )
        {
            log_error( "ERROR: Returned size of mem object reference count does not validate! (expected %d, got %d from %s:%d)\n",
                      (int)sizeof( refCount ), (int)size, __FILE__, __LINE__ );
            return -1;
        }

        cl_context otherCtx;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_CONTEXT, otherCtx, context, "context", "%p", cl_context )

        cl_mem origObj;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_ASSOCIATED_MEMOBJECT, origObj, (void *)NULL, "associated mem object", "%p", void * )

        size_t offset;
        TEST_MEM_OBJECT_PARAM( bufferObject, CL_MEM_OFFSET, offset, 0L, "offset", "%ld", size_t )

        cl_buffer_region region;
        region.origin = addressAlign;
        region.size = addressAlign;

        // Loop over possible sub-buffer objects to create.
        for ( unsigned int j = 0; j < sizeof(subBufferFlags) / sizeof(cl_mem_flags); ++j )
        {
            if ( subBufferFlags[ j ] & CL_MEM_READ_WRITE )
            {
                if ( !(bufferFlags[ i ] & CL_MEM_READ_WRITE) )
                    continue; // Buffer must be read_write for sub-buffer to be read_write.
            }
            if ( subBufferFlags[ j ] & CL_MEM_READ_ONLY )
            {
                if ( !(bufferFlags[ i ] & CL_MEM_READ_WRITE) && !(bufferFlags[ i ] & CL_MEM_READ_ONLY) )
                    continue; // Buffer must be read_write or read_only for sub-buffer to be read_only
            }
            if ( subBufferFlags[ j ] & CL_MEM_WRITE_ONLY )
            {
                if ( !(bufferFlags[ i ] & CL_MEM_READ_WRITE) && !(bufferFlags[ i ] & CL_MEM_WRITE_ONLY) )
                    continue; // Buffer must be read_write or write_only for sub-buffer to be write_only
            }
            if ( subBufferFlags[ j ] & CL_MEM_HOST_READ_ONLY )
            {
                if ( (bufferFlags[ i ] & CL_MEM_HOST_NO_ACCESS) || (bufferFlags[ i ] & CL_MEM_HOST_WRITE_ONLY) )
                    continue; // Buffer must be host all access or host read_only for sub-buffer to be host read_only
            }
            if ( subBufferFlags[ j ] & CL_MEM_HOST_WRITE_ONLY )
            {
                if ( (bufferFlags[ i ] & CL_MEM_HOST_NO_ACCESS) || (bufferFlags[ i ] & CL_MEM_HOST_READ_ONLY) )
                    continue; // Buffer must be host all access or host write_only for sub-buffer to be host write_only
            }
            //printf("@@@ bufferFlags[%u]=0x%x subBufferFlags[%u]=0x%x\n", i, bufferFlags[ i ], j, subBufferFlags[ j ]);

            subBufferObject = clCreateSubBuffer( bufferObject, subBufferFlags[ j ], CL_BUFFER_CREATE_TYPE_REGION, &region, &error );
            test_error( error, "Unable to create sub-buffer to test against" );

            // Perform sub-buffer object queries.
            cl_mem_object_type type;
            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_TYPE, type, CL_MEM_OBJECT_BUFFER, "type", "%d", int )

            cl_mem_flags flags;
            cl_mem_flags inheritedFlags = subBufferFlags[ j ];
            if ( (subBufferFlags[ j ] & (CL_MEM_READ_WRITE | CL_MEM_READ_ONLY | CL_MEM_WRITE_ONLY)) == 0 )
            {
              inheritedFlags |= bufferFlags[ i ] & (CL_MEM_READ_WRITE | CL_MEM_READ_ONLY | CL_MEM_WRITE_ONLY);
            }
            inheritedFlags |= bufferFlags[ i ] & (CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR | CL_MEM_USE_HOST_PTR);
            if ( (subBufferFlags[ j ] & (CL_MEM_HOST_READ_ONLY | CL_MEM_HOST_WRITE_ONLY | CL_MEM_HOST_NO_ACCESS)) == 0)
            {
              inheritedFlags |= bufferFlags[ i ] & (CL_MEM_HOST_READ_ONLY | CL_MEM_HOST_WRITE_ONLY | CL_MEM_HOST_NO_ACCESS);
            }
            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_FLAGS, flags, (unsigned int)inheritedFlags, "flags", "%d", unsigned int )

            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_SIZE, sz, (size_t)( addressAlign ), "size", "%ld", size_t )

            if ( bufferFlags[ i ] & CL_MEM_USE_HOST_PTR )
            {
                void * ptr;
                void * offsetInBuffer = (char *)buffer + addressAlign;

                TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_HOST_PTR, ptr, offsetInBuffer, "host pointer", "%p", void * )
            }

            cl_uint mapCount;
            error = clGetMemObjectInfo( subBufferObject, CL_MEM_MAP_COUNT, sizeof( mapCount ), &mapCount, &size );
            test_error( error, "Unable to get mem object map count" );
            if( size != sizeof( mapCount ) )
            {
                log_error( "ERROR: Returned size of mem object map count does not validate! (expected %d, got %d from %s:%d)\n",
                          (int)sizeof( mapCount ), (int)size, __FILE__, __LINE__ );
                return -1;
            }

            cl_uint refCount;
            error = clGetMemObjectInfo( subBufferObject, CL_MEM_REFERENCE_COUNT, sizeof( refCount ), &refCount, &size );
            test_error( error, "Unable to get mem object reference count" );
            if( size != sizeof( refCount ) )
            {
                log_error( "ERROR: Returned size of mem object reference count does not validate! (expected %d, got %d from %s:%d)\n",
                          (int)sizeof( refCount ), (int)size, __FILE__, __LINE__ );
                return -1;
            }

            cl_context otherCtx;
            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_CONTEXT, otherCtx, context, "context", "%p", cl_context )

            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_ASSOCIATED_MEMOBJECT, origObj, (cl_mem)bufferObject, "associated mem object", "%p", void * )

            TEST_MEM_OBJECT_PARAM( subBufferObject, CL_MEM_OFFSET, offset, (size_t)( addressAlign ), "offset", "%ld", size_t )

            clReleaseMemObject( subBufferObject );
            subBufferObject = NULL;

        }

        clReleaseMemObject( bufferObject );
        bufferObject = NULL;
    }

    return CL_SUCCESS;
}


int test_get_imageObject_info( cl_mem * image, cl_mem_flags objectFlags, cl_image_desc *imageInfo, cl_image_format *imageFormat, size_t pixelSize, cl_context context )
{
    int error;
    size_t size;
    cl_mem_object_type type;
    cl_mem_flags flags;
    cl_uint mapCount;
    cl_uint refCount;
    size_t rowPitchMultiplier;
    size_t slicePitchMultiplier;
    cl_context otherCtx;
    size_t offset;
    size_t sz;

    TEST_MEM_OBJECT_PARAM( *image, CL_MEM_TYPE, type, imageInfo->image_type, "type", "%d", int )

    TEST_MEM_OBJECT_PARAM( *image, CL_MEM_FLAGS, flags, (unsigned int)objectFlags, "flags", "%d", unsigned int )

    error = clGetMemObjectInfo( *image, CL_MEM_SIZE, sizeof( sz ), &sz, NULL );
    test_error( error, "Unable to get mem size" );

    // The size returned is not constrained by the spec.

    error = clGetMemObjectInfo( *image, CL_MEM_MAP_COUNT, sizeof( mapCount ), &mapCount, &size );
    test_error( error, "Unable to get mem object map count" );
    if( size != sizeof( mapCount ) )
    {
        log_error( "ERROR: Returned size of mem object map count does not validate! (expected %d, got %d from %s:%d)\n",
                  (int)sizeof( mapCount ), (int)size, __FILE__, __LINE__ );
        return -1;
    }

    error = clGetMemObjectInfo( *image, CL_MEM_REFERENCE_COUNT, sizeof( refCount ), &refCount, &size );
    test_error( error, "Unable to get mem object reference count" );
    if( size != sizeof( refCount ) )
    {
        log_error( "ERROR: Returned size of mem object reference count does not validate! (expected %d, got %d from %s:%d)\n",
                  (int)sizeof( refCount ), (int)size, __FILE__, __LINE__ );
        return -1;
    }

    TEST_MEM_OBJECT_PARAM( *image, CL_MEM_CONTEXT, otherCtx, context, "context", "%p", cl_context )

    TEST_MEM_OBJECT_PARAM( *image, CL_MEM_OFFSET, offset, 0L, "offset", "%ld", size_t )

    return CL_SUCCESS;
}


int test_get_image_info( cl_device_id deviceID, cl_context context, cl_mem_object_type type )
{
    int error;
    size_t size;
    void * image = NULL;

    cl_mem imageObject;
    cl_image_desc imageInfo;

    cl_mem_flags imageFlags[] = {
        CL_MEM_READ_WRITE,
        CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_READ_ONLY,
        CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_WRITE_ONLY,
        CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_READ_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_WRITE_ONLY | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_READ_ONLY | CL_MEM_USE_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR,
        CL_MEM_HOST_NO_ACCESS | CL_MEM_WRITE_ONLY | CL_MEM_USE_HOST_PTR,
    };
    MTdata d;

    PASSIVE_REQUIRE_IMAGE_SUPPORT( deviceID )

    cl_image_format imageFormat;
    size_t pixelSize = 4;

    imageFormat.image_channel_order = CL_RGBA;
    imageFormat.image_channel_data_type = CL_UNORM_INT8;

    imageInfo.image_width = imageInfo.image_height = imageInfo.image_depth = 1;
    imageInfo.image_array_size = 0;
    imageInfo.num_mip_levels = imageInfo.num_samples = 0;
    imageInfo.mem_object = NULL;

    d = init_genrand( gRandomSeed );

    for ( unsigned int i = 0; i < sizeof(imageFlags) / sizeof(cl_mem_flags); ++i )
    {
        imageInfo.image_row_pitch = 0;
        imageInfo.image_slice_pitch = 0;

        switch (type)
        {
            case CL_MEM_OBJECT_IMAGE1D:
                imageInfo.image_width = get_image_dim(&d, 1023);
                imageInfo.image_type = CL_MEM_OBJECT_IMAGE1D;
                break;

            case CL_MEM_OBJECT_IMAGE2D:
                imageInfo.image_width = get_image_dim(&d, 1023);
                imageInfo.image_height = get_image_dim(&d, 1023);
                imageInfo.image_type = CL_MEM_OBJECT_IMAGE2D;
                break;

            case CL_MEM_OBJECT_IMAGE3D:
                error = checkFor3DImageSupport(deviceID);
                if (error == CL_IMAGE_FORMAT_NOT_SUPPORTED)
                {
                    log_info("Device doesn't support 3D images. Skipping test.\n");
                    return CL_SUCCESS;
                }
                imageInfo.image_width = get_image_dim(&d, 127);
                imageInfo.image_height = get_image_dim(&d, 127);
                imageInfo.image_depth = get_image_dim(&d, 127);
                imageInfo.image_type = CL_MEM_OBJECT_IMAGE3D;
                break;

            case CL_MEM_OBJECT_IMAGE1D_ARRAY:
                imageInfo.image_width = get_image_dim(&d, 1023);
                imageInfo.image_array_size = get_image_dim(&d, 1023);
                imageInfo.image_type = CL_MEM_OBJECT_IMAGE1D_ARRAY;
                break;

            case CL_MEM_OBJECT_IMAGE2D_ARRAY:
                imageInfo.image_width = get_image_dim(&d, 255);
                imageInfo.image_height = get_image_dim(&d, 255);
                imageInfo.image_array_size = get_image_dim(&d, 255);
                imageInfo.image_type = CL_MEM_OBJECT_IMAGE2D_ARRAY;
                break;
        }

        if ( imageFlags[i] & CL_MEM_USE_HOST_PTR )
        {
            // Create an image object to test against.
            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image with (CL_MEM_USE_HOST_PTR) to test with" );
            }

            // Make sure image is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );

            void * ptr;
            TEST_MEM_OBJECT_PARAM( imageObject, CL_MEM_HOST_PTR, ptr, image, "host pointer", "%p", void * )
            int ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

            // release image object
            clReleaseMemObject(imageObject);

            // Try again with non-zero rowPitch.
            imageInfo.image_row_pitch = imageInfo.image_width * pixelSize;
            switch (type)
            {
                case CL_MEM_OBJECT_IMAGE1D_ARRAY:
                case CL_MEM_OBJECT_IMAGE2D_ARRAY:
                case CL_MEM_OBJECT_IMAGE3D:
                    imageInfo.image_slice_pitch = imageInfo.image_row_pitch * imageInfo.image_height;
                    break;
            }

            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image2d (CL_MEM_USE_HOST_PTR) to test with" );
            }

            // Make sure image2d is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );

            TEST_MEM_OBJECT_PARAM( imageObject, CL_MEM_HOST_PTR, ptr, image, "host pointer", "%p", void * )
            ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

        }
        else if ( (imageFlags[i] & CL_MEM_ALLOC_HOST_PTR) && (imageFlags[i] & CL_MEM_COPY_HOST_PTR) )
        {
            // Create an image object to test against.
            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image with (CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure image is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );
            int ret = test_get_imageObject_info( &imageObject, imageFlags[ i ], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

            // release image object
            clReleaseMemObject(imageObject);

            // Try again with non-zero rowPitch.
            imageInfo.image_row_pitch = imageInfo.image_width * pixelSize;
            switch (type)
            {
                case CL_MEM_OBJECT_IMAGE1D_ARRAY:
                case CL_MEM_OBJECT_IMAGE2D_ARRAY:
                case CL_MEM_OBJECT_IMAGE3D:
                    imageInfo.image_slice_pitch = imageInfo.image_row_pitch * imageInfo.image_height;
                    break;
            }

            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image with (CL_MEM_ALLOC_HOST_PTR | CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure image is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );
            ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

        }
        else if ( imageFlags[i] & CL_MEM_ALLOC_HOST_PTR )
        {
            // Create an image object to test against.
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, NULL, &error );
            test_error( error, "Unable to create image with (CL_MEM_ALLOC_HOST_PTR) to test with" );
            int ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

        }
        else if ( imageFlags[i] & CL_MEM_COPY_HOST_PTR )
        {
            // Create an image object to test against.
            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image with (CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure image is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );
            int ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

            clReleaseMemObject(imageObject);

            // Try again with non-zero rowPitch.
            imageInfo.image_row_pitch = imageInfo.image_width * pixelSize;
            switch (type)
            {
                case CL_MEM_OBJECT_IMAGE1D_ARRAY:
                case CL_MEM_OBJECT_IMAGE2D_ARRAY:
                case CL_MEM_OBJECT_IMAGE3D:
                    imageInfo.image_slice_pitch = imageInfo.image_row_pitch * imageInfo.image_height;
                    break;
            }

            image = malloc( imageInfo.image_width * imageInfo.image_height * imageInfo.image_depth * pixelSize *
                           ((imageInfo.image_array_size == 0) ? 1 : imageInfo.image_array_size) );
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, image, &error );
            if ( error )
            {
                free( image );
                test_error( error, "Unable to create image with (CL_MEM_COPY_HOST_PTR) to test with" );
            }

            // Make sure image is cleaned up appropriately if we encounter an error in the rest of the calls.
            error = clSetMemObjectDestructorCallback( imageObject, mem_obj_destructor_callback, image );
            test_error( error, "Unable to set mem object destructor callback" );
            ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

        }
        else
        {
            // Create an image object to test against.
            imageObject = clCreateImage( context, imageFlags[i], &imageFormat, &imageInfo, NULL, &error );
            test_error( error, "Unable to create image to test with" );
            int ret = test_get_imageObject_info( &imageObject, imageFlags[i], &imageInfo, &imageFormat, pixelSize, context );
            if (ret)
                return ret;

        }

        clReleaseMemObject( imageObject );
    }

    return CL_SUCCESS;
}


int test_get_image2d_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    return test_get_image_info(deviceID, context, CL_MEM_OBJECT_IMAGE2D);
}

int test_get_image3d_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    return test_get_image_info(deviceID, context, CL_MEM_OBJECT_IMAGE3D);
}

int test_get_image1d_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    return test_get_image_info(deviceID, context, CL_MEM_OBJECT_IMAGE1D);
}

int test_get_image1d_array_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    return test_get_image_info(deviceID, context, CL_MEM_OBJECT_IMAGE1D_ARRAY);
}

int test_get_image2d_array_info( cl_device_id deviceID, cl_context context, cl_command_queue ignoreQueue, int num_elements )
{
    return test_get_image_info(deviceID, context, CL_MEM_OBJECT_IMAGE2D_ARRAY);
}


