//
// 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 <stdio.h>
#include <stdlib.h>

#if !defined(_WIN32)
#include <stdbool.h>
#endif

#include <math.h>
#include <string.h>

#if !defined (__APPLE__)
#include <CL/cl.h>
#endif

#include "procs.h"
#include "../../test_common/gles/setup.h"
#include "../../test_common/harness/testHarness.h"

#if !defined(_WIN32)
#include <unistd.h>
#endif

static cl_context        sCurrentContext = NULL;


#define TEST_FN_REDIRECT( fn ) ADD_TEST( redirect_##fn )
#define TEST_FN_REDIRECTOR( fn ) \
int test_redirect_##fn(cl_device_id device, cl_context context, cl_command_queue queue, int numElements )    \
{ \
    int error; \
    clCommandQueueWrapper realQueue = clCreateCommandQueue( sCurrentContext, device, 0, &error ); \
    test_error( error, "Unable to create command queue" );    \
    return test_##fn( device, sCurrentContext, realQueue, numElements ); \
}

TEST_FN_REDIRECTOR( buffers )
TEST_FN_REDIRECTOR( buffers_getinfo )
TEST_FN_REDIRECTOR( images_read )
TEST_FN_REDIRECTOR( images_2D_getinfo )
TEST_FN_REDIRECTOR( images_read_cube )
TEST_FN_REDIRECTOR( images_cube_getinfo )
TEST_FN_REDIRECTOR( images_read_3D )
TEST_FN_REDIRECTOR( images_3D_getinfo )
TEST_FN_REDIRECTOR( images_write )
TEST_FN_REDIRECTOR( images_write_cube )
TEST_FN_REDIRECTOR( renderbuffer_read )
TEST_FN_REDIRECTOR( renderbuffer_write )
TEST_FN_REDIRECTOR( renderbuffer_getinfo )

#ifndef GL_ES_VERSION_2_0
TEST_FN_REDIRECTOR( test_fence_sync )
#endif

test_definition test_list[] = {
    TEST_FN_REDIRECT( buffers ),
    TEST_FN_REDIRECT( buffers_getinfo ),
    TEST_FN_REDIRECT( images_read ),
    TEST_FN_REDIRECT( images_2D_getinfo ),
    TEST_FN_REDIRECT( images_read_cube ),
    TEST_FN_REDIRECT( images_cube_getinfo ),
    TEST_FN_REDIRECT( images_read_3D ),
    TEST_FN_REDIRECT( images_3D_getinfo ),
    TEST_FN_REDIRECT( images_write ),
    TEST_FN_REDIRECT( images_write_cube ),
    TEST_FN_REDIRECT( renderbuffer_read ),
    TEST_FN_REDIRECT( renderbuffer_write ),
    TEST_FN_REDIRECT( renderbuffer_getinfo )
};

#ifndef GL_ES_VERSION_2_0
test_definition test_list32[] = {
    TEST_FN_REDIRECT( fence_sync )
};
#endif

const int test_num = ARRAY_SIZE( test_list );
const int test_num32 = ARRAY_SIZE( test_list32 );


