/*
 * sample_smart_analysis.cpp - smart analysis sample code
 *
 *  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: Zong Wei <wei.zong@intel.com>
 */

#include <base/xcam_smart_description.h>
#include <base/xcam_buffer.h>
#include <xcam_std.h>
#include "aiq3a_utils.h"
#include "x3a_result_factory.h"
#include "smart_analyzer.h"

using namespace XCam;

#define DEFAULT_SAVE_FRAME_NAME "frame_buffer"
#define XSMART_ANALYSIS_CONTEXT_CAST(context)  ((XCamSmartAnalyerContext*)(context))

class FrameSaver
{
public:
    explicit FrameSaver (bool save, uint32_t interval, uint32_t count);
    ~FrameSaver ();

    void save_frame (XCamVideoBuffer *buffer);

    void enable_save_file (bool enable) {
        _save_file = enable;
    }
    void set_interval (uint32_t inteval) {
        _interval = inteval;
    }
    void set_frame_save (uint32_t frame_save) {
        _frame_save = frame_save;
    }

private:
    XCAM_DEAD_COPY (FrameSaver);
    void open_file ();
    void close_file ();

private:
    FILE *_file;
    bool _save_file;
    uint32_t _interval;
    uint32_t _frame_save;
    uint32_t _frame_count;
    uint32_t _skip_frame_count;

};

FrameSaver::FrameSaver (bool save, uint32_t interval, uint32_t count)
    : _file (NULL)
    , _save_file (save)
    , _interval (interval)
    , _frame_save (count)
    , _frame_count (0)
    , _skip_frame_count (300)
{
}

FrameSaver::~FrameSaver ()
{
    close_file ();
}

void
FrameSaver::save_frame (XCamVideoBuffer *buffer)
{
    if (NULL == buffer) {
        return;
    }
    if (!_save_file)
        return ;

    if ((_frame_count++ % _interval) != 0)
        return;

    if (_frame_count < _skip_frame_count)
        return;

    if (_frame_count > (_frame_save * _interval + _skip_frame_count)) {
        return;
    }

    open_file ();

    if (!_file) {
        XCAM_LOG_ERROR ("open file failed");
        return;
    }

    uint8_t *memory = xcam_video_buffer_map (buffer);
    XCamVideoBufferPlanarInfo planar;
    for (uint32_t index = 0; index < buffer->info.components; index++) {
        xcam_video_buffer_get_planar_info (&buffer->info, &planar, index);
        uint32_t line_bytes = planar.width * planar.pixel_bytes;

        for (uint32_t i = 0; i < planar.height; i++) {
            if (fwrite (memory + buffer->info.offsets [index] + i * buffer->info.strides [index],
                        1, line_bytes, _file) != line_bytes) {
                XCAM_LOG_ERROR ("write file failed, size doesn't match");
                return;
            }
        }
    }
    xcam_video_buffer_unmap (buffer);
    close_file ();
}

void
FrameSaver::open_file ()
{
    if ((_file) && (_frame_save == 0))
        return;

    char file_name[512];
    if (_frame_save != 0) {
        snprintf (file_name, sizeof(file_name), "%s%d%s", DEFAULT_SAVE_FRAME_NAME, _frame_count, ".yuv");
    }

    _file = fopen(file_name, "wb");
}

void
FrameSaver::close_file ()
{
    if (_file)
        fclose (_file);
    _file = NULL;
}

class SampleHandler
{
public:
    explicit SampleHandler (const char *name = NULL);
    virtual ~SampleHandler ();

    XCamReturn init (uint32_t width, uint32_t height, double framerate);
    XCamReturn deinit ();
    bool set_results_callback (AnalyzerCallback *callback);

    XCamReturn update_params (const XCamSmartAnalysisParam *params);
    XCamReturn analyze (XCamVideoBuffer *buffer);

private:
    XCAM_DEAD_COPY (SampleHandler);

private:
    char                    *_name;
    uint32_t                 _width;
    uint32_t                 _height;
    double                   _framerate;
    AnalyzerCallback        *_callback;
    SmartPtr<FrameSaver>    _frameSaver;
};

