/*
  * test-pipe-manager.cpp -test pipe manager
  *
  *  Copyright (c) 2016 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: Yinhang Liu <yinhangx.liu@intel.com>
  */

#include <pipe_manager.h>
#include <smart_analyzer_loader.h>
#include <ocl/cl_post_image_processor.h>
#if HAVE_LIBDRM
#include <drm_display.h>
#endif
#include <getopt.h>
#include <test_common.h>
#include <signal.h>
#include <stdio.h>

#define DEFAULT_FPT_BUF_COUNT 32

using namespace XCam;

static bool is_stop = false;

struct FileFP {
    FILE *fp;
    FileFP ()
        : fp (NULL)
    {}
    ~FileFP ()
    {
        if (fp)
            fclose (fp);
        fp = NULL;
    }
};

class MainPipeManager
    : public PipeManager
{
public:
    MainPipeManager ()
        : _image_width (0)
        , _image_height (0)
        , _enable_display (false)
    {
#if HAVE_LIBDRM
        _display = DrmDisplay::instance ();
#endif
        XCAM_OBJ_PROFILING_INIT;
    }

    void set_image_width (uint32_t image_width) {
        _image_width = image_width;
    }

    void set_image_height (uint32_t image_height) {
        _image_height = image_height;
    }

    void enable_display (bool value) {
        _enable_display = value;
    }

#if HAVE_LIBDRM
    void set_display_mode (DrmDisplayMode mode) {
        _display->set_display_mode (mode);
    }
#endif

protected:
    virtual void post_buffer (const SmartPtr<VideoBuffer> &buf);
    int display_buf (const SmartPtr<VideoBuffer> &buf);

private:
    uint32_t              _image_width;
    uint32_t              _image_height;
    bool                  _enable_display;
#if HAVE_LIBDRM
    SmartPtr<DrmDisplay>  _display;
#endif
    XCAM_OBJ_PROFILING_DEFINES;
};

void
MainPipeManager::post_buffer (const SmartPtr<VideoBuffer> &buf)
{
    FPS_CALCULATION (fps_buf, XCAM_OBJ_DUR_FRAME_NUM);

    XCAM_OBJ_PROFILING_START;

    if (_enable_display)
        display_buf (buf);

    XCAM_OBJ_PROFILING_END("main_pipe_manager_display", XCAM_OBJ_DUR_FRAME_NUM);
}

int
MainPipeManager::display_buf (const SmartPtr<VideoBuffer> &data)
{
#if HAVE_LIBDRM
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    SmartPtr<VideoBuffer> buf = data;
    const VideoBufferInfo & frame_info = buf->get_video_info ();
    struct v4l2_rect rect = { 0, 0, frame_info.width, frame_info.height};

    if (!_display->is_render_inited ()) {
        ret = _display->render_init (0, 0, this->_image_width, this->_image_height,
                                     frame_info.format, &rect);
        CHECK (ret, "display failed on render_init");
    }
    ret = _display->render_setup_frame_buffer (buf);
    CHECK (ret, "display failed on framebuf set");
    ret = _display->render_buffer (buf);
    CHECK (ret, "display failed on rendering");
#else
    XCAM_UNUSED (data);
#endif

    return 0;
}

XCamReturn
read_buf (SmartPtr<VideoBuffer> &buf, FileFP &file)
{
    const VideoBufferInfo info = buf->get_video_info ();
    VideoBufferPlanarInfo planar;
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    uint8_t *memory = buf->map ();
    for (uint32_t index = 0; index < info.components; index++) {
        info.get_planar_info (planar, index);
        uint32_t line_bytes = planar.width * planar.pixel_bytes;

        for (uint32_t i = 0; i < planar.height; i++) {
            if (fread (memory + info.offsets [index] + i * info.strides [index], 1, line_bytes, file.fp) != line_bytes) {
                if (feof (file.fp)) {
                    fseek (file.fp, 0, SEEK_SET);
                    ret = XCAM_RETURN_BYPASS;
                } else {
                    XCAM_LOG_ERROR ("read file failed, size doesn't match");
                    ret = XCAM_RETURN_ERROR_FILE;
                }
                goto done;
            }
        }
    }
done:
    buf->unmap ();
    return ret;
}

