/*
 * cl_yuv_pipe_handler.cpp - CL YuvPipe Pipe handler
 *
 *  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: Wangfei <feix.w.wang@intel.com>
 * Author: Wind Yuan <feng.yuan@intel.com>
 */

#include "cl_utils.h"
#include "cl_yuv_pipe_handler.h"

#define USE_BUFFER_OBJECT 0

namespace XCam {

static const XCamKernelInfo kernel_yuv_pipe_info = {
    "kernel_yuv_pipe",
#include "kernel_yuv_pipe.clx"
    , 0,
};

float default_matrix[XCAM_COLOR_MATRIX_SIZE] = {
    0.299f, 0.587f, 0.114f,
    -0.14713f, -0.28886f, 0.436f,
    0.615f, -0.51499f, -0.10001f,
};
float default_macc[XCAM_CHROMA_AXIS_SIZE * XCAM_CHROMA_MATRIX_SIZE] = {
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
    1.000000f, 0.000000f, 0.000000f, 1.000000f, 1.000000f, 0.000000f, 0.000000f, 1.000000f,
};

CLYuvPipeImageKernel::CLYuvPipeImageKernel (const SmartPtr<CLContext> &context)
    : CLImageKernel (context, "kernel_yuv_pipe")

{
}

CLYuvPipeImageHandler::CLYuvPipeImageHandler (const SmartPtr<CLContext> &context, const char *name)
    : CLImageHandler (context, name)
    , _output_format(V4L2_PIX_FMT_NV12)
    , _enable_tnr_yuv (0)
    , _gain_yuv (1.0)
    , _thr_y (0.05)
    , _thr_uv (0.05)
    ,  _enable_tnr_yuv_state (0)

{
    memcpy(_macc_table, default_macc, sizeof(float)*XCAM_CHROMA_AXIS_SIZE * XCAM_CHROMA_MATRIX_SIZE);
    memcpy(_rgbtoyuv_matrix, default_matrix, sizeof(float)*XCAM_COLOR_MATRIX_SIZE);
}

bool
CLYuvPipeImageHandler::set_macc_table (const XCam3aResultMaccMatrix &macc)
{
    for(int i = 0; i < XCAM_CHROMA_AXIS_SIZE * XCAM_CHROMA_MATRIX_SIZE; i++)
        _macc_table[i] = (float)macc.table[i];
    return true;
}

bool
CLYuvPipeImageHandler::set_rgbtoyuv_matrix (const XCam3aResultColorMatrix &matrix)
{
    for (int i = 0; i < XCAM_COLOR_MATRIX_SIZE; i++)
        _rgbtoyuv_matrix[i] = (float)matrix.matrix[i];
    return true;
}

XCamReturn
CLYuvPipeImageHandler::prepare_buffer_pool_video_info (
    const VideoBufferInfo &input,
    VideoBufferInfo &output)
{
    bool format_inited = output.init (_output_format, input.width, input.height);

    XCAM_FAIL_RETURN (
        WARNING,
        format_inited,
        XCAM_RETURN_ERROR_PARAM,
        "CL image handler(%s) output format(%s) unsupported",
        get_name (), xcam_fourcc_to_string (_output_format));

    return XCAM_RETURN_NO_ERROR;
}

XCamReturn
CLYuvPipeImageHandler::prepare_parameters (
    SmartPtr<VideoBuffer> &input, SmartPtr<VideoBuffer> &output)
{
    SmartPtr<CLContext> context = get_context ();
    const VideoBufferInfo & video_info_in = input->get_video_info ();
    const VideoBufferInfo & video_info_out = output->get_video_info ();
    CLArgList args;
    CLWorkSize work_size;

    XCAM_ASSERT (_yuv_pipe_kernel.ptr ());
    SmartPtr<CLMemory> buffer_in, buffer_out, buffer_out_UV;

#if !USE_BUFFER_OBJECT
    CLImageDesc in_image_info;
    in_image_info.format.image_channel_order = CL_RGBA;
    in_image_info.format.image_channel_data_type = CL_UNSIGNED_INT32;
    in_image_info.width = video_info_in.aligned_width / 8;
    in_image_info.height = video_info_in.aligned_height * 3;
    in_image_info.row_pitch = video_info_in.strides[0];

    CLImageDesc out_image_info;
    out_image_info.format.image_channel_order = CL_RGBA;
    out_image_info.format.image_channel_data_type = CL_UNSIGNED_INT16;
    out_image_info.width = video_info_out.width / 8;
    out_image_info.height = video_info_out.aligned_height;
    out_image_info.row_pitch = video_info_out.strides[0];

    buffer_in = convert_to_climage (context, input, in_image_info);
    buffer_out = convert_to_climage (context, output, out_image_info, video_info_out.offsets[0]);

    out_image_info.height = video_info_out.aligned_height / 2;
    out_image_info.row_pitch = video_info_out.strides[1];
    buffer_out_UV = convert_to_climage (context, output, out_image_info, video_info_out.offsets[1]);
#else
    buffer_in = convert_to_clbuffer (context, input);
    buffer_out = convert_to_clbuffer (context, output);
#endif
    SmartPtr<CLBuffer> matrix_buffer = new CLBuffer (
        context, sizeof(float)*XCAM_COLOR_MATRIX_SIZE,
        CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR , &_rgbtoyuv_matrix);
    SmartPtr<CLBuffer> macc_table_buffer = new CLBuffer(
        context, sizeof(float)*XCAM_CHROMA_AXIS_SIZE * XCAM_CHROMA_MATRIX_SIZE,
        CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR , &_macc_table);

    uint32_t plannar_offset = video_info_in.aligned_height;

    if (!_buffer_out_prev.ptr ()) {
        _buffer_out_prev = buffer_out;
        _buffer_out_prev_UV = buffer_out_UV;
        _enable_tnr_yuv_state = _enable_tnr_yuv;
        _enable_tnr_yuv = 0;
    }
    else {
        if (_enable_tnr_yuv == 0)
            _enable_tnr_yuv = _enable_tnr_yuv_state;
    }
    XCAM_FAIL_RETURN (
        WARNING,
        buffer_in->is_valid () && buffer_out->is_valid (),
        XCAM_RETURN_ERROR_MEM,
        "cl image handler(%s) in/out memory not available", XCAM_STR (get_name ()));

    //set args;
    args.push_back (new CLMemArgument (buffer_out));

#if !USE_BUFFER_OBJECT
    args.push_back (new CLMemArgument (buffer_out_UV));
#endif

    args.push_back (new CLMemArgument (_buffer_out_prev));

#if !USE_BUFFER_OBJECT
    args.push_back (new CLMemArgument (_buffer_out_prev_UV));
#else
    uint32_t vertical_offset = video_info_out.aligned_height;
    args.push_back (new CLArgumentT<uint32_t> (vertical_offset));
#endif
    args.push_back (new CLArgumentT<uint32_t> (plannar_offset));
    args.push_back (new CLMemArgument (matrix_buffer));
    args.push_back (new CLMemArgument (macc_table_buffer));
    args.push_back (new CLArgumentT<float> (_gain_yuv));
    args.push_back (new CLArgumentT<float> (_thr_y));
    args.push_back (new CLArgumentT<float> (_thr_uv));
    args.push_back (new CLArgumentT<uint32_t> (_enable_tnr_yuv));
    args.push_back (new CLMemArgument (buffer_in));

    work_size.dim = XCAM_DEFAULT_IMAGE_DIM;
    work_size.global[0] = video_info_out.width / 8 ;
    work_size.global[1] = video_info_out.aligned_height / 2 ;
    work_size.local[0] = 8;
    work_size.local[1] = 4;

    XCAM_ASSERT (_yuv_pipe_kernel.ptr ());
    XCamReturn ret = _yuv_pipe_kernel->set_arguments (args, work_size);
    XCAM_FAIL_RETURN (
        WARNING, ret == XCAM_RETURN_NO_ERROR, ret,
        "yuv pipe kernel set arguments failed.");

    if (buffer_out->is_valid ()) {
        _buffer_out_prev = buffer_out;
        _buffer_out_prev_UV = buffer_out_UV;
    }

    return XCAM_RETURN_NO_ERROR;
}

bool
CLYuvPipeImageHandler::set_yuv_pipe_kernel(SmartPtr<CLYuvPipeImageKernel> &kernel)
{
    SmartPtr<CLImageKernel> image_kernel = kernel;
    add_kernel (image_kernel);
    _yuv_pipe_kernel = kernel;
    return true;
}

bool
CLYuvPipeImageHandler::set_tnr_yuv_config (const XCam3aResultTemporalNoiseReduction& config)
{
    if (!_yuv_pipe_kernel->is_valid ()) {
        XCAM_LOG_ERROR ("set config error, invalid YUV-Pipe kernel !");
    }

    _gain_yuv = (float)config.gain;
    _thr_y = (float)config.threshold[0];
    _thr_uv = (float)config.threshold[1];
    XCAM_LOG_DEBUG ("set TNR YUV config: _gain(%f), _thr_y(%f), _thr_uv(%f)",
                    _gain_yuv, _thr_y, _thr_uv);
    return true;
}

bool
CLYuvPipeImageHandler::set_tnr_enable (bool enable_tnr_yuv)
{
    _enable_tnr_yuv = (enable_tnr_yuv ? 1 : 0);
    return true;
}

SmartPtr<CLImageHandler>
create_cl_yuv_pipe_image_handler (const SmartPtr<CLContext> &context)
{
    SmartPtr<CLYuvPipeImageHandler> yuv_pipe_handler;
    SmartPtr<CLYuvPipeImageKernel> yuv_pipe_kernel;

    yuv_pipe_kernel = new CLYuvPipeImageKernel (context);
    XCAM_ASSERT (yuv_pipe_kernel.ptr ());
    const char * options = USE_BUFFER_OBJECT ? "-DUSE_BUFFER_OBJECT=1" : "-DUSE_BUFFER_OBJECT=0";
    XCAM_FAIL_RETURN (
        ERROR, yuv_pipe_kernel->build_kernel (kernel_yuv_pipe_info, options) == XCAM_RETURN_NO_ERROR, NULL,
        "build yuv-pipe kernel(%s) failed", kernel_yuv_pipe_info.kernel_name);

    XCAM_ASSERT (yuv_pipe_kernel->is_valid ());
    yuv_pipe_handler = new CLYuvPipeImageHandler (context, "cl_handler_pipe_yuv");
    yuv_pipe_handler->set_yuv_pipe_kernel (yuv_pipe_kernel);

    return yuv_pipe_handler;
}

};