//
// 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.
//
#ifndef test_conformance_checker_Image_MEM_HOST_WRITE_ONLY_h
#define test_conformance_checker_Image_MEM_HOST_WRITE_ONLY_h

#include "checker_image_mem_host_read_only.hpp"

template <class T>
class cImage_check_mem_host_write_only
    : public cImage_check_mem_host_read_only<T> {

public:
    cImage_check_mem_host_write_only(cl_device_id deviceID, cl_context context,
                                     cl_command_queue queue)
        : cImage_check_mem_host_read_only<T>(deviceID, context, queue)
    {}

    ~cImage_check_mem_host_write_only(){};

    clMemWrapper m_Image_2;

    cl_int verify_RW_Image();
    cl_int verify_RW_Image_Mapping();

    cl_int Setup_Test_Environment();
    cl_int update_host_mem_2();

    cl_int verify_data(T *pdtaIn);
};

template <class T>
cl_int cImage_check_mem_host_write_only<T>::Setup_Test_Environment()
{
    int all = this->get_image_elements();

    T vv2 = 0;
    this->host_m_2.Init(all, vv2);
    vv2 = TEST_VALUE;
    this->host_m_0.Init(all, vv2);

    cl_int err = CL_SUCCESS;
    this->m_Image_2 = clCreateImage(
        this->m_context,
        CL_MEM_READ_WRITE | CL_MEM_HOST_READ_ONLY | CL_MEM_COPY_HOST_PTR,
        &(this->m_cl_image_format), &(this->m_cl_Image_desc),
        this->host_m_2.pData, &err);
    test_error(err, "clCreateImage error");

    return err;
}

// Copy image data from a write_only image to a read_write image and read the
// contents.
template <class T>
cl_int cImage_check_mem_host_write_only<T>::update_host_mem_2()
{
    size_t orig[3] = { 0, 0, 0 };
    size_t img_region[3] = { 0, 0, 0 };
    img_region[0] = this->m_cl_Image_desc.image_width;
    img_region[1] = this->m_cl_Image_desc.image_height;
    img_region[2] = this->m_cl_Image_desc.image_depth;

    cl_event event;
    cl_int err = CL_SUCCESS;
    err = clEnqueueCopyImage(this->m_queue, this->m_Image, this->m_Image_2,
                             orig, orig, img_region, 0, NULL, &event);
    test_error(err, "clEnqueueCopyImage error");

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    this->host_m_2.Set_to_zero();

    err = clEnqueueReadImage(
        this->m_queue, this->m_Image_2, this->m_blocking, this->buffer_origin,
        this->region, this->buffer_row_pitch_bytes,
        this->buffer_slice_pitch_bytes, this->host_m_2.pData, 0, NULL, &event);
    test_error(err, "clEnqueueReadImage error");

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    return err;
}

template <class T>
cl_int cImage_check_mem_host_write_only<T>::verify_data(T *pdtaIn)
{
    cl_int err = CL_SUCCESS;
    if (!this->host_m_1.Equal_rect_from_orig(pdtaIn, this->buffer_origin,
                                             this->region, this->host_row_pitch,
                                             this->host_slice_pitch))
    {
        log_error("Image and host data difference found\n");
        return FAILURE;
    }

    int total = (int)(this->region[0] * this->region[1] * this->region[2]);
    T v = TEST_VALUE;
    int tot = (int)(this->host_m_2.Count(v));
    if (tot != total)
    {
        log_error("Image data content difference found\n");
        return FAILURE;
    }

    return err;
}

