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

#ifndef _WIN32
#include <unistd.h>
#endif

#include "harness/conversions.h"

static void CL_CALLBACK test_native_kernel_fn( void *userData )
{
    struct arg_struct {
        cl_int * source;
        cl_int * dest;
        cl_int count;
    } *args = (arg_struct *)userData;

    for( cl_int i = 0; i < args->count; i++ )
        args->dest[ i ] = args->source[ i ];
}

REGISTER_TEST(native_kernel)
{
    int error;
    RandomSeed seed( gRandomSeed );
    // Check if we support native kernels
    cl_device_exec_capabilities capabilities;
    error = clGetDeviceInfo(device, CL_DEVICE_EXECUTION_CAPABILITIES, sizeof(capabilities), &capabilities, NULL);
    if (!(capabilities & CL_EXEC_NATIVE_KERNEL)) {
        log_info("Device does not support CL_EXEC_NATIVE_KERNEL.\n");
        return 0;
    }

    clMemWrapper streams[ 2 ];
    std::vector<cl_int> inBuffer(num_elements), outBuffer(num_elements);
    clEventWrapper finishEvent;

    struct arg_struct
    {
        cl_mem inputStream;
        cl_mem outputStream;
        cl_int count;
    } args;


    // Create some input values
    generate_random_data(kInt, num_elements, seed, inBuffer.data());

    // Create I/O streams
    streams[0] =
        clCreateBuffer(context, CL_MEM_COPY_HOST_PTR,
                       num_elements * sizeof(cl_int), inBuffer.data(), &error);
    test_error( error, "Unable to create I/O stream" );
    streams[1] =
        clCreateBuffer(context, 0, num_elements * sizeof(cl_int), NULL, &error);
    test_error( error, "Unable to create I/O stream" );


    // Set up the arrays to call with
    args.inputStream = streams[ 0 ];
    args.outputStream = streams[ 1 ];
    args.count = num_elements;

    void * memLocs[ 2 ] = { &args.inputStream, &args.outputStream };


    // Run the kernel
    error = clEnqueueNativeKernel( queue, test_native_kernel_fn,
                                      &args, sizeof( args ),
                                      2, &streams[ 0 ],
                                      (const void **)memLocs,
                                      0, NULL, &finishEvent );
    test_error( error, "Unable to queue native kernel" );

    // Finish and wait for the kernel to complete
    error = clFinish( queue );
    test_error(error, "clFinish failed");

    error = clWaitForEvents( 1, &finishEvent );
    test_error(error, "clWaitForEvents failed");

    // Now read the results and verify
    error = clEnqueueReadBuffer(queue, streams[1], CL_TRUE, 0,
                                num_elements * sizeof(cl_int), outBuffer.data(),
                                0, NULL, NULL);
    test_error( error, "Unable to read results" );

    for (int i = 0; i < num_elements; i++)
    {
        if (inBuffer[i] != outBuffer[i])
        {
            log_error("ERROR: Data sample %d for native kernel did not "
                      "validate (expected %d, got %d)\n",
                      i, (int)inBuffer[i], (int)outBuffer[i]);
            return 1;
        }
    }

    return 0;
}
