////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2018-2022 Qualcomm Technologies, Inc.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "is_dbg_util.h"
#include "eis_util.h"
#include "zoomwindows.h"

class oisCenterCommand
{
public:
    // Calibration constants
    const bool          _enabled;                    /**< Enable OIS centering mechanism */

    const float         _exposure_time_th;           /**< If (exp_time > exposure_time_th) then OIS centering feature is enabled.
                                                     *   Otherwise OIS will work in regular operational mode.
                                                     *   Units are in seconds.
                                                     */

    const uint32_t      _response_time_us;           /**< response time in number of frames. Since we do not want to change the OIS
                                                     *   module back and forth we have to introduce some delay mechanism.
                                                     */

    // State variables
    uint64_t            _curr_cnt_time;
    cam_is_ois_mode     _curr_mode;

public:
    oisCenterCommand(
        const is_init_data_common* init_common,
        const is_init_data_sensor* init_sensors)
    :
        _enabled((true == init_common->is_ois_enabled) &&
                 (0 != init_sensors->is_chromatix_info.top.ois_frequency) &&
                 (0 != init_sensors->is_chromatix_info.ois.center_command_enable)),
        _exposure_time_th((true == _enabled) ? init_sensors->is_chromatix_info.ois.center_exposure_time_th : NAN),
        _response_time_us((true == _enabled) ? (uint32_t)MILLI_TO_USEC(init_sensors->is_chromatix_info.ois.center_response_time_ms) : 0),
        _curr_cnt_time(0),
        _curr_mode((true == _enabled) ? IS_OIS_MODE_AUTOMATIC : IS_OIS_MODE_INVALID)
    {
        IS_ASSERT(init_sensors->is_chromatix_info.ois.center_response_time_ms >= 0);
    }

    ~oisCenterCommand() {}

    inline void set_first_frame()
    {
        //TODO (etsairi): implement!!!
        return;
    }
};

// todo: shall be moved to a common file
class SensorData
{
public:

    /** OIS module type */
    // TODO (rgaizman): should this be under is interface ?
    typedef enum ois_module_type_e
    {
        LENS_SHIFT                  = 0,
        GENERAL_MODEL               = 1,
        GENERAL_MODEL_2             = 2,
        GIMBAL                      = 3,
        GIMBAL_PRISM                = 4,
        GIMBAL_PRISM_Y_LENS_SHIFT_X = 5,
        RESERVED                    = 6,
    } ois_module_type_t;

    // init parameters:
    const uint32_t          _sensor_mount_angle;     /**< Sensor mount angle (0, 90, 180, 270) */
    const CameraPosition    _camera_position;        /**< Camera position (front, back, etc.) */

    const uint32_t          _input_width;
    const uint32_t          _input_height;

    const uint32_t          _ife_input_width;
    const uint32_t          _ife_input_height;

    const int32_t           _s3d_offset;             /**< Offset between sensor timestamp and gyro timestamps in microseconds */
    const int32_t           _ois_offset;             //TODO (etsairi): document
    const int32_t           _ifovc_offset;           //TODO (etsairi): document

    const ois_module_type_t _ois_module_type;

    double                  _ois_data_conversion_x[ARR_SIZE(eis_ois::data_conversion_x)];
    double                  _ois_data_conversion_y[ARR_SIZE(eis_ois::data_conversion_x)];

    const float             _minimal_total_margin_x; //TODO (etsairi): should be a part of common struct?
    const float             _minimal_total_margin_y; //TODO (etsairi): should be a part of common struct?

    double                  _crop_ratio_height;    // crop ratio of EIS for total margins
    double                  _crop_ratio_width;     // crop ratio of EIS for total margins

    double                  _one_over_virtual_margins_upscale;     // virtual_margins_upscale done inside EIS transform

    // parameters that can change per-frame
    uint32_t                _last_effective_readout_duration;    /**< Last effective readout duration - used for calculation of gyro boundaries */
    oisCenterCommand        _ois_center;

    SensorData(
        const is_init_data_common* init_common,
        const is_init_data_sensor* init_sensors)
    :
        _sensor_mount_angle(init_sensors->sensor_mount_angle),
        _camera_position(init_sensors->camera_position),