template <class T> cl_int cImage_check_mem_host_write_only<T>::verify_RW_Image()
{
    cl_int err = CL_SUCCESS;

    this->Init_rect();

    cl_event event;
    size_t img_orig[3] = { 0, 0, 0 };
    size_t img_region[3] = { 0, 0, 0 };
    img_region[0] = this->m_cl_Image_desc.image_width;
    img_region[1] = this->m_cl_Image_desc.image_height;
    img_region[2] = this->m_cl_Image_desc.image_depth;

    int color[4] = { 0xFF, 0xFF, 0xFF, 0xFF };
    err = clEnqueueFillImage(this->m_queue, this->m_Image, &color, img_orig,
                             img_region, 0, NULL,
                             &event); // Fill the buffer with data

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }
    test_error(err, "clEnqueueFillImage error");

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    T v = TEST_VALUE;

    err = clEnqueueWriteImage(
        this->m_queue, this->m_Image, this->m_blocking, this->buffer_origin,
        this->region, this->buffer_row_pitch_bytes,
        this->buffer_slice_pitch_bytes, this->host_m_0.pData, 0, NULL, &event);
    test_error(err, "clEnqueueWriteImage error"); // Test writing to buffer

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    update_host_mem_2(); // Read buffer contents into mem_2

    err = this->verify_data(
        this->host_m_2
            .pData); // Compare the contents of mem_2 and mem_1,
                     // mem_1 is same as mem_0 in setup test environment
    test_error(err, "verify_data error");

    v = 0;
    this->host_m_2.Set_to(v);
    err = clEnqueueReadImage(
        this->m_queue, this->m_Image, this->m_blocking, this->buffer_origin,
        this->region, this->buffer_row_pitch_bytes,
        this->buffer_slice_pitch_bytes, this->host_m_1.pData, 0, NULL, &event);

    if (err == CL_SUCCESS)
    {
        log_error(
            "Calling clEnqueueReadImage on a memory object created with the "
            "CL_MEM_HOST_WRITE_ONLY flag should not return CL_SUCCESS\n");
        err = FAILURE;
        return FAILURE;
    }
    else
    {
        log_info("Test succeeded\n\n");
        err = CL_SUCCESS;
    }

    /* Qualcomm fix: 12506 Do not wait on invalid event/ no need for
    syncronization calls after clEnqueueReadImage fails
     *
     * The call to clEnqueueReadImage fails as expected and returns an invalid
    event on
     * which clWaitForEvents cannot be called. (It will rightly fail with a
    CL_INVALID_EVENT error)
     * Further, we don't need to do any additional flushes or finishes here
    since we were in sync
     * before the (failing) call to clEnqueueReadImage

    if (!this->m_blocking) {
      err = clWaitForEvents(1, &event);
      test_error(err, " clWaitForEvents error")
    }
    Qualcomm fix: end*/

    return err;
}

template <class T>
cl_int cImage_check_mem_host_write_only<T>::verify_RW_Image_Mapping()
{
    this->Init_rect();

    cl_event event;
    size_t img_orig[3] = { 0, 0, 0 };
    size_t img_region[3] = { 0, 0, 0 };
    img_region[0] = this->m_cl_Image_desc.image_width;
    img_region[1] = this->m_cl_Image_desc.image_height;
    img_region[2] = this->m_cl_Image_desc.image_depth;

    int color[4] = { 0xFF, 0xFF, 0xFF, 0xFF };
    cl_int err = CL_SUCCESS;


    // Fill image with pattern
    err = clEnqueueFillImage(this->m_queue, this->m_Image, &color, img_orig,
                             img_region, 0, NULL, &event);

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    // Map image for writing
    T* dataPtr = (T*)clEnqueueMapImage(
        this->m_queue, this->m_Image, this->m_blocking, CL_MAP_WRITE,
        this->buffer_origin, this->region, &(this->buffer_row_pitch_bytes),
        &(this->buffer_slice_pitch_bytes), 0, NULL, &event, &err);
    test_error(err, "clEnqueueMapImage CL_MAP_WRITE pointer error");

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    // Verify map pointer
    err = this->verify_mapping_ptr(dataPtr);
    test_error(err, "clEnqueueMapImage CL_MAP_WRITE pointer error");

    // Verify mapped data

    // The verify_data_with_offset method below compares dataPtr against
    // this->host_m_2.pData. The comparison should start at origin {0, 0, 0}.
    update_host_mem_2();

    // Check the content of mem and host_ptr
    size_t offset[3] = { 0, 0, 0 };
    err = cImage_check_mem_host_read_only<T>::verify_data_with_offset(dataPtr,
                                                                      offset);
    test_error(err, "verify_data error");

    // Unmap memory object
    err = clEnqueueUnmapMemObject(this->m_queue, this->m_Image, dataPtr, 0,
                                  NULL, &event);
    test_error(err, "clEnqueueUnmapMemObject error");

    if (!this->m_blocking)
    {
        err = clWaitForEvents(1, &event);
        test_error(err, "clWaitForEvents error");
    }

    err = clReleaseEvent(event);
    test_error(err, "clReleaseEvent error");

    dataPtr = (T*)clEnqueueMapImage(
        this->m_queue, this->m_Image, this->m_blocking, CL_MAP_READ,
        this->buffer_origin, this->region, &(this->buffer_row_pitch_bytes),
        &(this->buffer_slice_pitch_bytes), 0, NULL, &event, &err);

    if (err == CL_SUCCESS)
    {
        log_error("Calling clEnqueueMapImage (CL_MAP_READ) on a memory object "
                  "created with the CL_MEM_HOST_WRITE_ONLY flag should not "
                  "return CL_SUCCESS\n");
        err = FAILURE;
        return FAILURE;
    }
    else
    {
        log_info("Test succeeded\n\n");
        err = CL_SUCCESS;
    }

    return err;
}

#endif
