//
// 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 "action_classes.h"


extern const char *IGetStatusString( cl_int status );

#define PRINT_OPS 0

int test_waitlist( cl_device_id device, cl_context context, cl_command_queue queue, Action *actionToTest, bool multiple )
{
    NDRangeKernelAction    actions[ 2 ];
    clEventWrapper events[ 3 ];
    cl_int status[ 3 ];
    cl_int error;

  if (multiple)
    log_info("\tExecuting reference event 0, then reference event 1 with reference event 0 in its waitlist, then test event 2 with reference events 0 and 1 in its waitlist.\n");
  else
    log_info("\tExecuting reference event 0, then test event 2 with reference event 0 in its waitlist.\n");

    // Set up the first base action to wait against
    error = actions[ 0 ].Setup( device, context, queue );
    test_error( error, "Unable to setup base event to wait against" );

    if( multiple )
    {
        // Set up a second event to wait against
        error = actions[ 1 ].Setup( device, context, queue );
        test_error( error, "Unable to setup second base event to wait against" );
    }

    // Now set up the actual action to test
    error = actionToTest->Setup( device, context, queue );
    test_error( error, "Unable to set up test event" );

    // Execute all events now
  if (PRINT_OPS) log_info("\tExecuting action 0...\n");
    error = actions[ 0 ].Execute( queue, 0, NULL, &events[ 0 ] );
    test_error( error, "Unable to execute first event" );

    if( multiple )
    {
    if (PRINT_OPS) log_info("\tExecuting action 1...\n");
        error = actions[ 1 ].Execute( queue, 1, &events[0], &events[ 1 ] );
        test_error( error, "Unable to execute second event" );
    }

    // Sanity check
  if( multiple ) {
    if (PRINT_OPS) log_info("\tChecking status of action 1...\n");
        error = clGetEventInfo( events[ 1 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 1 ] ), &status[ 1 ], NULL );
    test_error( error, "Unable to get event status" );
  }
  if (PRINT_OPS) log_info("\tChecking status of action 0...\n");
    error = clGetEventInfo( events[ 0 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 0 ] ), &status[ 0 ], NULL );
  test_error( error, "Unable to get event status" );

  log_info("\t\tEvent status after starting reference events: reference event 0: %s, reference event 1: %s, test event 2: %s.\n",
           IGetStatusString( status[ 0 ] ), (multiple ? IGetStatusString( status[ 1 ] ) : "N/A"), "N/A");

    if( ( status[ 0 ] == CL_COMPLETE ) || ( multiple && status[ 1 ] == CL_COMPLETE ) )
    {
        log_info( "WARNING: Reference event(s) already completed before we could execute test event! Possible that the reference event blocked (implicitly passing)\n" );
        return 0;
    }

  if (PRINT_OPS) log_info("\tExecuting action to test...\n");
    error = actionToTest->Execute( queue, ( multiple ) ? 2 : 1, &events[ 0 ], &events[ 2 ] );
    test_error( error, "Unable to execute test event" );

    // Hopefully, the first event is still running
  if (PRINT_OPS) log_info("\tChecking status of action to test 2...\n");
    error = clGetEventInfo( events[ 2 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 2 ] ), &status[ 2 ], NULL );
    test_error( error, "Unable to get event status" );
  if( multiple ) {
    if (PRINT_OPS) log_info("\tChecking status of action 1...\n");
        error = clGetEventInfo( events[ 1 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 1 ] ), &status[ 1 ], NULL );
    test_error( error, "Unable to get event status" );
  }
  if (PRINT_OPS) log_info("\tChecking status of action 0...\n");
    error = clGetEventInfo( events[ 0 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 0 ] ), &status[ 0 ], NULL );
  test_error( error, "Unable to get event status" );

  log_info("\t\tEvent status after starting test event: reference event 0: %s, reference event 1: %s, test event 2: %s.\n",
           IGetStatusString( status[ 0 ] ), (multiple ? IGetStatusString( status[ 1 ] ) : "N/A"), IGetStatusString( status[ 2 ] ));

    if( multiple )
    {
        if( status[ 0 ] == CL_COMPLETE && status[ 1 ] == CL_COMPLETE )
        {
            log_info( "WARNING: Both events completed, so unable to test further (implicitly passing).\n" );
            clFinish( queue );
            return 0;
        }

    if(status[1] == CL_COMPLETE && status[0] != CL_COMPLETE)
    {
      log_error("ERROR: Test failed because the second wait event is complete and the first is not.(status: 0: %s and 1: %s)\n", IGetStatusString( status[ 0 ] ), IGetStatusString( status[ 1 ] ) );
            clFinish( queue );
            return -1;
    }
    }
    else
    {
        if( status[ 0 ] == CL_COMPLETE )
        {
            log_info( "WARNING: Reference event completed, so unable to test further (implicitly passing).\n" );
            clFinish( queue );
            return 0;
        }
        if( status[ 0 ] != CL_RUNNING && status[ 0 ] != CL_QUEUED && status[ 0 ] != CL_SUBMITTED )
        {
            log_error( "ERROR: Test failed because first wait event is not currently running, queued, or submitted! (status: 0: %s)\n", IGetStatusString( status[ 0 ] ) );
            clFinish( queue );
            return -1;
        }
    }

    if( status[ 2 ] != CL_QUEUED && status[ 2 ] != CL_SUBMITTED )
    {
        log_error( "ERROR: Test event is not waiting to run! (status: 2: %s)\n", IGetStatusString( status[ 2 ] ) );
        clFinish( queue );
        return -1;
    }

    // Now wait for the first reference event
  if (PRINT_OPS) log_info("\tWaiting for action 1 to finish...\n");
    error = clWaitForEvents( 1, &events[ 0 ] );
    test_error( error, "Unable to wait for reference event" );

    // Grab statuses again
  if (PRINT_OPS) log_info("\tChecking status of action to test 2...\n");
    error = clGetEventInfo( events[ 2 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 2 ] ), &status[ 2 ], NULL );
    test_error( error, "Unable to get event status" );
  if( multiple ) {
    if (PRINT_OPS) log_info("\tChecking status of action 1...\n");
        error = clGetEventInfo( events[ 1 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 1 ] ), &status[ 1 ], NULL );
    test_error( error, "Unable to get event status" );
  }
  if (PRINT_OPS) log_info("\tChecking status of action 0...\n");
    error = clGetEventInfo( events[ 0 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 0 ] ), &status[ 0 ], NULL );
  test_error( error, "Unable to get event status" );

  log_info("\t\tEvent status after waiting for reference event 0: reference event 0: %s, reference event 1: %s, test event 2: %s.\n",
           IGetStatusString( status[ 0 ] ), (multiple ? IGetStatusString( status[ 1 ] ) : "N/A"), IGetStatusString( status[ 2 ] ));

    // Sanity
    if( status[ 0 ] != CL_COMPLETE )
    {
        log_error( "ERROR: Waited for first event but it's not complete (status: 0: %s)\n", IGetStatusString( status[ 0 ] ) );
        clFinish( queue );
        return -1;
    }

    // If we're multiple, and the second event isn't complete, then our test event should still be queued
    if( multiple && status[ 1 ] != CL_COMPLETE )
    {
    if( status[ 1 ] == CL_RUNNING && status[ 2 ] == CL_RUNNING ) {
      log_error("ERROR: Test event and second event are both running.\n");
      clFinish( queue );
      return -1;
    }
        if( status[ 2 ] != CL_QUEUED && status[ 2 ] != CL_SUBMITTED )
        {
            log_error( "ERROR: Test event did not wait for second event before starting! (status of ref: 1: %s, of test: 2: %s)\n", IGetStatusString( status[ 1 ] ), IGetStatusString( status[ 2 ] ) );
            clFinish( queue );
            return -1;
        }

        // Now wait for second event to complete, too
    if (PRINT_OPS) log_info("\tWaiting for action 1 to finish...\n");
        error = clWaitForEvents( 1, &events[ 1 ] );
        test_error( error, "Unable to wait for second reference event" );

        // Grab statuses again
    if (PRINT_OPS) log_info("\tChecking status of action to test 2...\n");
    error = clGetEventInfo( events[ 2 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 2 ] ), &status[ 2 ], NULL );
    test_error( error, "Unable to get event status" );
    if( multiple ) {
      if (PRINT_OPS) log_info("\tChecking status of action 1...\n");
      error = clGetEventInfo( events[ 1 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 1 ] ), &status[ 1 ], NULL );
      test_error( error, "Unable to get event status" );
    }
    if (PRINT_OPS) log_info("\tChecking status of action 0...\n");
    error = clGetEventInfo( events[ 0 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 0 ] ), &status[ 0 ], NULL );
    test_error( error, "Unable to get event status" );

    log_info("\t\tEvent status after waiting for reference event 1: reference event 0: %s, reference event 1: %s, test event 2: %s.\n",
             IGetStatusString( status[ 0 ] ), (multiple ? IGetStatusString( status[ 1 ] ) : "N/A"), IGetStatusString( status[ 2 ] ));

        // Sanity
        if( status[ 1 ] != CL_COMPLETE )
        {
            log_error( "ERROR: Waited for second reference event but it didn't complete (status: 1: %s)\n", IGetStatusString( status[ 1 ] ) );
            clFinish( queue );
            return -1;
        }
    }

    // At this point, the test event SHOULD be running, but if it completed, we consider it a pass
    if( status[ 2 ] == CL_COMPLETE )
    {
        log_info( "WARNING: Test event already completed. Assumed valid.\n" );
        clFinish( queue );
        return 0;
    }
    if( status[ 2 ] != CL_RUNNING && status[ 2 ] != CL_SUBMITTED && status[ 2 ] != CL_QUEUED)
    {
        log_error( "ERROR: Second event did not start running after reference event(s) completed! (status: 2: %s)\n", IGetStatusString( status[ 2 ] ) );
        clFinish( queue );
        return -1;
    }

    // Wait for the test event, then return
  if (PRINT_OPS) log_info("\tWaiting for action 2 to test to finish...\n");
    error = clWaitForEvents( 1, &events[ 2 ] );
    test_error( error, "Unable to wait for test event" );

  error |= clGetEventInfo( events[ 2 ], CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof( status[ 2 ] ), &status[ 2 ], NULL );
  test_error( error, "Unable to get event status" );

  log_info("\t\tEvent status after waiting for test event: reference event 0: %s, reference event 1: %s, test event 2: %s.\n",
           IGetStatusString( status[ 0 ] ), (multiple ? IGetStatusString( status[ 1 ] ) : "N/A"), IGetStatusString( status[ 2 ] ));

  // Sanity
  if( status[ 2 ] != CL_COMPLETE )
  {
    log_error( "ERROR: Test event didn't complete (status: 2: %s)\n", IGetStatusString( status[ 2 ] ) );
    clFinish( queue );
    return -1;
  }

  clFinish(queue);
    return 0;
}