        _input_width(init_sensors->is_input_frame_width),
        _input_height(init_sensors->is_input_frame_height),

        _ife_input_width(init_sensors->ife_input_frame_width),       /**< IFE input frame width == Sensor output width  */
        _ife_input_height(init_sensors->ife_input_frame_height),     /**< IFE input frame height == Sensor output height */

        _s3d_offset(init_sensors->is_chromatix_info.timing.s3d_offset),
        _ois_offset((true == init_common->is_ois_enabled) ? init_sensors->is_chromatix_info.timing.ois_offset : 0),
        _ifovc_offset((true == init_common->is_ifovc_enabled) ? (int32_t)init_sensors->is_chromatix_info.timing.ifovc_offset : 0),

        _ois_module_type( static_cast<ois_module_type_t>(init_sensors->is_chromatix_info.ois.module_type) ),

        _minimal_total_margin_x(init_sensors->is_chromatix_info.general.minimal_total_margin),
        _minimal_total_margin_y(init_sensors->is_chromatix_info.general.minimal_total_margin),

        _last_effective_readout_duration(0),  /* Default value for first frame - readout duration suited for read time of one frame */
        _ois_center(init_common, init_sensors)
    {
        uint32_t cropped_size_height;
        uint32_t cropped_size_width;

        eis_internal_get_crop_size(init_sensors->is_input_frame_height,
                                   init_sensors->is_input_frame_width,
                                   init_common->is_output_frame_height,
                                   init_common->is_output_frame_width,
                                   init_sensors->is_chromatix_info.general.minimal_total_margin,
                                   init_sensors->is_chromatix_info.general.minimal_total_margin,
                                   &cropped_size_height,
                                   &cropped_size_width);

        _crop_ratio_height = cropped_size_height / (double)init_sensors->is_input_frame_height;
        _crop_ratio_width  = cropped_size_width  / (double)init_sensors->is_input_frame_width;

        if (init_common->do_virtual_upscale_in_transform)
        {
            _one_over_virtual_margins_upscale = cropped_size_width / (double)init_common->is_output_frame_width;
        }
        else
        {
            _one_over_virtual_margins_upscale = 1;
        }

        for (size_t i = 0; i < ARR_SIZE(_ois_data_conversion_x); i++)
        {
            _ois_data_conversion_x[i] = init_sensors->is_chromatix_info.ois.data_conversion_x[i];
            _ois_data_conversion_y[i] = init_sensors->is_chromatix_info.ois.data_conversion_y[i];
        }
    }

    ~SensorData() {}

    inline void update_crop_ratio(const ZoomWindows::ZoomWindow& output_crop_fov)
    {
        IS_ASSERT(output_crop_fov.bot_y > output_crop_fov.top_y);
        IS_ASSERT(output_crop_fov.right_x > output_crop_fov.left_x);

        _crop_ratio_height = (output_crop_fov.bot_y - output_crop_fov.top_y) / _input_height;
        _crop_ratio_width = (output_crop_fov.right_x - output_crop_fov.left_x) / _input_width;
    }

    inline void set_first_frame()
    {
        //TODO (etsairi): is this correct?
        _last_effective_readout_duration = 0;
        _ois_center.set_first_frame();
    }