SampleHandler::SampleHandler (const char *name)
    : _name (NULL)
    , _width (0)
    , _height (0)
    , _framerate (30.0)
    , _callback (NULL)
{
    if (name)
        _name = strndup (name, XCAM_MAX_STR_SIZE);

    if (!_frameSaver.ptr ()) {
        _frameSaver = new FrameSaver (true, 2, 16);
    }
}

SampleHandler::~SampleHandler ()
{
    if (_name)
        xcam_free (_name);
}

XCamReturn
SampleHandler::init (uint32_t width, uint32_t height, double framerate)
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    _width = width;
    _height = height;
    _framerate = framerate;

    return ret;
}

XCamReturn
SampleHandler::deinit ()
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    return ret;
}

bool
SampleHandler::set_results_callback (AnalyzerCallback *callback)
{
    XCAM_ASSERT (!_callback);
    _callback = callback;
    return true;
}

XCamReturn
SampleHandler::update_params (const XCamSmartAnalysisParam *params)
{
    XCAM_UNUSED (params);

    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    return ret;
}

XCamReturn
SampleHandler::analyze (XCamVideoBuffer *buffer)
{
    XCAM_LOG_DEBUG ("Smart SampleHandler::analyze on ts:" XCAM_TIMESTAMP_FORMAT, XCAM_TIMESTAMP_ARGS (buffer->timestamp));
    if (NULL == buffer) {
        return XCAM_RETURN_ERROR_PARAM;
    }
    XCamReturn ret = XCAM_RETURN_NO_ERROR;

    XCAM_LOG_DEBUG ("format(0x%x), color_bits(%d)", buffer->info.format, buffer->info.color_bits);
    XCAM_LOG_DEBUG ("size(%d), components(%d)", buffer->info.size, buffer->info.components);
    XCAM_LOG_DEBUG ("width(%d), heitht(%d)", buffer->info.width, buffer->info.height);
    XCAM_LOG_DEBUG ("aligned_width(%d), aligned_height(%d)", buffer->info.aligned_width, buffer->info.aligned_height);

    _frameSaver->save_frame (buffer);

    X3aResultList results;
    XCam3aResultBrightness xcam3a_brightness_result;
    xcam_mem_clear (xcam3a_brightness_result);
    xcam3a_brightness_result.head.type =   XCAM_3A_RESULT_BRIGHTNESS;
    xcam3a_brightness_result.head.process_type = XCAM_IMAGE_PROCESS_ALWAYS;
    xcam3a_brightness_result.head.version = XCAM_VERSION;
    xcam3a_brightness_result.brightness_level = 9.9;

    SmartPtr<X3aResult> brightness_result =
        X3aResultFactory::instance ()->create_3a_result ((XCam3aResultHead*)&xcam3a_brightness_result);
    results.push_back(brightness_result);

    if (_callback) {
        if (XCAM_RETURN_NO_ERROR == ret) {
            _callback->x3a_calculation_done (NULL, results);
        } else {
            _callback->x3a_calculation_failed (NULL, buffer->timestamp, "pre 3a analyze failed");
        }
    }

    return ret;
}

class XCamSmartAnalyerContext
    : public AnalyzerCallback
{
public:
    XCamSmartAnalyerContext ();
    ~XCamSmartAnalyerContext ();
    bool setup_handler ();
    SmartPtr<SampleHandler> &get_handler () {
        return _handler;
    }

    uint32_t get_results (X3aResultList &results);

    // derive from AnalyzerCallback
    virtual void x3a_calculation_done (XAnalyzer *analyzer, X3aResultList &results);

private:
    XCAM_DEAD_COPY (XCamSmartAnalyerContext);

private:
// members
    SmartPtr<SampleHandler> _handler;
    Mutex                   _result_mutex;
    X3aResultList           _results;
};

XCamSmartAnalyerContext::XCamSmartAnalyerContext ()
{
    setup_handler ();
}

XCamSmartAnalyerContext::~XCamSmartAnalyerContext ()
{
    _handler->deinit ();
}

bool
XCamSmartAnalyerContext::setup_handler ()
{
    XCAM_ASSERT (!_handler.ptr ());
    _handler = new SampleHandler ();
    XCAM_ASSERT (_handler.ptr ());
    _handler->set_results_callback (this);
    return true;
}

void
XCamSmartAnalyerContext::x3a_calculation_done (XAnalyzer *analyzer, X3aResultList &results)
{
    XCAM_UNUSED (analyzer);
    SmartLock  locker (_result_mutex);
    _results.insert (_results.end (), results.begin (), results.end ());
}