#define TEST_ACTION( name ) \
    {    \
        name##Action action;    \
        log_info( "-- Testing " #name " (waiting on 1 event)...\n" );    \
        if( ( error = test_waitlist( deviceID, context, queue, &action, false ) ) != CL_SUCCESS )    \
            retVal++;            \
        clFinish( queue ); \
    }    \
    if( error == CL_SUCCESS )    /* Only run multiples test if single test passed */ \
    {    \
        name##Action action;    \
        log_info( "-- Testing " #name " (waiting on 2 events)...\n" );    \
        if( ( error = test_waitlist( deviceID, context, queue, &action, true ) ) != CL_SUCCESS )    \
            retVal++;            \
        clFinish( queue ); \
    }

int test_waitlists( cl_device_id deviceID, cl_context context, cl_command_queue oldQueue, int num_elements )
{
    cl_int error;
    int retVal = 0;
    cl_queue_properties props[] = {CL_QUEUE_PROPERTIES, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE, 0};

    if( !checkDeviceForQueueSupport( deviceID, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE ) )
    {
        log_info( "WARNING: Device does not support out-of-order exec mode; skipping test.\n" );
        return 0;
    }

    clCommandQueueWrapper queue = clCreateCommandQueueWithProperties( context, deviceID, &props[0], &error );
    test_error(error, "Unable to create out-of-order queue");

    log_info( "\n" );

    TEST_ACTION( NDRangeKernel )

    TEST_ACTION( ReadBuffer )
    TEST_ACTION( WriteBuffer )
    TEST_ACTION( MapBuffer )
    TEST_ACTION( UnmapBuffer )

    if( checkForImageSupport( deviceID ) == CL_IMAGE_FORMAT_NOT_SUPPORTED )
    {
        log_info( "\nNote: device does not support images. Skipping remainder of waitlist tests...\n" );
    }
    else
    {
        TEST_ACTION( ReadImage2D )
        TEST_ACTION( WriteImage2D )
        TEST_ACTION( CopyImage2Dto2D )
        TEST_ACTION( Copy2DImageToBuffer )
        TEST_ACTION( CopyBufferTo2DImage )
        TEST_ACTION( MapImage )

        if( checkFor3DImageSupport( deviceID ) == CL_IMAGE_FORMAT_NOT_SUPPORTED )
            log_info("Device does not support 3D images. Skipping remainder of waitlist tests...\n");
        else
        {
            TEST_ACTION( ReadImage3D )
            TEST_ACTION( WriteImage3D )
            TEST_ACTION( CopyImage2Dto3D )
            TEST_ACTION( CopyImage3Dto2D )
            TEST_ACTION( CopyImage3Dto3D )
            TEST_ACTION( Copy3DImageToBuffer )
            TEST_ACTION( CopyBufferTo3DImage )
        }
    }

    return retVal;
}