void pipe_stop_handler(int sig)
{
    XCAM_UNUSED (sig);
    is_stop = true;
}

void print_help (const char *bin_name)
{
    printf ("Usage: %s [--format=NV12] [--width=1920] ...\n"
            "\t --format           specify output pixel format, default is NV12\n"
            "\t --width            specify input image width, default is 1920\n"
            "\t --height           specify input image height, default is 1080\n"
            "\t --fake-input       specify the path of image as fake source\n"
            "\t --defog-mode       specify defog mode\n"
            "\t                    select from [disabled, retinex, dcp], default is [disabled]\n"
            "\t --wavelet-mode     specify wavelet denoise mode, default is disable\n"
            "\t                    select from [0:disable, 1:Hat Y, 2:Hat UV, 3:Haar Y, 4:Haar UV, 5:Haar YUV, 6:Haar Bayes Shrink]\n"
            "\t --3d-denoise       specify 3D Denoise mode\n"
            "\t                    select from [disabled, yuv, uv], default is [disabled]\n"
            "\t --enable-wireframe enable wire frame\n"
            "\t --enable-warp      enable image warp\n"
            "\t --display-mode     display mode\n"
            "\t                    select from [primary, overlay], default is [primary]\n"
            "\t -p                 enable local display, need root privilege\n"
            "\t -h                 help\n"
            , bin_name);
}

