/*
 * cl_memory.cpp - CL memory
 *
 *  Copyright (c) 2015 Intel Corporation
 *
 * 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.
 *
 * Author: Wind Yuan <feng.yuan@intel.com>
 */

#include "cl_utils.h"
#include "cl_memory.h"
#if HAVE_LIBDRM
#include "intel/cl_va_memory.h"
#endif

namespace XCam {

CLImageDesc::CLImageDesc ()
    : format {CL_R, CL_UNORM_INT8}
    , width (0)
    , height (0)
    , row_pitch (0)
    , slice_pitch (0)
    , array_size (0)
    , size (0)
{
}

bool
CLImageDesc::operator == (const CLImageDesc& desc) const
{
    if (desc.format.image_channel_data_type == this->format.image_channel_data_type &&
            desc.format.image_channel_order == this->format.image_channel_order &&
            desc.width == this->width &&
            desc.height == this->height &&
            desc.row_pitch == this->row_pitch &&
            desc.slice_pitch == this->slice_pitch &&
            desc.array_size == this->array_size)// &&
        //desc.size == this->size)
        return true;
    return false;
}

CLMemory::CLMemory (const SmartPtr<CLContext> &context)
    : _context (context)
    , _mem_id (NULL)
    , _mem_fd (-1)
    , _mem_need_destroy (true)
    , _mapped_ptr (NULL)
{
    XCAM_ASSERT (context.ptr () && context->is_valid ());
}

CLMemory::~CLMemory ()
{
    release_fd ();

    if (_mapped_ptr)
        enqueue_unmap (_mapped_ptr);

    if (_mem_id && _mem_need_destroy) {
        _context->destroy_mem (_mem_id);
    }
}

int32_t
CLMemory::export_fd ()
{
    if (_mem_fd >= 0)
        return _mem_fd;

#if HAVE_LIBDRM
    SmartPtr<CLIntelContext> context = _context.dynamic_cast_ptr<CLIntelContext> ();
    _mem_fd = context->export_mem_fd (_mem_id);
#endif
    if (_mem_fd < 0)
        XCAM_LOG_ERROR ("invalid fd:%d", _mem_fd);

    return _mem_fd;
}

void
CLMemory::release_fd ()
{
    if (_mem_fd <= 0)
        return;

    close (_mem_fd);
    _mem_fd = -1;
}

XCamReturn
CLMemory::enqueue_unmap (
    void *ptr,
    CLEventList &event_waits,
    SmartPtr<CLEvent> &event_out)
{
    SmartPtr<CLContext> context = get_context ();
    cl_mem mem_id = get_mem_id ();

    XCAM_ASSERT (is_valid ());
    if (!is_valid ())
        return XCAM_RETURN_ERROR_PARAM;

    XCAM_ASSERT (ptr == _mapped_ptr);
    if (ptr == _mapped_ptr)
        _mapped_ptr = NULL;

    return context->enqueue_unmap (mem_id, ptr, event_waits, event_out);
}

bool CLMemory::get_cl_mem_info (
    cl_image_info param_name, size_t param_size,
    void *param, size_t *param_size_ret)
{
    cl_mem mem_id = get_mem_id ();
    cl_int error_code = CL_SUCCESS;
    if (!mem_id)
        return false;

    error_code = clGetMemObjectInfo (mem_id, param_name, param_size, param, param_size_ret);
    XCAM_FAIL_RETURN(
        WARNING,
        error_code == CL_SUCCESS,
        false,
        "clGetMemObjectInfo failed on param:%d, errno:%d", param_name, error_code);
    return true;
}

CLBuffer::CLBuffer (const SmartPtr<CLContext> &context)
    : CLMemory (context)
{
}

CLBuffer::CLBuffer (
    const SmartPtr<CLContext> &context, uint32_t size,
    cl_mem_flags  flags, void *host_ptr)
    : CLMemory (context)
    , _flags (flags)
    , _size (size)
{
    init_buffer (context, size, flags, host_ptr);
}

bool
CLBuffer::init_buffer (
    const SmartPtr<CLContext> &context, uint32_t size,
    cl_mem_flags  flags, void *host_ptr)
{
    cl_mem mem_id = NULL;

    mem_id = context->create_buffer (size, flags, host_ptr);
    if (mem_id == NULL) {
        XCAM_LOG_WARNING ("CLBuffer create buffer failed");
        return false;
    }

    set_mem_id (mem_id);
    return true;
}

CLSubBuffer::CLSubBuffer (
    const SmartPtr<CLContext> &context, SmartPtr<CLBuffer> main_buf,
    cl_mem_flags flags, uint32_t offset, uint32_t size)
    : CLBuffer (context)
    , _main_buf (main_buf)
    , _flags (flags)
    , _size (size)
{
    init_sub_buffer (context, main_buf, flags, offset, size);
}

bool
CLSubBuffer::init_sub_buffer (
    const SmartPtr<CLContext> &context,
    SmartPtr<CLBuffer> main_buf,
    cl_mem_flags flags,
    uint32_t offset,
    uint32_t size)
{
    cl_mem sub_mem = NULL;
    cl_mem main_mem = main_buf->get_mem_id ();
    XCAM_FAIL_RETURN (ERROR, main_mem != NULL, false, "get memory from main image failed");

    cl_buffer_region region;
    region.origin = offset;
    region.size = size;

    sub_mem = context->create_sub_buffer (main_mem, region, flags);
    if (sub_mem == NULL) {
        XCAM_LOG_WARNING ("CLBuffer create sub buffer failed");
        return false;
    }

    set_mem_id (sub_mem);
    return true;
}

XCamReturn
CLBuffer::enqueue_read (
    void *ptr, uint32_t offset, uint32_t size,
    CLEventList &event_waits,
    SmartPtr<CLEvent> &event_out)
{
    SmartPtr<CLContext> context = get_context ();
    cl_mem mem_id = get_mem_id ();

    XCAM_ASSERT (is_valid ());
    if (!is_valid ())
        return XCAM_RETURN_ERROR_PARAM;

    return context->enqueue_read_buffer (mem_id, ptr, offset, size, true, event_waits, event_out);
}

XCamReturn
CLBuffer::enqueue_write (
    void *ptr, uint32_t offset, uint32_t size,
    CLEventList &event_waits,
    SmartPtr<CLEvent> &event_out)
{
    SmartPtr<CLContext> context = get_context ();
    cl_mem mem_id = get_mem_id ();

    XCAM_ASSERT (is_valid ());
    if (!is_valid ())
        return XCAM_RETURN_ERROR_PARAM;

    return context->enqueue_write_buffer (mem_id, ptr, offset, size, true, event_waits, event_out);
}

XCamReturn
CLBuffer::enqueue_map (
    void *&ptr, uint32_t offset, uint32_t size,
    cl_map_flags map_flags,
    CLEventList &event_waits,
    SmartPtr<CLEvent> &event_out)
{
    SmartPtr<CLContext> context = get_context ();
    cl_mem mem_id = get_mem_id ();
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    XCAM_ASSERT (is_valid ());
    if (!is_valid ())
        return XCAM_RETURN_ERROR_PARAM;

    ret = context->enqueue_map_buffer (mem_id, ptr, offset, size, true, map_flags, event_waits, event_out);
    XCAM_FAIL_RETURN (
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "enqueue_map failed ");

    set_mapped_ptr (ptr);
    return ret;
}

CLImage::CLImage (const SmartPtr<CLContext> &context)
    : CLMemory (context)
{
}

uint32_t
CLImage::get_pixel_bytes () const
{
    return calculate_pixel_bytes(_image_desc.format);
}

bool
CLImage::get_cl_image_info (cl_image_info param_name, size_t param_size, void *param, size_t *param_size_ret)
{
    cl_mem mem_id = get_mem_id ();
    cl_int error_code = CL_SUCCESS;
    if (!mem_id)
        return false;

    error_code = clGetImageInfo (mem_id, param_name, param_size, param, param_size_ret);
    XCAM_FAIL_RETURN(
        WARNING,
        error_code == CL_SUCCESS,
        false,
        "clGetImageInfo failed on param:%d, errno:%d", param_name, error_code);
    return true;
}

uint32_t
CLImage::calculate_pixel_bytes (const cl_image_format &fmt)
{
    uint32_t a = 0, b = 0;
    switch (fmt.image_channel_order) {
    case CL_R:
    case CL_A:
    case CL_Rx:
        a = 1;
        break;
    case CL_RG:
    case CL_RA:
    case CL_RGx:
        a = 2;
        break;
    case CL_RGB:
    case CL_RGBx:
        a = 3;
        break;
    case CL_RGBA:
    case CL_BGRA:
    case CL_ARGB:
        a = 4;
        break;
    default:
        XCAM_LOG_DEBUG ("calculate_pixel_bytes with wrong channel_order:0x%04x", fmt.image_channel_order);
        return 0;
    }

    switch (fmt.image_channel_data_type) {
    case CL_UNORM_INT8:
    case CL_SNORM_INT8:
    case CL_SIGNED_INT8:
    case CL_UNSIGNED_INT8:
        b = 1;
        break;
    case CL_SNORM_INT16:
    case CL_UNORM_INT16:
    case CL_SIGNED_INT16:
    case CL_UNSIGNED_INT16:
    case CL_HALF_FLOAT:
        b = 2;
        break;
    case CL_UNORM_INT24:
        b = 3;
        break;
    case CL_SIGNED_INT32:
    case CL_UNSIGNED_INT32:
    case CL_FLOAT:
        b = 4;
        break;
    default:
        XCAM_LOG_DEBUG ("calculate_pixel_bytes with wrong channel_data_type:0x%04x", fmt.image_channel_data_type);
        return 0;
    }

    return a * b;
}

bool
CLImage::video_info_2_cl_image_desc (
    const VideoBufferInfo & video_info,
    CLImageDesc &image_desc)
{
    image_desc.width = video_info.width;
    image_desc.height = video_info.height;
    image_desc.array_size = 0;
    image_desc.row_pitch = video_info.strides[0];
    XCAM_ASSERT (image_desc.row_pitch >= image_desc.width);
    image_desc.slice_pitch = 0;

    switch (video_info.format) {
    case XCAM_PIX_FMT_RGB48:
        //cl_image_info.fmt.image_channel_order = CL_RGB;
        //cl_image_info.fmt.image_channel_data_type = CL_UNORM_INT16;
        XCAM_LOG_WARNING (
            "video_info to cl_image_info doesn't support XCAM_PIX_FMT_RGB48, maybe try XCAM_PIX_FMT_RGBA64 instread\n"
            " **** XCAM_PIX_FMT_RGB48 need check with cl implementation ****");
        return false;
        break;
    case V4L2_PIX_FMT_GREY:
        image_desc.format.image_channel_order = CL_R;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;

    case XCAM_PIX_FMT_RGBA64:
        image_desc.format.image_channel_order = CL_RGBA;
        image_desc.format.image_channel_data_type = CL_UNORM_INT16;
        break;

    case V4L2_PIX_FMT_RGB24:
        image_desc.format.image_channel_order = CL_RGB;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;

    case V4L2_PIX_FMT_RGB565:
        image_desc.format.image_channel_order = CL_RGB;
        image_desc.format.image_channel_data_type = CL_UNORM_SHORT_565;
        break;
    case V4L2_PIX_FMT_XBGR32:
    case V4L2_PIX_FMT_ABGR32:
    case V4L2_PIX_FMT_BGR32:
        image_desc.format.image_channel_order = CL_BGRA;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;
        // cl doesn'tn support ARGB32 up to now, how about consider V4L2_PIX_FMT_RGBA32
    case V4L2_PIX_FMT_RGB32:
    case V4L2_PIX_FMT_ARGB32:
    case V4L2_PIX_FMT_XRGB32:
        image_desc.format.image_channel_order = CL_ARGB;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;

    case V4L2_PIX_FMT_RGBA32:
        image_desc.format.image_channel_order = CL_RGBA;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;

    case V4L2_PIX_FMT_SBGGR10:
    case V4L2_PIX_FMT_SGBRG10:
    case V4L2_PIX_FMT_SGRBG10:
    case V4L2_PIX_FMT_SRGGB10:
    case V4L2_PIX_FMT_SBGGR12:
    case V4L2_PIX_FMT_SGBRG12:
    case V4L2_PIX_FMT_SGRBG12:
    case V4L2_PIX_FMT_SRGGB12:
    case V4L2_PIX_FMT_SBGGR16:
    case XCAM_PIX_FMT_SGRBG16:
        image_desc.format.image_channel_order = CL_R;
        image_desc.format.image_channel_data_type = CL_UNORM_INT16;
        break;

    case V4L2_PIX_FMT_SBGGR8:
    case V4L2_PIX_FMT_SGBRG8:
    case V4L2_PIX_FMT_SGRBG8:
    case V4L2_PIX_FMT_SRGGB8:
        image_desc.format.image_channel_order = CL_R;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        break;

    case V4L2_PIX_FMT_NV12:
        image_desc.format.image_channel_order = CL_R;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        image_desc.array_size = 2;
        image_desc.slice_pitch = video_info.strides [0] * video_info.aligned_height;
        break;

    case V4L2_PIX_FMT_YUYV:
        image_desc.format.image_channel_order = CL_RGBA;
        image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        image_desc.width /= 2;
        break;

    case XCAM_PIX_FMT_LAB:
        image_desc.format.image_channel_order = CL_R;
        image_desc.format.image_channel_data_type = CL_FLOAT;
        break;

    case XCAM_PIX_FMT_RGB48_planar:
    case XCAM_PIX_FMT_RGB24_planar:
        image_desc.format.image_channel_order = CL_RGBA;
        if (XCAM_PIX_FMT_RGB48_planar == video_info.format)
            image_desc.format.image_channel_data_type = CL_UNORM_INT16;
        else
            image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        image_desc.width = video_info.aligned_width / 4;
        image_desc.array_size = 3;
        image_desc.slice_pitch = video_info.strides [0] * video_info.aligned_height;
        break;

    case XCAM_PIX_FMT_SGRBG16_planar:
    case XCAM_PIX_FMT_SGRBG8_planar:
        image_desc.format.image_channel_order = CL_RGBA;
        if (XCAM_PIX_FMT_SGRBG16_planar == video_info.format)
            image_desc.format.image_channel_data_type = CL_UNORM_INT16;
        else
            image_desc.format.image_channel_data_type = CL_UNORM_INT8;
        image_desc.width = video_info.aligned_width / 4;
        image_desc.array_size = 4;
        image_desc.slice_pitch = video_info.strides [0] * video_info.aligned_height;
        break;

    default:
        XCAM_LOG_WARNING (
            "video_info to cl_image_info doesn't support format:%s",
            xcam_fourcc_to_string (video_info.format));
        return false;
    }

    return true;
}

void
CLImage::init_desc_by_image ()
{
    size_t width = 0, height = 0, row_pitch = 0, slice_pitch = 0, array_size = 0, mem_size = 0;
    cl_image_format format = {CL_R, CL_UNORM_INT8};

    get_cl_image_info (CL_IMAGE_FORMAT, sizeof(format), &format);
    get_cl_image_info (CL_IMAGE_WIDTH, sizeof(width), &width);
    get_cl_image_info (CL_IMAGE_HEIGHT, sizeof(height), &height);
    get_cl_image_info (CL_IMAGE_ROW_PITCH, sizeof(row_pitch), &row_pitch);
    get_cl_image_info (CL_IMAGE_SLICE_PITCH, sizeof(slice_pitch), &slice_pitch);
    get_cl_image_info (CL_IMAGE_ARRAY_SIZE, sizeof(array_size), &array_size);
    get_cl_mem_info (CL_MEM_SIZE, sizeof(mem_size), &mem_size);

    _image_desc.format = format;
    _image_desc.width = width;
    _image_desc.height = height;
    _image_desc.row_pitch = row_pitch;
    _image_desc.slice_pitch = slice_pitch;
    _image_desc.array_size = array_size;
    _image_desc.size = mem_size;
}

XCamReturn
CLImage::enqueue_map (
    void *&ptr,
    size_t *origin, size_t *region,
    size_t *row_pitch, size_t *slice_pitch,
    cl_map_flags map_flags,
    CLEventList &event_waits,
    SmartPtr<CLEvent> &event_out)
{
    SmartPtr<CLContext> context = get_context ();
    cl_mem mem_id = get_mem_id ();
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    XCAM_ASSERT (is_valid ());
    if (!is_valid ())
        return XCAM_RETURN_ERROR_PARAM;

    ret = context->enqueue_map_image (mem_id, ptr, origin, region, row_pitch, slice_pitch, true, map_flags, event_waits, event_out);
    XCAM_FAIL_RETURN (
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "enqueue_map failed ");

    set_mapped_ptr (ptr);
    return ret;
}

CLImage2D::CLImage2D (
    const SmartPtr<CLContext> &context,
    const VideoBufferInfo &video_info,
    cl_mem_flags  flags)
    : CLImage (context)
{
    CLImageDesc cl_desc;

    if (!video_info_2_cl_image_desc (video_info, cl_desc)) {
        XCAM_LOG_WARNING ("CLVaImage create va image failed on default videoinfo");
        return;
    }

    init_image_2d (context, cl_desc, flags);
}

CLImage2D::CLImage2D (
    const SmartPtr<CLContext> &context,
    const CLImageDesc &cl_desc,
    cl_mem_flags  flags,
    SmartPtr<CLBuffer> bind_buf)
    : CLImage (context)
{
    _bind_buf = bind_buf;
    init_image_2d (context, cl_desc, flags);
}

bool CLImage2D::init_image_2d (
    const SmartPtr<CLContext> &context,
    const CLImageDesc &desc,
    cl_mem_flags  flags)
{
    cl_mem mem_id = 0;
    cl_image_desc cl_desc;

    xcam_mem_clear (cl_desc);
    cl_desc.image_type = CL_MEM_OBJECT_IMAGE2D;
    cl_desc.image_width = desc.width;
    cl_desc.image_height = desc.height;
    cl_desc.image_depth = 1;
    cl_desc.image_array_size = 0;
    cl_desc.image_row_pitch = 0;
    cl_desc.image_slice_pitch = 0;
    cl_desc.num_mip_levels = 0;
    cl_desc.num_samples = 0;
    cl_desc.buffer = NULL;
    if (_bind_buf.ptr ()) {
        if (desc.row_pitch)
            cl_desc.image_row_pitch = desc.row_pitch;
        else {
            cl_desc.image_row_pitch = calculate_pixel_bytes(desc.format) * desc.width;
        }
        XCAM_ASSERT (cl_desc.image_row_pitch);
        cl_desc.buffer = _bind_buf->get_mem_id ();
        XCAM_ASSERT (cl_desc.buffer);
    }

    mem_id = context->create_image (flags, desc.format, cl_desc);
    if (mem_id == NULL) {
        XCAM_LOG_WARNING ("CLImage2D create image 2d failed");
        return false;
    }
    set_mem_id (mem_id);
    init_desc_by_image ();
    return true;
}

CLImage2DArray::CLImage2DArray (
    const SmartPtr<CLContext> &context,
    const VideoBufferInfo &video_info,
    cl_mem_flags  flags,
    uint32_t extra_array_size)
    : CLImage (context)
{
    CLImageDesc cl_desc;

    XCAM_ASSERT (video_info.components >= 2);

    if (!video_info_2_cl_image_desc (video_info, cl_desc)) {
        XCAM_LOG_WARNING ("CLVaImage create va image failed on default videoinfo");
        return;
    }
    XCAM_ASSERT (cl_desc.array_size >= 2);

    //special process for BYT platform for slice-pitch
    //if (video_info.format == V4L2_PIX_FMT_NV12)
    cl_desc.height = XCAM_ALIGN_UP (cl_desc.height, 16);

    cl_desc.array_size += extra_array_size;

    init_image_2d_array (context, cl_desc, flags);
}

bool CLImage2DArray::init_image_2d_array (
    const SmartPtr<CLContext> &context,
    const CLImageDesc &desc,
    cl_mem_flags  flags)
{
    cl_mem mem_id = 0;
    cl_image_desc cl_desc;

    xcam_mem_clear (cl_desc);
    cl_desc.image_type = CL_MEM_OBJECT_IMAGE2D_ARRAY;
    cl_desc.image_width = desc.width;
    cl_desc.image_height = desc.height;
    cl_desc.image_depth = 1;
    cl_desc.image_array_size = desc.array_size;
    cl_desc.image_row_pitch = 0;
    cl_desc.image_slice_pitch = 0;
    cl_desc.num_mip_levels = 0;
    cl_desc.num_samples = 0;
    cl_desc.buffer = NULL;

    mem_id = context->create_image (flags, desc.format, cl_desc);
    if (mem_id == NULL) {
        XCAM_LOG_WARNING ("CLImage2D create image 2d failed");
        return false;
    }
    set_mem_id (mem_id);
    init_desc_by_image ();
    return true;
}


};