uint32_t
XCamSmartAnalyerContext::get_results (X3aResultList &results)
{
    uint32_t size = 0;
    SmartLock  locker (_result_mutex);

    results.assign (_results.begin (), _results.end ());
    size = _results.size ();
    _results.clear ();

    return size;
}

static XCamReturn
xcam_create_context (XCamSmartAnalysisContext **context, uint32_t *async_mode, XcamPostResultsFunc post_func)
{
    XCAM_ASSERT (context);
    XCAM_UNUSED (post_func);
    XCamSmartAnalyerContext *analysis_context = new XCamSmartAnalyerContext ();
    *context = ((XCamSmartAnalysisContext*)(analysis_context));
    *async_mode = false;

    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_destroy_context (XCamSmartAnalysisContext *context)
{
    XCamSmartAnalyerContext *analysis_context = XSMART_ANALYSIS_CONTEXT_CAST (context);
    delete analysis_context;
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_update_params (XCamSmartAnalysisContext *context, const XCamSmartAnalysisParam *params)
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    XCamSmartAnalyerContext *analysis_context = XSMART_ANALYSIS_CONTEXT_CAST (context);
    XCAM_ASSERT (analysis_context);

    SmartPtr<SampleHandler> handler = analysis_context->get_handler ();
    XCAM_ASSERT (handler.ptr ());
    XCAM_ASSERT (params);

    ret = handler->update_params (params);
    if (ret != XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_WARNING ("update params failed");
    }

    return ret;
}

static XCamReturn
xcam_get_results (XCamSmartAnalysisContext *context, XCam3aResultHead *results[], uint32_t *res_count)
{
    XCamSmartAnalyerContext *analysis_context = XSMART_ANALYSIS_CONTEXT_CAST (context);
    XCAM_ASSERT (analysis_context);
    X3aResultList analysis_results;
    uint32_t result_count = analysis_context->get_results (analysis_results);

    if (!result_count) {
        *res_count = 0;
        XCAM_LOG_DEBUG ("Smart Analysis return no result");
        return XCAM_RETURN_NO_ERROR;
    }

    // mark as static
    static XCam3aResultHead *res_array[XCAM_3A_MAX_RESULT_COUNT];
    XCAM_ASSERT (result_count < XCAM_3A_MAX_RESULT_COUNT);
    result_count = translate_3a_results_to_xcam (analysis_results, res_array, XCAM_3A_MAX_RESULT_COUNT);

    for (uint32_t i = 0; i < result_count; ++i) {
        results[i] = res_array[i];
    }
    *res_count = result_count;
    XCAM_ASSERT (result_count > 0);

    return XCAM_RETURN_NO_ERROR;
}


static XCamReturn
xcam_analyze (XCamSmartAnalysisContext *context, XCamVideoBuffer *buffer, XCam3aResultHead *results[], uint32_t *res_count)
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    if (!buffer) {
        return XCAM_RETURN_ERROR_PARAM;
    }

    XCamSmartAnalyerContext *analysis_context = XSMART_ANALYSIS_CONTEXT_CAST (context);
    XCAM_ASSERT (analysis_context);

    SmartPtr<SampleHandler> handler = analysis_context->get_handler ();
    XCAM_ASSERT (handler.ptr ());

    ret = handler->analyze(buffer);
    if (ret != XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_WARNING ("buffer analyze failed");
    }

    xcam_get_results (context, results, res_count);
    return ret;
}

static void
xcam_free_results (XCamSmartAnalysisContext *context, XCam3aResultHead *results[], uint32_t res_count)
{
    XCAM_UNUSED (context);
    for (uint32_t i = 0; i < res_count; ++i) {
        if (results[i])
            free_3a_result (results[i]);
    }
}

XCAM_BEGIN_DECLARE

XCamSmartAnalysisDescription xcam_smart_analysis_desciption = {
    XCAM_VERSION,
    sizeof (XCamSmartAnalysisDescription),
    XCAM_SMART_PLUGIN_PRIORITY_DEFAULT,
    "sample test",
    xcam_create_context,
    xcam_destroy_context,
    xcam_update_params,
    xcam_analyze,
    xcam_free_results
};

XCAM_END_DECLARE