    inline void calc_ts_start_and_ts_end(
        const is_input_distortion_correction_t*     eis3_input,
        uint64_t&                                   ts_start,
        uint64_t&                                   ts_mid,
        uint64_t&                                   ts_end)
    {
        const frame_times_t* ft = &eis3_input->frame_times; // [us]
        const float effective_readout_duration = (float)((ft->sensor_rolling_shutter_skew *
                                                            eis3_input->window_regions.eis_pre_crop_vIN.windowHeight) /
                                                            (float)eis3_input->window_regions.eis_pre_crop_vIN.fullHeight);

        const uint32_t half_exposure_time = (uint32_t)(0.5 * ft->exposure_time);       // [us]
        const uint32_t half_readout_time = (uint32_t)(effective_readout_duration / 2); // [us]
                                                                                       // sof shift according to total ife crop (native crop + zoom crop)
        const uint64_t effective_sof = ft->sof + (uint64_t)(ft->sensor_rolling_shutter_skew
                                                                * eis3_input->window_regions.eis_pre_crop_vIN.windowTop / (float)eis3_input->window_regions.eis_pre_crop_vIN.fullHeight);

        // s3d_offset definition is to sync sof we get to actual sof
        const uint64_t mid_of_frame_real_time = (uint64_t)((int64_t)effective_sof -
                                                           (int64_t)half_exposure_time +
                                                           (int64_t)half_readout_time +
                                                           (int64_t)_s3d_offset);

        IS_ASSERT(ft->exposure_time > 0);

        _last_effective_readout_duration = (uint32_t)effective_readout_duration;

        ts_start    = (uint64_t)(mid_of_frame_real_time - 0.5 * effective_readout_duration);
        ts_end      = (uint64_t)(mid_of_frame_real_time + 0.5 * effective_readout_duration);
        //ts_mid      =            mid_of_frame_real_time;
        ts_mid = timestamp_interpolation(ts_start, ts_end, 27, 13);
    }
};

class MultisensorData
{
public:
    SensorData*             _sensors;
    const uint32_t          _num_sensors;
    const uint32_t          _is_output_frame_height;
    const uint32_t          _is_output_frame_width;

    MultisensorData(
        const is_init_data_common*      init_common,
        const is_init_data_sensor*      init_sensors,
        uint32_t                        num_sensors,
        int32_t&                        status)
    :
        _sensors(NULL),
        _num_sensors(num_sensors),
        _is_output_frame_height(init_common->is_output_frame_height),
        _is_output_frame_width(init_common->is_output_frame_width)
    {
        IS_ASSERT(IS_RET_SUCCESS == status);
        IS_ASSERT(num_sensors > 0);

        _sensors = (SensorData*)malloc(sizeof(*_sensors) * _num_sensors);
        if (NULL == _sensors)
        {
            IS_ERR(IS_ERR_OUT_OF_MEMORY, "Out of Memory");
            status = IS_RET_OUT_OF_MEMORY;
            goto Exit;
        }

        for (uint32_t idx = 0; idx < num_sensors; idx++)
        {
            new(&_sensors[idx])  SensorData(init_common, &init_sensors[idx]);
        }

    Exit:
        return;
    }

    ~MultisensorData()
    {
        FREE(_sensors);
    }

    inline void set_first_frame()
    {
        for (uint32_t idx = 0; idx < _num_sensors; idx++)
        {
            _sensors[idx].set_first_frame();
        }
    }

    inline cam_is_ois_mode set_ois_mode(const is_input_distortion_correction_t* eis3_input)
    {
        const frame_times_t* const frame_times  = &eis3_input->frame_times;
        cam_is_ois_mode ret_status              = IS_OIS_MODE_INVALID;

        for (uint32_t idx = 0; idx < _num_sensors; idx++)
        {
            oisCenterCommand* ois_center = &_sensors[idx]._ois_center;

            if (idx != eis3_input->active_sensor_idx)
            {
                if (false != ois_center->_enabled)
                {
                    // update all the rest of the sensors which has valid OIS center command settings, so when they will
                    // be used there will be no delay with the OIS command
                    ois_center->_curr_cnt_time += frame_times->frame_time;
                }
            }
            else
            {
                if (false == ois_center->_enabled) // relevant sensor but OIS center command is disabled
                {
                    ret_status = IS_OIS_MODE_INVALID;         // nothing to do ...
                }
                else
                {
                    const cam_is_ois_mode curr_mode =
                        (frame_times->exposure_time <= ois_center->_exposure_time_th) ? IS_OIS_MODE_CENTER : IS_OIS_MODE_AUTOMATIC;

                    if ((curr_mode != ois_center->_curr_mode) && (ois_center->_curr_cnt_time > ois_center->_response_time_us))
                    {
                        ois_center->_curr_cnt_time = 0;
                        ois_center->_curr_mode = curr_mode;
                    }
                    else
                    {
                        ois_center->_curr_cnt_time += frame_times->frame_time;
                    }

                    ret_status = ois_center->_curr_mode;
                }
            }
        }

        return ret_status;
    }
};

