/*
 * aiq_wrapper.cpp - aiq wrapper:
 *
 *  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 <base/xcam_3a_description.h>
#include <xcam_std.h>
#include "x3a_analyzer_aiq.h"
#include "x3a_statistics_queue.h"
#include "aiq3a_utils.h"
#include "x3a_result_factory.h"
#include "x3a_analyze_tuner.h"

#define DEFAULT_AIQ_CPF_FILE       "/etc/atomisp/imx185.cpf"


using namespace XCam;

#define AIQ_CONTEXT_CAST(context)  ((XCam3AAiqContext*)(context))

class XCam3AAiqContext
    : public AnalyzerCallback
{
public:
    XCam3AAiqContext ();
    ~XCam3AAiqContext ();
    bool setup_analyzer (struct atomisp_sensor_mode_data &sensor_mode_data, const char *cpf);
    void set_size (uint32_t width, uint32_t height);
    bool setup_stats_pool (uint32_t bit_depth = 8);
    bool is_stats_pool_ready () const {
        return (_stats_pool.ptr () ? true : false);
    }
    SmartPtr<X3aAnalyzeTuner> &get_analyzer () {
        return _analyzer;
    }

    SmartPtr<X3aIspStatistics> get_stats_buffer ();
    uint32_t get_results (X3aResultList &results);

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

private:
    XCAM_DEAD_COPY (XCam3AAiqContext);

private:
// members
    SmartPtr<X3aAnalyzeTuner>      _analyzer;
    SmartPtr<X3aStatisticsQueue>   _stats_pool;
    uint32_t                       _video_width;
    uint32_t                       _video_height;

    Mutex                          _result_mutex;
    X3aResultList                  _results;
    double                         _brightness_level;
};

XCam3AAiqContext::XCam3AAiqContext ()
    : _video_width (0)
    , _video_height (0)
    , _brightness_level(0)
{
}

XCam3AAiqContext::~XCam3AAiqContext ()
{
    _analyzer->stop ();
    _analyzer->deinit ();
}

bool
XCam3AAiqContext::setup_analyzer (struct atomisp_sensor_mode_data &sensor_mode_data, const char *cpf)
{
    XCAM_ASSERT (!_analyzer.ptr ());
    SmartPtr<X3aAnalyzer> aiq_analyzer = new X3aAnalyzerAiq (sensor_mode_data, cpf);
    XCAM_ASSERT (aiq_analyzer.ptr ());

    _analyzer = new X3aAnalyzeTuner ();
    XCAM_ASSERT (_analyzer.ptr ());

    _analyzer->set_analyzer (aiq_analyzer);
    _analyzer->set_results_callback (this);
    return true;
}

void
XCam3AAiqContext::set_size (uint32_t width, uint32_t height)
{
    _video_width = width;
    _video_height = height;
}

bool
XCam3AAiqContext::setup_stats_pool (uint32_t bit_depth)
{
    VideoBufferInfo info;
    info.init (XCAM_PIX_FMT_SGRBG16, _video_width, _video_height);

    _stats_pool = new X3aStatisticsQueue;
    XCAM_ASSERT (_stats_pool.ptr ());

    _stats_pool->set_bit_depth (bit_depth);
    XCAM_FAIL_RETURN (
        WARNING,
        _stats_pool->set_video_info (info),
        false,
        "3a stats set video info failed");


    if (!_stats_pool->reserve (6)) {
        XCAM_LOG_WARNING ("init_3a_stats_pool failed to reserve stats buffer.");
        return false;
    }

    return true;
}

SmartPtr<X3aIspStatistics>
XCam3AAiqContext::get_stats_buffer ()
{
    SmartPtr<X3aIspStatistics> new_stats =
        _stats_pool->get_buffer (_stats_pool).dynamic_cast_ptr<X3aIspStatistics> ();

    XCAM_FAIL_RETURN (
        WARNING,
        new_stats.ptr (),
        NULL,
        "get isp stats buffer failed");

    return new_stats;
}


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

void
XCam3AAiqContext::update_brightness_result(XCamCommonParam *params)
{
    if( params->brightness == _brightness_level)
        return;
    _brightness_level = params->brightness;

    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 = _brightness_level;

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

uint32_t
XCam3AAiqContext::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 SmartPtr<X3aAnalyzeTuner>
get_analyzer (XCam3AContext *context)
{
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    if (!aiq_context)
        return NULL;

    return aiq_context->get_analyzer ();
}

static XCamReturn
xcam_create_context (XCam3AContext **context)
{
    XCAM_ASSERT (context);
    XCam3AAiqContext *aiq_context = new XCam3AAiqContext ();
    *context = ((XCam3AContext*)(aiq_context));
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_destroy_context (XCam3AContext *context)
{
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    delete aiq_context;
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_configure_3a (XCam3AContext *context, uint32_t width, uint32_t height, double framerate)
{
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    struct atomisp_sensor_mode_data sensor_mode_data;

    switch ((int)framerate) {
    case 30:
        sensor_mode_data.coarse_integration_time_min = 1;
        sensor_mode_data.coarse_integration_time_max_margin = 1;
        sensor_mode_data.fine_integration_time_min = 0;
        sensor_mode_data.fine_integration_time_max_margin = 0;
        sensor_mode_data.fine_integration_time_def = 0;
        sensor_mode_data.frame_length_lines = 1125;
        sensor_mode_data.line_length_pck = 1100;
        sensor_mode_data.read_mode = 0;
        sensor_mode_data.vt_pix_clk_freq_mhz = 37125000;
        sensor_mode_data.crop_horizontal_start = 0;
        sensor_mode_data.crop_vertical_start = 0;
        sensor_mode_data.crop_horizontal_end = 1920;
        sensor_mode_data.crop_vertical_end = 1080;
        sensor_mode_data.output_width = 1920;
        sensor_mode_data.output_height = 1080;
        sensor_mode_data.binning_factor_x = 1;
        sensor_mode_data.binning_factor_y = 1;
        break;
    default:
        sensor_mode_data.coarse_integration_time_min = 1;
        sensor_mode_data.coarse_integration_time_max_margin = 1;
        sensor_mode_data.fine_integration_time_min = 0;
        sensor_mode_data.fine_integration_time_max_margin = 0;
        sensor_mode_data.fine_integration_time_def = 0;
        sensor_mode_data.frame_length_lines = 1125;
        sensor_mode_data.line_length_pck = 1320;
        sensor_mode_data.read_mode = 0;
        sensor_mode_data.vt_pix_clk_freq_mhz = 37125000;
        sensor_mode_data.crop_horizontal_start = 0;
        sensor_mode_data.crop_vertical_start = 0;
        sensor_mode_data.crop_horizontal_end = 1920;
        sensor_mode_data.crop_vertical_end = 1080;
        sensor_mode_data.output_width = 1920;
        sensor_mode_data.output_height = 1080;
        sensor_mode_data.binning_factor_x = 1;
        sensor_mode_data.binning_factor_y = 1;
        break;
    }

    XCAM_ASSERT (aiq_context);
    const char *path_cpf = getenv ("AIQ_CPF_PATH");
    XCAM_FAIL_RETURN (
        WARNING,
        aiq_context->setup_analyzer (sensor_mode_data, path_cpf == NULL ? DEFAULT_AIQ_CPF_FILE : path_cpf),
        XCAM_RETURN_ERROR_UNKNOWN,
        "setup aiq 3a analyzer failed");

    SmartPtr<X3aAnalyzeTuner> analyzer = aiq_context->get_analyzer ();

    ret = analyzer->prepare_handlers ();
    XCAM_FAIL_RETURN (
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "analyzer(aiq3alib) prepare handlers failed");

    ret = analyzer->init (width, height, framerate);
    XCAM_FAIL_RETURN (
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "configure aiq 3a failed");

    ret = analyzer->start ();
    XCAM_FAIL_RETURN (
        WARNING,
        ret == XCAM_RETURN_NO_ERROR,
        ret,
        "start aiq 3a failed");

    aiq_context->set_size (width, height);

    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_set_3a_stats (XCam3AContext *context, XCam3AStats *stats, int64_t timestamp)
{
    XCamReturn ret = XCAM_RETURN_NO_ERROR;
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    XCAM_ASSERT (aiq_context);

    SmartPtr<X3aAnalyzeTuner> analyzer = aiq_context->get_analyzer ();
    XCAM_ASSERT (analyzer.ptr ());
    XCAM_ASSERT (stats);

    if (!aiq_context->is_stats_pool_ready ()) {
        // init statistics queue
        XCAM_FAIL_RETURN (
            WARNING,
            aiq_context->setup_stats_pool (stats->info.bit_depth),
            XCAM_RETURN_ERROR_UNKNOWN,
            "aiq configure 3a failed on stats pool setup");
    }

    // Convert stats to atomisp_3a_stats;
    SmartPtr<X3aIspStatistics> isp_stats = aiq_context->get_stats_buffer ();
    if (!isp_stats.ptr ()) {
        XCAM_LOG_WARNING ("get stats bufffer failed or stopped");
        return XCAM_RETURN_ERROR_MEM;
    }

    struct atomisp_3a_statistics *raw_stats = isp_stats->get_isp_stats ();
    XCAM_ASSERT (raw_stats);

    translate_3a_stats (stats, raw_stats);
    isp_stats->set_timestamp (timestamp);

    ret = analyzer->push_3a_stats (isp_stats);
    if (ret != XCAM_RETURN_NO_ERROR) {
        XCAM_LOG_WARNING ("set 3a stats failed");
    }

    return ret;
}

static XCamReturn
xcam_update_common_params (XCam3AContext *context, XCamCommonParam *params)
{
    if (params) {
        SmartPtr<X3aAnalyzeTuner> analyzer = get_analyzer (context);
        XCAM_ASSERT (analyzer.ptr ());

        analyzer->update_common_parameters (*params);
    }
#if 0
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    aiq_context->update_brightness_result(params);
#endif
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_analyze_awb (XCam3AContext *context, XCamAwbParam *params)
{
    if (params) {
        SmartPtr<X3aAnalyzeTuner> analyzer = get_analyzer (context);
        XCAM_ASSERT (analyzer.ptr ());

        analyzer->update_awb_parameters (*params);
    }
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_analyze_ae (XCam3AContext *context, XCamAeParam *params)
{
    if (params) {
        SmartPtr<X3aAnalyzeTuner> analyzer = get_analyzer (context);
        XCAM_ASSERT (analyzer.ptr ());

        analyzer->update_ae_parameters (*params);
    }
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_analyze_af (XCam3AContext *context, XCamAfParam *params)
{
    if (params) {
        SmartPtr<X3aAnalyzeTuner> analyzer = get_analyzer (context);
        XCAM_ASSERT (analyzer.ptr ());

        analyzer->update_af_parameters (*params);
    }
    return XCAM_RETURN_NO_ERROR;
}

static XCamReturn
xcam_combine_analyze_results (XCam3AContext *context, XCam3aResultHead *results[], uint32_t *res_count)
{
    XCam3AAiqContext *aiq_context = AIQ_CONTEXT_CAST (context);
    XCAM_ASSERT (aiq_context);
    X3aResultList aiq_results;
    uint32_t result_count = aiq_context->get_results (aiq_results);

    if (!result_count) {
        *res_count = 0;
        XCAM_LOG_DEBUG ("aiq wrapper combine with no result out");
        return XCAM_RETURN_NO_ERROR;
    }

    // mark as static
    static XCam3aResultHead *res_array[XCAM_3A_MAX_RESULT_COUNT];
    xcam_mem_clear (res_array);
    XCAM_ASSERT (result_count < XCAM_3A_MAX_RESULT_COUNT);

    // result_count may changed
    result_count = translate_3a_results_to_xcam (aiq_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 void
xcam_free_results (XCam3aResultHead *results[], uint32_t res_count)
{
    for (uint32_t i = 0; i < res_count; ++i) {
        if (results[i])
            free_3a_result (results[i]);
    }
}

XCAM_BEGIN_DECLARE

XCam3ADescription xcam_3a_desciption = {
    XCAM_VERSION,
    sizeof (XCam3ADescription),
    xcam_create_context,
    xcam_destroy_context,
    xcam_configure_3a,
    xcam_set_3a_stats,
    xcam_update_common_params,
    xcam_analyze_awb,
    xcam_analyze_ae,
    xcam_analyze_af,
    xcam_combine_analyze_results,
    xcam_free_results
};

XCAM_END_DECLARE