//
// 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 "../../test_common/harness/compat.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "procs.h"



cl_int get_type_size( cl_context context, cl_command_queue queue, const char *type, cl_ulong *size  )
{
    const char *sizeof_kernel_code[4] =
    {
        "", /* optional pragma string */
        "__kernel __attribute__((reqd_work_group_size(1,1,1))) void test_sizeof(__global uint *dst) \n"
        "{\n"
        "   dst[0] = (uint) sizeof( ", type, " );\n"
        "}\n"
    };

    cl_program  p;
    cl_kernel   k;
    cl_mem      m;
    cl_uint        temp;


    if (!strncmp(type, "double", 6))
    {
        sizeof_kernel_code[0] = "#pragma OPENCL EXTENSION cl_khr_fp64 : enable\n";
    }
    else if (!strncmp(type, "half", 4))
    {
        sizeof_kernel_code[0] = "#pragma OPENCL EXTENSION cl_khr_fp16 : enable\n";
    }

    cl_int err = create_single_kernel_helper_with_build_options(context, &p, &k, 4, sizeof_kernel_code, "test_sizeof", "-cl-std=CL2.0");
    if( err )
        return err;

    m = clCreateBuffer( context, CL_MEM_WRITE_ONLY | CL_MEM_COPY_HOST_PTR, sizeof( cl_ulong ), size, &err );
    if( NULL == m )
    {
        clReleaseProgram( p );
        clReleaseKernel( k );
        log_error("\nclCreateBuffer FAILED\n");
        return err;
    }

    err = clSetKernelArg( k, 0, sizeof( cl_mem ), &m );
    if( err )
    {
        clReleaseProgram( p );
        clReleaseKernel( k );
        clReleaseMemObject( m );
        log_error("\nclSetKernelArg FAILED\n");
        return err;
    }

    err = clEnqueueTask( queue, k, 0, NULL, NULL );
    clReleaseProgram( p );
    clReleaseKernel( k );
    if( err )
    {
        clReleaseMemObject( m );
        log_error( "\nclEnqueueTask FAILED\n" );
        return err;
    }

    err = clEnqueueReadBuffer( queue, m, CL_TRUE, 0, sizeof( cl_uint ), &temp, 0, NULL, NULL );
    clReleaseMemObject( m );
    if( err )
        log_error( "\nclEnqueueReadBuffer FAILED\n" );

    *size = (cl_ulong) temp;

    return err;
}

typedef struct size_table
{
    const char *name;
    cl_ulong   size;
    cl_ulong   cl_size;
}size_table;

const size_table  scalar_table[] =
{
    // Fixed size entries from table 6.1
    {  "char",              1,  sizeof( cl_char )   },
    {  "uchar",             1,  sizeof( cl_uchar)   },
    {  "unsigned char",     1,  sizeof( cl_uchar)   },
    {  "short",             2,  sizeof( cl_short)   },
    {  "ushort",            2,  sizeof( cl_ushort)  },
    {  "unsigned short",    2,  sizeof( cl_ushort)  },
    {  "int",               4,  sizeof( cl_int )    },
    {  "uint",              4,  sizeof( cl_uint)    },
    {  "unsigned int",      4,  sizeof( cl_uint)    },
    {  "float",             4,  sizeof( cl_float)   },
    {  "long",              8,  sizeof( cl_long )   },
    {  "ulong",             8,  sizeof( cl_ulong)   },
    {  "unsigned long",     8,  sizeof( cl_ulong)   }
};

const size_table  vector_table[] =
{
    // Fixed size entries from table 6.1
    {  "char",      1,  sizeof( cl_char )   },
    {  "uchar",     1,  sizeof( cl_uchar)   },
    {  "short",     2,  sizeof( cl_short)   },
    {  "ushort",    2,  sizeof( cl_ushort)  },
    {  "int",       4,  sizeof( cl_int )    },
    {  "uint",      4,  sizeof( cl_uint)    },
    {  "float",     4,  sizeof( cl_float)   },
    {  "long",      8,  sizeof( cl_long )   },
    {  "ulong",     8,  sizeof( cl_ulong)   }
};

const char  *ptr_table[] =
{
    "void*",
    "size_t",
    "sizeof(int)",      // check return type of sizeof
    "ptrdiff_t"
};