int main (int argc, char *argv[])
{
    const char *bin_name = argv[0];

    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    VideoBufferInfo buf_info;
    SmartPtr<VideoBuffer> video_buf;
    SmartPtr<SmartAnalyzer> smart_analyzer;
    SmartPtr<CLPostImageProcessor> cl_post_processor;
    SmartPtr<BufferPool> buf_pool;

    uint32_t pixel_format = V4L2_PIX_FMT_NV12;
    uint32_t image_width = 1920;
    uint32_t image_height = 1080;
    bool need_display = false;
#if HAVE_LIBDRM
    DrmDisplayMode display_mode = DRM_DISPLAY_MODE_PRIMARY;
#endif
    const char *input_path = NULL;
    FileFP input_fp;

    uint32_t defog_mode = 0;
    CLWaveletBasis wavelet_mode = CL_WAVELET_DISABLED;
    uint32_t wavelet_channel = CL_IMAGE_CHANNEL_UV;
    bool wavelet_bayes_shrink = false;
    uint32_t denoise_3d_mode = 0;
    uint8_t denoise_3d_ref_count = 3;
    bool enable_wireframe = false;
    bool enable_image_warp = false;

    int opt;
    const char *short_opts = "ph";
    const struct option long_opts [] = {
        {"format", required_argument, NULL, 'F'},
        {"width", required_argument, NULL, 'W'},
        {"height", required_argument, NULL, 'H'},
        {"fake-input", required_argument, NULL, 'A'},
        {"defog-mode", required_argument, NULL, 'D'},
        {"wavelet-mode", required_argument, NULL, 'V'},
        {"3d-denoise", required_argument, NULL, 'N'},
        {"enable-wireframe", no_argument, NULL, 'I'},
        {"enable-warp", no_argument, NULL, 'S'},
        {"display-mode", required_argument, NULL, 'P'},
        {NULL, 0, NULL, 0}
    };

    while ((opt = getopt_long (argc, argv, short_opts, long_opts, NULL)) != -1) {
        switch (opt) {
        case 'F': {
            XCAM_ASSERT (optarg);
            CHECK_EXP ((strlen (optarg) == 4), "invalid pixel format\n");
            pixel_format = v4l2_fourcc ((unsigned) optarg[0],
                                        (unsigned) optarg[1],
                                        (unsigned) optarg[2],
                                        (unsigned) optarg[3]);
            break;
        }
        case 'W': {
            XCAM_ASSERT (optarg);
            image_width = atoi (optarg);
            break;
        }
        case 'H': {
            XCAM_ASSERT (optarg);
            image_height = atoi (optarg);
            break;
        }
        case 'A': {
            XCAM_ASSERT (optarg);
            XCAM_LOG_INFO ("use image %s as input source", optarg);
            input_path = optarg;
            break;
        }
        case 'D': {
            XCAM_ASSERT (optarg);
            if (!strcmp (optarg, "disabled"))
                defog_mode = CLPostImageProcessor::DefogDisabled;
            else if (!strcmp (optarg, "retinex"))
                defog_mode = CLPostImageProcessor::DefogRetinex;
            else if (!strcmp (optarg, "dcp"))
                defog_mode = CLPostImageProcessor::DefogDarkChannelPrior;
            else {
                print_help (bin_name);
                return -1;
            }
            break;
        }
        case 'V': {
            XCAM_ASSERT (optarg);
            if (atoi(optarg) < 0 || atoi(optarg) > 255) {
                print_help (bin_name);
                return -1;
            }
            if (atoi(optarg) == 1) {
                wavelet_mode = CL_WAVELET_HAT;
                wavelet_channel = CL_IMAGE_CHANNEL_Y;
            } else if (atoi(optarg) == 2) {
                wavelet_mode = CL_WAVELET_HAT;
                wavelet_channel = CL_IMAGE_CHANNEL_UV;
            } else if (atoi(optarg) == 3) {
                wavelet_mode = CL_WAVELET_HAAR;
                wavelet_channel = CL_IMAGE_CHANNEL_Y;
            } else if (atoi(optarg) == 4) {
                wavelet_mode = CL_WAVELET_HAAR;
                wavelet_channel = CL_IMAGE_CHANNEL_UV;
            } else if (atoi(optarg) == 5) {
                wavelet_mode = CL_WAVELET_HAAR;
                wavelet_channel = CL_IMAGE_CHANNEL_UV | CL_IMAGE_CHANNEL_Y;
            } else if (atoi(optarg) == 6) {
                wavelet_mode = CL_WAVELET_HAAR;
                wavelet_channel = CL_IMAGE_CHANNEL_UV | CL_IMAGE_CHANNEL_Y;
                wavelet_bayes_shrink = true;
            } else {
                wavelet_mode = CL_WAVELET_DISABLED;
            }
            break;
        }
        case 'N': {
            XCAM_ASSERT (optarg);
            if (!strcmp (optarg, "disabled"))
                denoise_3d_mode = CLPostImageProcessor::Denoise3DDisabled;
            else if (!strcmp (optarg, "yuv"))
                denoise_3d_mode = CLPostImageProcessor::Denoise3DYuv;
            else if (!strcmp (optarg, "uv"))
                denoise_3d_mode = CLPostImageProcessor::Denoise3DUV;
            else {
                print_help (bin_name);
                return -1;
            }
            break;
        }
        case 'I': {
            enable_wireframe = true;
            break;
        }
        case 'S': {
            enable_image_warp = true;
            break;
        }
        case 'P': {
#if HAVE_LIBDRM
            XCAM_ASSERT (optarg);
            if (!strcmp (optarg, "primary"))
                display_mode = DRM_DISPLAY_MODE_PRIMARY;
            else if (!strcmp (optarg, "overlay"))
                display_mode = DRM_DISPLAY_MODE_OVERLAY;
            else {
                print_help (bin_name);
                return -1;
            }
#else
            XCAM_LOG_WARNING ("preview is not supported");
#endif
            break;
        }
        case 'p': {
#if HAVE_LIBDRM
            need_display = true;
#else
            XCAM_LOG_WARNING ("preview is not supported, disable preview now");
            need_display = false;
#endif
            break;
        }
        case 'h':
            print_help (bin_name);
            return 0;
        default:
            print_help (bin_name);
            return -1;
        }
    }

    signal (SIGINT, pipe_stop_handler);

    if (!input_path) {
        XCAM_LOG_ERROR ("path of image is NULL");
        return -1;
    }
    input_fp.fp = fopen (input_path, "rb");
    if (!input_fp.fp) {
        XCAM_LOG_ERROR ("failed to open file: %s", XCAM_STR (input_path));
        return -1;
    }

    SmartPtr<MainPipeManager> pipe_manager = new MainPipeManager ();
    pipe_manager->set_image_width (image_width);
    pipe_manager->set_image_height (image_height);

    SmartHandlerList smart_handlers = SmartAnalyzerLoader::load_smart_handlers (DEFAULT_SMART_ANALYSIS_LIB_DIR);
    if (!smart_handlers.empty () ) {
        smart_analyzer = new SmartAnalyzer ();
        if (smart_analyzer.ptr ()) {
            SmartHandlerList::iterator i_handler = smart_handlers.begin ();
            for (; i_handler != smart_handlers.end (); ++i_handler) {
                XCAM_ASSERT ((*i_handler).ptr ());
                smart_analyzer->add_handler (*i_handler);
            }
        } else {
            XCAM_LOG_INFO ("load smart analyzer(%s) failed", DEFAULT_SMART_ANALYSIS_LIB_DIR);
        }
    }
    if (smart_analyzer.ptr ()) {
        if (smart_analyzer->prepare_handlers () != XCAM_RETURN_NO_ERROR) {
            XCAM_LOG_WARNING ("analyzer(%s) prepare handlers failed", smart_analyzer->get_name ());
        }
        pipe_manager->set_smart_analyzer (smart_analyzer);
    }

    cl_post_processor = new CLPostImageProcessor ();
    cl_post_processor->set_stats_callback (pipe_manager);
    cl_post_processor->set_defog_mode ((CLPostImageProcessor::CLDefogMode) defog_mode);
    cl_post_processor->set_wavelet (wavelet_mode, wavelet_channel, wavelet_bayes_shrink);
    cl_post_processor->set_3ddenoise_mode ((CLPostImageProcessor::CL3DDenoiseMode) denoise_3d_mode, denoise_3d_ref_count);

    cl_post_processor->set_wireframe (enable_wireframe);
    cl_post_processor->set_image_warp (enable_image_warp);
    if (smart_analyzer.ptr () && (enable_wireframe || enable_image_warp)) {
        cl_post_processor->set_scaler (true);
        cl_post_processor->set_scaler_factor (640.0 / image_width);
    }

    pipe_manager->add_image_processor (cl_post_processor);

    buf_info.init (pixel_format, image_width, image_height);
    buf_pool = new CLVideoBufferPool ();
    XCAM_ASSERT (buf_pool.ptr ());
    if (!buf_pool->set_video_info (buf_info) || !buf_pool->reserve (DEFAULT_FPT_BUF_COUNT)) {
        XCAM_LOG_ERROR ("init buffer pool failed");
        return -1;
    }

    if (need_display) {
        need_display = false;
        XCAM_LOG_WARNING ("CLVideoBuffer doesn't support local preview, disable local preview now");
    }

    if (need_display) {
#if HAVE_LIBDRM
        if (DrmDisplay::set_preview (need_display)) {
            pipe_manager->set_display_mode (display_mode);
            cl_post_processor->set_output_format (V4L2_PIX_FMT_XBGR32);
        } else {
            need_display = false;
            XCAM_LOG_WARNING ("set preview failed, disable local preview now");
        }
#else
        XCAM_LOG_WARNING ("preview is not supported, disable preview now");
        need_display = false;
#endif
    }
    pipe_manager->enable_display (need_display);

    ret = pipe_manager->start ();
    CHECK (ret, "pipe manager start failed");

    while (!is_stop) {
        video_buf = buf_pool->get_buffer (buf_pool);
        XCAM_ASSERT (video_buf.ptr ());

        ret = read_buf (video_buf, input_fp);
        if (ret == XCAM_RETURN_BYPASS) {
            ret = read_buf (video_buf, input_fp);
        }

        if (ret == XCAM_RETURN_NO_ERROR)
            pipe_manager->push_buffer (video_buf);
    }

    ret = pipe_manager->stop();
    CHECK (ret, "pipe manager stop failed");

    return 0;
}