//
// 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_command_queue_properties props = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE;

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

    clCommandQueueWrapper queue =
        clCreateCommandQueue(context, deviceID, props, &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;
}