int main(int argc, const char *argv[])
{
  int error = 0;
  cl_platform_id platform_id = NULL;
  /* To keep it simple, use a static allocation of 32 argv pointers.
     argc is not expected to go beyond 32 */
  const char* argv_tmp[32] = {0};
  int argc_tmp = 0;

    test_start();

  cl_device_type requestedDeviceType = CL_DEVICE_TYPE_DEFAULT;

    for(int z = 1; z < argc; ++z)
    {//for
    if(strcmp( argv[ z ], "-list" ) == 0 )
    {
        log_info( "Available 2.x tests:\n" );
        for( int i = 0; i < test_num; i++ )
            log_info( "\t%s\n", test_list[i].name );

        log_info( "Available 3.2 tests:\n" );
        for( int i = 0; i < test_num32; i++ )
            log_info( "\t%s\n", test_list32[i].name );

    log_info( "Note: Any 3.2 test names must follow 2.1 test names on the command line." );
    log_info( "Use environment variables to specify desired device." );

        test_finish();
        return 0;
    }

    /* support requested device type */
        if(!strcmp(argv[z], "CL_DEVICE_TYPE_GPU"))
        {
           printf("Requested device type is CL_DEVICE_TYPE_GPU\n");
           requestedDeviceType = CL_DEVICE_TYPE_GPU;
        }
        else
        if(!strcmp(argv[z], "CL_DEVICE_TYPE_CPU"))
        {
           printf("Requested device type is CL_DEVICE_TYPE_CPU\n");
           log_info("Invalid CL device type. GL tests can only run on a GPU device.\n");
           test_finish();
           return 0;
        }
    }//for

  // Check to see if any 2.x or 3.2 test names were specified on the command line.
  unsigned first_32_testname = 0;

  for (int j=1; (j<argc) && (!first_32_testname); ++j)
    for (int i = 0; i < test_num32; ++i)
      if (strcmp(test_list32[i].name, argv[j]) == 0 ) {
        first_32_testname = j;
        break;
      }

  // Create the environment for the test.
    GLEnvironment *glEnv = GLEnvironment::Instance();

  // Check if any devices of the requested type support CL/GL interop.
  int supported = glEnv->SupportsCLGLInterop( requestedDeviceType );
  if( supported == 0 ) {
    log_info("Test not run because GL-CL interop is not supported for any devices of the requested type.\n");
    test_finish();
    error = 0;
    goto cleanup;
  } else if ( supported == -1 ) {
    log_error("Failed to determine if CL-GL interop is supported.\n");
    test_finish();
    error = -1;
    goto cleanup;
  }

  // OpenGL tests for non-3.2 ////////////////////////////////////////////////////////
  if ((argc == 1) || (first_32_testname != 1)) {

    // At least one device supports CL-GL interop, so init the test.
    if( glEnv->Init( &argc, (char **)argv, CL_FALSE ) ) {
      log_error("Failed to initialize the GL environment for this test.\n");
      test_finish();
      error = -1;
      goto cleanup;
    }

    // Create a context to use and then grab a device (or devices) from it
    sCurrentContext = glEnv->CreateCLContext();
    if( sCurrentContext == NULL )
      {
        log_error( "ERROR: Unable to obtain CL context from GL\n" );
        test_finish();
        error = -1;
        goto cleanup;
      }

    size_t numDevices = 0;
    cl_device_id deviceIDs[ 16 ];

    error = clGetContextInfo( sCurrentContext, CL_CONTEXT_DEVICES, 0, NULL, &numDevices);
    if( error != CL_SUCCESS )
      {
        print_error( error, "Unable to get device count from context" );
        test_finish();
        error = -1;
        goto cleanup;
      }
    numDevices /= sizeof(cl_device_id);

    if (numDevices < 1) {
      log_error("No devices found.\n");
      test_finish();
      error = -1;
      goto cleanup;
    }

    error = clGetContextInfo( sCurrentContext, CL_CONTEXT_DEVICES, sizeof( deviceIDs ), deviceIDs, NULL);
    if( error != CL_SUCCESS ) {
      print_error( error, "Unable to get device list from context" );
      test_finish();
      error = -1;
      goto cleanup;
    }

    // Execute tests.
    int argc_ = (first_32_testname) ? first_32_testname : argc;

      for( size_t i = 0; i < numDevices; i++ ) {
        log_info( "\nTesting OpenGL 2.x\n" );
        if( printDeviceHeader( deviceIDs[ i ] ) != CL_SUCCESS ) {
          test_finish();
          error = -1;
          goto cleanup;
        }

        error = clGetDeviceInfo(deviceIDs[ i ],
                                CL_DEVICE_PLATFORM,
                                sizeof(platform_id),
                                &platform_id,
                                NULL);
        if(error)
        {
          goto cleanup;
        }

        error = init_clgl_ext(platform_id);
        if (error < 0)
        {
          goto cleanup;
        }

        /* parseAndCallCommandLineTests considers every command line argument
           as a test name. This results in the test failing because of considering
           args such as 'CL_DEVICE_TYPE_GPU' as test names unless
           the actual test name happens to be the first argument.
           Instead of changing the behaviour of parseAndCallCommandLineTests
           modify the arguments passed to it so as to not affect other tests.
        */
    int w = 1;
    argc_tmp= argc_;
    for(int k = 1; k < argc; k++)
    {
        if( (strcmp(argv[k], "full") == 0) ||
            (strcmp(argv[k], "CL_DEVICE_TYPE_CPU") == 0) ||
            (strcmp(argv[k], "CL_DEVICE_TYPE_GPU") == 0))
        {
            argc_tmp--;
            continue;
        }
        else
        {
            argv_tmp[w++] = argv[k];
        }
    }

        // Note: don't use the entire harness, because we have a different way of obtaining the device (via the context)
        error = parseAndCallCommandLineTests( argc_tmp, argv_tmp, deviceIDs[i], test_num, test_list, true, 0, 1024 );
        if( error != 0 )
          break;
    }

    // Clean-up.
      // We move this to a common cleanup step to make sure that things will be released properly before the test exit
      goto cleanup;
      // clReleaseContext( sCurrentContext );
      // delete glEnv;
  }

  // OpenGL 3.2 tests. ////////////////////////////////////////////////////////
  if ((argc==1) || first_32_testname) {

    // At least one device supports CL-GL interop, so init the test.
    if( glEnv->Init( &argc, (char **)argv, CL_TRUE ) ) {
      log_error("Failed to initialize the GL environment for this test.\n");
      test_finish();
      error = -1;
      goto cleanup;
    }

    // Create a context to use and then grab a device (or devices) from it
    sCurrentContext = glEnv->CreateCLContext();
    if( sCurrentContext == NULL ) {
      log_error( "ERROR: Unable to obtain CL context from GL\n" );
      test_finish();
      error = -1;
      goto cleanup;
    }

    size_t numDevices = 0;
    cl_device_id deviceIDs[ 16 ];

    error = clGetContextInfo( sCurrentContext, CL_CONTEXT_DEVICES, 0, NULL, &numDevices);
    if( error != CL_SUCCESS ) {
      print_error( error, "Unable to get device count from context" );
      test_finish();
      error = -1;
      goto cleanup;
    }
    numDevices /= sizeof(cl_device_id);

    if (numDevices < 1) {
      log_error("No devices found.\n");
      test_finish();
      error = -1;
      goto cleanup;
    }

    error = clGetContextInfo( sCurrentContext, CL_CONTEXT_DEVICES, sizeof( deviceIDs ), deviceIDs, NULL);
    if( error != CL_SUCCESS ) {
      print_error( error, "Unable to get device list from context" );
      test_finish();
      error = -1;
      goto cleanup;
    }

    int argc_ = (first_32_testname) ? 1 + (argc - first_32_testname) : argc;
    const char** argv_ = (first_32_testname) ? &argv[first_32_testname-1] : argv;

    // Execute the tests.
      for( size_t i = 0; i < numDevices; i++ ) {
        log_info( "\nTesting OpenGL 3.2\n" );
        if( printDeviceHeader( deviceIDs[ i ] ) != CL_SUCCESS ) {
          test_finish();
          error = -1;
          goto cleanup;
        }
#ifdef GL_ES_VERSION_2_0
        log_info("Cannot test OpenGL 3.2! This test was built for OpenGL ES 2.0\n");
        test_finish();
        error = -1;
        goto cleanup;
#else
        // Note: don't use the entire harness, because we have a different way of obtaining the device (via the context)
        error = parseAndCallCommandLineTests( argc_, argv_, deviceIDs[ i ], test_num32, test_list32, true, 0, 1024 );
        if( error != 0 )
          break;
#endif
    }

    // Converge on a common cleanup to make sure that things will be released properly before the test exit
    goto cleanup;
  }


// cleanup CL/GL/EGL environment properly when the test exit.
// This change does not affect any functionality of the test

// Intentional falling through
cleanup:

    // Always make sure that OpenCL context is released properly when the test exit
    if(sCurrentContext)
    {
        clReleaseContext( sCurrentContext );
        sCurrentContext = NULL;
    }

    // Cleanup EGL
    glEnv->terminate_egl_display();

    delete glEnv;

    return error;
}