const char *other_types[] =
{
    "event_t",
    "image2d_t",
    "image3d_t",
    "sampler_t"
};

static int IsPowerOfTwo( cl_ulong x ){ return 0 == (x & (x-1)); }

int test_sizeof(cl_device_id device, cl_context context, cl_command_queue queue, int num_elements)
{
    size_t i, j;
    cl_ulong test;
    cl_uint ptr_size = CL_UINT_MAX;
    cl_int err = CL_SUCCESS;

    // Check address space size
    err = clGetDeviceInfo(device, CL_DEVICE_ADDRESS_BITS, sizeof(ptr_size), &ptr_size, NULL);
    if( err || ptr_size > 64)
    {
        log_error( "FAILED:  Unable to get CL_DEVICE_ADDRESS_BITS for device %p\n", device );
        return -1;
    }
    log_info( "\tCL_DEVICE_ADDRESS_BITS = %u\n", ptr_size );
    ptr_size /= 8;

    // Test standard scalar sizes
    for( i = 0; i < sizeof( scalar_table ) / sizeof( scalar_table[0] ); i++ )
    {
        if( ! gHasLong &&
           (0 == strcmp(scalar_table[i].name, "long") ||
            0 == strcmp(scalar_table[i].name, "ulong") ||
            0 == strcmp(scalar_table[i].name, "unsigned long")))
        {
            log_info("\nLongs are not supported by this device. Skipping test.\t");
            continue;
        }

        test = CL_ULONG_MAX;
        err = get_type_size( context, queue, scalar_table[i].name, &test  );
        if( err )
            return err;
        if( test != scalar_table[i].size )
        {
            log_error( "\nFAILED: Type %s has size %lld, but expected size %lld!\n", scalar_table[i].name, test, scalar_table[i].size );
            return -1;
        }
        if( test != scalar_table[i].cl_size )
        {
            log_error( "\nFAILED: Type %s has size %lld, but cl_ size is %lld!\n", scalar_table[i].name, test, scalar_table[i].cl_size );
            return -2;
        }
        log_info( "%16s", scalar_table[i].name );
    }
    log_info( "\n" );

    // Test standard vector sizes
    for( j = 2; j <= 16; j *= 2 )
    {
        // For each vector size, iterate through types
        for( i = 0; i < sizeof( vector_table ) / sizeof( vector_table[0] ); i++ )
        {
            if( !gHasLong &&
               (0 == strcmp(vector_table[i].name, "long") ||
                0 == strcmp(vector_table[i].name, "ulong")))
            {
                log_info("\nLongs are not supported by this device. Skipping test.\t");
                continue;
            }

            char name[32];
            sprintf( name, "%s%ld", vector_table[i].name, j );

            test = CL_ULONG_MAX;
            err = get_type_size( context, queue, name, &test  );
            if( err )
                return err;
            if( test != j * vector_table[i].size )
            {
                log_error( "\nFAILED: Type %s has size %lld, but expected size %lld!\n", name, test, j * vector_table[i].size );
                return -1;
            }
            if( test != j * vector_table[i].cl_size )
            {
                log_error( "\nFAILED: Type %s has size %lld, but cl_ size is %lld!\n", name, test, j * vector_table[i].cl_size );
                return -2;
            }
            log_info( "%16s", name );
        }
        log_info( "\n" );
    }

    //Check that pointer sizes are correct
    for( i = 0; i < sizeof( ptr_table ) / sizeof( ptr_table[0] ); i++ )
    {
        test = CL_ULONG_MAX;
        err = get_type_size( context, queue, ptr_table[i], &test  );
        if( err )
            return err;
        if( test != ptr_size )
        {
            log_error( "\nFAILED: Type %s has size %lld, but expected size %u!\n", ptr_table[i], test, ptr_size );
            return -1;
        }
        log_info( "%16s", ptr_table[i] );
    }

    // Check that intptr_t is large enough
    test = CL_ULONG_MAX;
    err = get_type_size( context, queue, "intptr_t", &test  );
    if( err )
        return err;
    if( test < ptr_size )
    {
        log_error( "\nFAILED: intptr_t has size %lld, but must be at least %u!\n", test, ptr_size );
        return -1;
    }
    if( ! IsPowerOfTwo( test ) )
    {
        log_error( "\nFAILED: sizeof(intptr_t) is %lld, but must be a power of two!\n", test );
        return -2;
    }
    log_info( "%16s", "intptr_t" );

    // Check that uintptr_t is large enough
    test = CL_ULONG_MAX;
    err = get_type_size( context, queue, "uintptr_t", &test  );
    if( err )
        return err;
    if( test < ptr_size )
    {
        log_error( "\nFAILED: uintptr_t has size %lld, but must be at least %u!\n", test, ptr_size );
        return -1;
    }
    if( ! IsPowerOfTwo( test ) )
    {
        log_error( "\nFAILED: sizeof(uintptr_t) is %lld, but must be a power of two!\n", test );
        return -2;
    }
    log_info( "%16s\n", "uintptr_t" );

    //Check that other types are powers of two
    for( i = 0; i < sizeof( other_types ) / sizeof( other_types[0] ); i++ )
    {
        if( 0 == strcmp(other_types[i], "image2d_t") &&
           checkForImageSupport( device ) == CL_IMAGE_FORMAT_NOT_SUPPORTED)
        {
            log_info("\nimages are not supported by this device. Skipping test.\t");
            continue;
        }

        if( gIsEmbedded &&
           0 == strcmp(other_types[i], "image3d_t") &&
           checkFor3DImageSupport( device ) == CL_IMAGE_FORMAT_NOT_SUPPORTED)
        {
            log_info("\n3D images are not supported by this device. Skipping test.\t");
            continue;
        }

        if( 0 == strcmp(other_types[i], "sampler_t") &&
           checkForImageSupport( device ) == CL_IMAGE_FORMAT_NOT_SUPPORTED)
        {
          log_info("\nimages are not supported by this device. Skipping test.\t");
          continue;
        }

        test = CL_ULONG_MAX;
        err = get_type_size( context, queue, other_types[i], &test  );
        if( err )
            return err;
        if( ! IsPowerOfTwo( test ) )
        {
            log_error( "\nFAILED: Type %s has size %lld, which is not a power of two (section 6.1.5)!\n", other_types[i], test );
            return -1;
        }
        log_info( "%16s", other_types[i] );
    }
    log_info( "\n" );


    //Check double
    if( is_extension_available( device, "cl_khr_fp64" ) )
    {
        log_info( "\tcl_khr_fp64:" );
        test = CL_ULONG_MAX;
        err = get_type_size( context, queue, "double", &test  );
        if( err )
            return err;
        if( test != 8 )
        {
            log_error( "\nFAILED: double has size %lld, but must be 8!\n", test );
            return -1;
        }
        log_info( "%16s", "double" );

        // Test standard vector sizes
        for( j = 2; j <= 16; j *= 2 )
        {
            char name[32];
            sprintf( name, "double%ld", j );

            test = CL_ULONG_MAX;
            err = get_type_size( context, queue, name, &test  );
            if( err )
                return err;
            if( test != 8*j )
            {
                log_error( "\nFAILED: %s has size %lld, but must be %ld!\n", name, test, 8 * j);
                return -1;
            }
            log_info( "%16s", name );
        }
        log_info( "\n" );
    }

    //Check half
    if( is_extension_available( device, "cl_khr_fp16" ) )
    {
        log_info( "\tcl_khr_fp16:" );
        test = CL_ULONG_MAX;
        err = get_type_size( context, queue, "half", &test  );
        if( err )
            return err;
        if( test != 2 )
        {
            log_error( "\nFAILED: half has size %lld, but must be 2!\n", test );
            return -1;
        }
        log_info( "%16s", "half" );

        // Test standard vector sizes
        for( j = 2; j <= 16; j *= 2 )
        {
            char name[32];
            sprintf( name, "half%ld", j );

            test = CL_ULONG_MAX;
            err = get_type_size( context, queue, name, &test  );
            if( err )
                return err;
            if( test != 2*j )
            {
                log_error( "\nFAILED: %s has size %lld, but must be %ld!\n", name, test, 2 * j);
                return -1;
            }
            log_info( "%16s", name );
        }
        log_info( "\n" );
    }

    return err;
}


