/*
 * Copyright (C) 2015-2021 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.
 */

#define LOG_TAG "AiqCore"

#include <math.h>

#include <memory>
#include <string>

#include "PlatformData.h"
#include "AiqUtils.h"
#include "Parameters.h"

#include "iutils/CameraLog.h"
#include "iutils/Errors.h"
#include "iutils/Utils.h"

#include "AiqCore.h"

namespace icamera {
AiqCore::AiqCore(int cameraId) :
    mCameraId(cameraId),
    mTimestamp(0),
    mSensorPixelClock(0.0),
    mAeForceLock(false),
    mAwbForceLock(false),
    mAfForceLock(false),
    mAeRunTime(0),
    mAwbRunTime(0),
    mAiqRunTime(0),
    mAiqState(AIQ_NOT_INIT),
    mHyperFocalDistance(0.0f),
    mTuningMode(TUNING_MODE_MAX),
    mShadingMode(SHADING_MODE_FAST),
    mLensShadingMapMode(LENS_SHADING_MAP_MODE_OFF),
    mLscGridRGGBLen(0),
    mLastEvShift(0.0f),
    mTonemapMode(TONEMAP_MODE_FAST),
    mTonemapMaxCurvePoints(0) {
    LOG3A("@%s", __func__);

    mIntel3AParameter = std::unique_ptr<Intel3AParameter>(new Intel3AParameter(cameraId));

    CLEAR(mFrameParams);
    CLEAR(mLastAeResult),
    CLEAR(mLastAwbResult),
    CLEAR(mLastAfResult),

    CLEAR(mGbceParams);
    CLEAR(mPaParams);
    CLEAR(mSaParams);
    CLEAR(mPaColorGains);

    CLEAR(mResizeLscGridR);
    CLEAR(mResizeLscGridGr);
    CLEAR(mResizeLscGridGb);
    CLEAR(mResizeLscGridB);

    CLEAR(mLensShadingMapSize);
    CLEAR(mLscGridRGGB);

    // init LscOffGrid to 1.0f
    std::fill(std::begin(mLscOffGrid), std::end(mLscOffGrid), 1.0f);

    mAiqParams = std::unique_ptr<cca::cca_aiq_params>(new cca::cca_aiq_params);
    mAiqResults = std::unique_ptr<cca::cca_aiq_results>(new cca::cca_aiq_results);

    camera_info_t info = {};
    PlatformData::getCameraInfo(mCameraId, info);
    info.capability->getTonemapMaxCurvePoints(mTonemapMaxCurvePoints);
    if (mTonemapMaxCurvePoints > 0 && mTonemapMaxCurvePoints < MIN_TONEMAP_POINTS) {
        LOGW("%s: wrong tonemap points", __func__);
        mTonemapMaxCurvePoints = 0;
    }
}

AiqCore::~AiqCore() {
    LOG3A("@%s", __func__);
}

int AiqCore::initAiqPlusParams() {
    LOG3A("@%s", __func__);

    CLEAR(mGbceParams);
    CLEAR(mPaParams);
    CLEAR(mPaColorGains);
    CLEAR(mSaParams);

    /* use convergence time from tunings */
    mSaParams.manual_convergence_time = -1.0;

    return OK;
}

int AiqCore::init() {
    LOG3A("@%s", __func__);

    initAiqPlusParams();

#ifndef ENABLE_SANDBOXING
    ia_env env = {&Log::ccaPrintDebug, &Log::ccaPrintError, &Log::ccaPrintInfo};
    ia_log_init(&env);
#endif

    mAiqState = AIQ_INIT;

    int ret = mIntel3AParameter->init();
    CheckError(ret != OK, ret, "@%s, Init 3a parameter failed ret: %d", __func__, ret);

    CLEAR(mLastAeResult),
    CLEAR(mLastAwbResult),
    CLEAR(mLastAfResult),
    mAeRunTime = 0;
    mAwbRunTime = 0;
    mAiqRunTime = 0;

    return OK;
}

int AiqCore::deinit() {
    LOG3A("@%s", __func__);

#ifndef ENABLE_SANDBOXING
    ia_log_deinit();
#endif

    mAiqState = AIQ_NOT_INIT;

    return OK;
}

int AiqCore::configure(const std::vector<ConfigMode>& configModes) {
    LOG3A("@%s", __func__);

    if (mAiqState == AIQ_CONFIGURED) {
        return OK;
    }

    mAiqState = AIQ_CONFIGURED;

    return OK;
}

int AiqCore::setSensorInfo(const ia_aiq_frame_params &frameParams,
                           const ia_aiq_exposure_sensor_descriptor &descriptor) {
    LOG3A("@%s", __func__);

    mFrameParams = frameParams;
    mSensorPixelClock = descriptor.pixel_clock_freq_mhz;
    mIntel3AParameter->setSensorInfo(descriptor);

    return OK;
}

/**
 *  Hyperfocal distance is the closest distance at which a lens can be focused
 *  while keeping objects at infinity acceptably sharp. When the lens is focused
 *  at this distance, all objects at distances from half of the hyperfocal
 *  distance out to infinity will be acceptably sharp.
 *
 *  The equation used for this is:
 *        f*f
 *  H = -------
 *        N*c
 *
 *  where:
 *   f is the focal length
 *   N is the f-number (f/D for aperture diameter D)
 *   c is the Circle Of Confusion (COC)
 *
 *   the COC is calculated as the pixel width of 2 pixels
 *
 *  The hyperfocal distance in mm. It is ensured it will never be 0 to
 *  avoid division by 0. If any of the required CMC items is missing
 *  it will return the default value 5m
 */
int AiqCore::calculateHyperfocalDistance(TuningMode mode) {
    LOG3A("@%s, tuning mode: %d", __func__, mode);

    IntelCca* intelCca = getIntelCca(mode);
    CheckError(!intelCca, BAD_VALUE, "%s, cca is nullptr, mode:%d", __func__, mode);

    cca::cca_cmc cmc;
    ia_err ret = intelCca->getCMC(&cmc);
    CheckError(ret != ia_err_none, BAD_VALUE, "@%s get cmc data failed", __func__);

    mHyperFocalDistance = AiqUtils::calculateHyperfocalDistance(cmc);

    return OK;
}

/**
 *
 * Calculate the Depth of field (DOF) for a given AF Result.
 *
 * The Formulas to calculate the near and afar DOF are:
 *          H * s
 * Dn = ---------------
 *         H + (s-f)
 *
 *          H * s
 * Df =  ------------
 *         H - (s-f)
 *
 * Where:
 * H is the hyperfocal distance (that we get from CPF) (it cannot be 0)
 * s is the distance to focused object (current focus distance)
 * f is the focal length
 *
 * \param[in] afResults with current focus distance in mm
 * \param[out] dof info: DOF for near and far limit in diopters
 */
int AiqCore::calculateDepthOfField(const cca::cca_af_results &afResults,
                                   camera_range_t *focusRange) {
    LOG3A("@%s, focusRange:%p", __func__, focusRange);
    CheckError(!focusRange, BAD_VALUE, "@%s, Bad input values", __func__);

    const float DEFAULT_DOF = 5000.0f;
    focusRange->min = 1000.0f / DEFAULT_DOF;
    focusRange->max = 1000.0f / DEFAULT_DOF;

    float focusDistance = 1.0f * afResults.current_focus_distance;
    if (focusDistance == 0.0f) {
        // Not reporting error since this may be normal in fixed focus sensors
        return OK;
    }

    IntelCca* intelCca = getIntelCca(mTuningMode);
    CheckError(!intelCca, BAD_VALUE, "%s, cca is nullptr, mTuningMode:%d", __func__, mTuningMode);

    cca::cca_cmc cmc;
    int ret = intelCca->getCMC(&cmc);
    CheckError(ret != OK, BAD_VALUE, "@%s get cmc data failed", __func__);

    // focal length is stored in CMC in hundreds of millimeters
    float focalLengthMillis = static_cast<float>(cmc.optics.effect_focal_length) / 100;

    float num = mHyperFocalDistance * focusDistance;
    float denom = (mHyperFocalDistance + focusDistance - focalLengthMillis);
    if (denom != 0.0f) {
        focusRange->min = num / denom;
    }

    denom = (mHyperFocalDistance - focusDistance + focalLengthMillis);
    if (denom != 0.0f) {
        focusRange->max = num / denom;
    }

    focusRange->min = 1000.0f / focusRange->min;
    focusRange->max = 1000.0f / focusRange->max;

    return OK;
}

int AiqCore::updateParameter(const aiq_parameter_t &param) {
    LOG3A("@%s", __func__);

    if (mTuningMode != param.tuningMode) {
        int ret = calculateHyperfocalDistance(param.tuningMode);
        CheckError(ret != OK, ret, "%s calculateHyperfocalDistance fails", __func__);
        mTuningMode = param.tuningMode;
    }
    mShadingMode = param.shadingMode;
    mLensShadingMapMode = param.lensShadingMapMode;
    mLensShadingMapSize = param.lensShadingMapSize;

    mGbceParams.ev_shift = param.evShift;

    // In still frame use force update by setting convergence time to 0.
    // in other cases use tunings.
    mSaParams.manual_convergence_time = (param.frameUsage == FRAME_USAGE_STILL) ? 0.0 : -1.0;

    mIntel3AParameter->updateParameter(param);
    mAeForceLock = param.aeForceLock;
    mAwbForceLock = param.awbForceLock;
    mAfForceLock = mIntel3AParameter->mAfForceLock;

    mTonemapMode = param.tonemapMode;

    return OK;
}

int AiqCore::setStatsParams(const cca::cca_stats_params &statsParams) {
    LOG3A("@%s, frame_id:%lld, frame_timestamp:%lld, mTuningMode:%d", __func__,
          statsParams.frame_id, statsParams.frame_timestamp, mTuningMode);
    int ret = OK;

    IntelCca* intelCca = getIntelCca(mTuningMode);
    CheckError(!intelCca, UNKNOWN_ERROR, "%s, intelCca is nullptr, mode:%d", __func__, mTuningMode);
    {
        PERF_CAMERA_ATRACE_PARAM1_IMAGING("intelCca->setStatsParams", 1);
        ia_err iaErr = intelCca->setStatsParams(statsParams);
        ret = AiqUtils::convertError(iaErr);
        CheckError(ret != OK, ret, "setStatsParams fails, ret: %d", ret);
    }

    mTimestamp = statsParams.frame_timestamp;

    return ret;
}

int AiqCore::runAe(long requestId, AiqResult* aiqResult) {
    LOG3A("@%s, aiqResult:%p, requestId:%d", __func__, aiqResult, requestId);
    CheckError(!aiqResult, BAD_VALUE, "@%s, aiqResult is nullptr", __func__);

    // run AE
    return runAEC(requestId, &aiqResult->mAeResults);
}

int AiqCore::runAiq(long requestId, AiqResult *aiqResult) {
    LOG3A("@%s, aiqResult:%p, requestId:%d, mTonemapMode:%d", __func__,
          aiqResult, requestId, mTonemapMode);
    CheckError(!aiqResult, BAD_VALUE, "@%s, aiqResult is nullptr", __func__);

    int aaaRunType = IMAGING_ALGO_AWB | IMAGING_ALGO_GBCE | IMAGING_ALGO_PA;
    if (PlatformData::getLensHwType(mCameraId) == LENS_VCM_HW) {
        aaaRunType |= IMAGING_ALGO_AF;
    }
    if (mShadingMode != SHADING_MODE_OFF) {
        aaaRunType |= IMAGING_ALGO_SA;
    }

    // get the IntelCca instance
    IntelCca* intelCca = getIntelCca(mTuningMode);
    CheckError(!intelCca, UNKNOWN_ERROR, "%s, intelCca is null, mode:%d", __func__, mTuningMode);

    mAiqParams->bitmap = 0;

    // fill the parameter
    if (aaaRunType & IMAGING_ALGO_AWB) {
        if (!mAwbForceLock && (mAwbRunTime % mIntel3AParameter->mAwbPerTicks == 0)) {
            mIntel3AParameter->mAwbParams.is_bypass = false;
            mAiqParams->awb_input = mIntel3AParameter->mAwbParams;
        } else {
            mIntel3AParameter->mAwbParams.is_bypass = true;
        }
        LOG3A("AWB bypass %d", mIntel3AParameter->mAwbParams.is_bypass);
        mAiqParams->bitmap |= cca::CCA_MODULE_AWB;
    }

    if (aaaRunType & IMAGING_ALGO_AF && !mAfForceLock) {
        mAiqParams->bitmap |= cca::CCA_MODULE_AF;
        mAiqParams->af_input = mIntel3AParameter->mAfParams;
    }

    if (aaaRunType & IMAGING_ALGO_GBCE) {
        mGbceParams.gbce_on = (mTonemapMaxCurvePoints > 0) ? true : false;

        //run gbce with bypass level if AE lock and ev shift isn't changed
        if (mAeForceLock && mGbceParams.ev_shift == mLastEvShift) {
            mGbceParams.is_bypass = true;
        } else {
            mGbceParams.is_bypass = false;
        }
        mAiqParams->bitmap |= cca::CCA_MODULE_GBCE;
        mAiqParams->gbce_input = mGbceParams;
    }

    if (aaaRunType & IMAGING_ALGO_PA) {
        mPaParams.color_gains = {};
        mAiqParams->bitmap |= cca::CCA_MODULE_PA;
        mAiqParams->pa_input = mPaParams;
    }

    if (aaaRunType & IMAGING_ALGO_SA) {
        mAiqParams->bitmap |= cca::CCA_MODULE_SA;
        mSaParams.lsc_on = mLensShadingMapMode == LENS_SHADING_MAP_MODE_ON ? true : false;
        mAiqParams->sa_input = mSaParams;
    }
    LOG3A("@%s, params->bitmap:%d, mAiqRunTime:%lld", __func__, mAiqParams->bitmap, mAiqRunTime);

    // runAIQ for awb/af/gbce/pa/sa
    int ret = OK;
    {
        PERF_CAMERA_ATRACE_PARAM1_IMAGING("intelAiq->runAIQ", 1);

        ia_err iaErr = intelCca->runAIQ(requestId, *mAiqParams.get(), mAiqResults.get());
        mAiqRunTime++;
        ret = AiqUtils::convertError(iaErr);
        CheckError(ret != OK, ret, "@%s, runAIQ, ret: %d", __func__, ret);
    }

    // handle awb result
    if (aaaRunType & IMAGING_ALGO_AWB) {
        cca::cca_awb_results *newAwbResults = &mLastAwbResult;

        if (!mAwbForceLock && (mAwbRunTime % mIntel3AParameter->mAwbPerTicks == 0)) {
            *newAwbResults = mAiqResults->awb_output;
        }

        if (!PlatformData::isIsysEnabled(mCameraId)) {
            // Fix AWB gain to 1 for none-ISYS cases
            newAwbResults->accurate_r_per_g = 1.0;
            newAwbResults->accurate_b_per_g = 1.0;
        }

        mIntel3AParameter->updateAwbResult(newAwbResults);
        aiqResult->mAwbResults = *newAwbResults;
        AiqUtils::dumpAwbResults(aiqResult->mAwbResults);
        ++mAwbRunTime;
    }

    // handle af result
    if (aaaRunType & IMAGING_ALGO_AF) {
        focusDistanceResult(&mAiqResults->af_output, &aiqResult->mAfDistanceDiopters,
                            &aiqResult->mFocusRange);
        aiqResult->mAfResults = mAiqResults->af_output;
        AiqUtils::dumpAfResults(aiqResult->mAfResults);

        mIntel3AParameter->fillAfTriggerResult(&aiqResult->mAfResults);
    }

    // handle gbce result
    if (aaaRunType & IMAGING_ALGO_GBCE) {
        aiqResult->mGbceResults = mAiqResults->gbce_output;
        AiqUtils::dumpGbceResults(aiqResult->mGbceResults);
    }

    // handle pa result
    if (aaaRunType & IMAGING_ALGO_PA) {
        mIntel3AParameter->updatePaResult(&mAiqResults->pa_output);
        aiqResult->mPaResults = mAiqResults->pa_output;
        AiqUtils::dumpPaResults(aiqResult->mPaResults);
    }

    // handle sa result
    if (aaaRunType & IMAGING_ALGO_SA) {
        AiqUtils::dumpSaResults(mAiqResults->sa_output);
        ret |= processSAResults(&mAiqResults->sa_output, aiqResult->mLensShadingMap);
    }
    CheckError(ret != OK, ret, "run3A failed, ret: %d", ret);

    uint16_t pixelInLine = aiqResult->mAeResults.exposures[0].sensor_exposure->line_length_pixels;
    uint16_t lineInFrame = aiqResult->mAeResults.exposures[0].sensor_exposure->frame_length_lines;
    aiqResult->mFrameDuration = pixelInLine * lineInFrame / mSensorPixelClock;
    aiqResult->mRollingShutter = pixelInLine * (mFrameParams.cropped_image_height - 1)
                                 / mSensorPixelClock;

    mLastEvShift = mIntel3AParameter->mAeParams.ev_shift;
    aiqResult->mTimestamp = mTimestamp;

    return OK;
}

int AiqCore::runAEC(long requestId, cca::cca_ae_results* aeResults) {
    LOG3A("@%s, aeResults:%p", __func__, aeResults);
    CheckError(!aeResults, BAD_VALUE, "@%s, aeResults is nullptr", __func__);
    PERF_CAMERA_ATRACE();

    int ret = OK;
    cca::cca_ae_results *newAeResults = &mLastAeResult;

    // Run AEC with setting bypass mode to false
    if (mAeRunTime == 0 || (mIntel3AParameter->mAeParams.ev_shift != mLastEvShift)
        || (!mAeForceLock && (mAeRunTime % mIntel3AParameter->mAePerTicks == 0))) {
        mIntel3AParameter->mAeParams.is_bypass = false;
    } else {
        mIntel3AParameter->mAeParams.is_bypass = true;
    }

    LOG3A("AEC frame_use: %d, bypass: %d", mIntel3AParameter->mAeParams.frame_use,
          mIntel3AParameter->mAeParams.is_bypass);
    IntelCca* intelCca = getIntelCca(mTuningMode);
    CheckError(!intelCca, UNKNOWN_ERROR, "%s, intelCca is null, m:%d", __func__, mTuningMode);
    {
        PERF_CAMERA_ATRACE_PARAM1_IMAGING("intelCca->runAEC", 1);
        ia_err iaErr = intelCca->runAEC(requestId, mIntel3AParameter->mAeParams, newAeResults);
        ret = AiqUtils::convertError(iaErr);
        CheckError(ret != OK, ret, "Error running AE, ret: %d", ret);
    }

    mIntel3AParameter->updateAeResult(newAeResults);
    *aeResults = *newAeResults;
    AiqUtils::dumpAeResults(*aeResults);
    ++mAeRunTime;

    return ret;
}

void AiqCore::focusDistanceResult(const cca::cca_af_results *afResults,
                                  float *afDistanceDiopters,
                                  camera_range_t *focusRange) {
    LOG3A("@%s, afResults:%p, afDistanceDiopters:%p, focusRange:%p", __func__,
          afResults, afDistanceDiopters, focusRange);
    CheckError(!afResults || !afDistanceDiopters || !focusRange, VOID_VALUE,
               "@%s, Bad input values", __func__);

    *afDistanceDiopters = 1.2f;
    if (mIntel3AParameter->mAfParams.focus_mode == ia_aiq_af_operation_mode_infinity) {
        // infinity mode is special: we need to report 0.0f (1/inf = 0)
        *afDistanceDiopters = 0.0f;
    } else if (afResults->current_focus_distance != 0) {
        // In AIQ, 'current_focus_distance' is in millimeters
        // For rounding multiply by extra 100.
        // This allows the diopters to have 2 decimal values
        *afDistanceDiopters = 100 * 1000 * (1.0 / afResults->current_focus_distance);
        *afDistanceDiopters = ceil(*afDistanceDiopters);
        // Divide by 100 for final result.
        *afDistanceDiopters = *afDistanceDiopters / 100;
    }
    LOG3A("%s, Zero focus distance in AF result, reporting %f", __func__, *afDistanceDiopters);

    calculateDepthOfField(*afResults, focusRange);
    LOG3A("%s, focus distance with diopters: %f %f", __func__, focusRange->min, focusRange->max);
}

int AiqCore::checkColorOrder(cmc_bayer_order bayerOrder, ColorOrder *colorOrder) {
    LOG3A("@%s, bayerOrder = %d, colorOrder:%p", __func__, bayerOrder, colorOrder);
    CheckError(!colorOrder, BAD_VALUE, "@%s, colorOrder is nullptr", __func__);

    int ret = OK;
    switch (bayerOrder) {
    case cmc_bayer_order_grbg:
    /* use gr r b gb constitute 2X2 array
     * gr    r
     * b     gb
     * The four channel use x y coordinate to indicate
     * gr(0, 0) r(1, 0) b(0, 1) gb(1, 1)
    */
        colorOrder->r[0] = 1;
        colorOrder->r[1] = 0;
        colorOrder->b[0] = 0;
        colorOrder->b[1] = 1;
        colorOrder->gr[0] = 0;
        colorOrder->gr[1] = 0;
        colorOrder->gb[0] = 1;
        colorOrder->gb[1] = 1;
        break;
    case cmc_bayer_order_rggb:
        colorOrder->r[0] = 0;
        colorOrder->r[1] = 0;
        colorOrder->b[0] = 1;
        colorOrder->b[1] = 1;
        colorOrder->gr[0] = 1;
        colorOrder->gr[1] = 0;
        colorOrder->gb[0] = 0;
        colorOrder->gb[1] = 1;
        break;
    case cmc_bayer_order_bggr:
        colorOrder->r[0] = 1;
        colorOrder->r[1] = 1;
        colorOrder->b[0] = 0;
        colorOrder->b[1] = 0;
        colorOrder->gr[0] = 0;
        colorOrder->gr[1] = 1;
        colorOrder->gb[0] = 1;
        colorOrder->gb[1] = 0;
        break;
    case cmc_bayer_order_gbrg:
        colorOrder->r[0] = 0;
        colorOrder->r[1] = 1;
        colorOrder->b[0] = 1;
        colorOrder->b[1] = 0;
        colorOrder->gr[0] = 1;
        colorOrder->gr[1] = 1;
        colorOrder->gb[0] = 0;
        colorOrder->gb[1] = 0;
        break;
    default:
        ret = BAD_VALUE;
        break;
    }
    return ret;
}

int AiqCore::reFormatLensShadingMap(const LSCGrid &inputLscGrid, float *dstLscGridRGGB) {
    LOG3A("@%s, width %d, height %d", __func__, inputLscGrid.width, inputLscGrid.height);

    CheckError(inputLscGrid.isBad() || !dstLscGridRGGB, BAD_VALUE,
               "@%s, Bad input values for lens shading map reformatting", __func__);

    // Metadata spec request order [R, Geven, Godd, B]
    // the lensShading from ISP is 4 width * height block,
    // for ia_aiq_bayer_order_grbg, the four block is G, R, B, G
    size_t size = inputLscGrid.height * inputLscGrid.width;
    for (size_t i = 0; i < size; i++) {
        *dstLscGridRGGB++ = inputLscGrid.gridR[i];
        *dstLscGridRGGB++ = inputLscGrid.gridGr[i];
        *dstLscGridRGGB++ = inputLscGrid.gridGb[i];
        *dstLscGridRGGB++ = inputLscGrid.gridB[i];
    }

    return OK;
}

int AiqCore::storeLensShadingMap(const LSCGrid &inputLscGrid,
                                 const LSCGrid &resizeLscGrid, float *dstLscGridRGGB) {
    LOG3A("@%s", __func__);
    CheckError(inputLscGrid.isBad() || resizeLscGrid.isBad() || !dstLscGridRGGB, BAD_VALUE,
               "@%s, Bad input values for lens shading map storing", __func__);

    int destWidth = resizeLscGrid.width;
    int destHeight = resizeLscGrid.height;
    int width = inputLscGrid.width;
    int height = inputLscGrid.height;

    if (width != destWidth || height != destHeight) {
        // requests lensShadingMapSize must be smaller than 64*64
        // and it is a constant size.
        // Our lensShadingMapSize is dynamic based on the resolution, so need
        // to do resize for 4 channels separately

        AiqUtils::resize2dArray(inputLscGrid.gridR,  width, height,
                      resizeLscGrid.gridR,  destWidth, destHeight);
        AiqUtils::resize2dArray(inputLscGrid.gridGr,  width, height,
                      resizeLscGrid.gridGr,  destWidth, destHeight);
        AiqUtils::resize2dArray(inputLscGrid.gridGb,  width, height,
                      resizeLscGrid.gridGb,  destWidth, destHeight);
        AiqUtils::resize2dArray(inputLscGrid.gridB,  width, height,
                      resizeLscGrid.gridB,  destWidth, destHeight);

        LOG3A("resize lens shading map from [%d,%d] to [%d,%d]",
              width, height, destWidth, destHeight);
    } else {
        size_t size = destWidth * destHeight * sizeof(resizeLscGrid.gridR[0]);
        STDCOPY((int8_t *) resizeLscGrid.gridR,  (int8_t *) inputLscGrid.gridR,  size);
        STDCOPY((int8_t *) resizeLscGrid.gridGr, (int8_t *) inputLscGrid.gridGr, size);
        STDCOPY((int8_t *) resizeLscGrid.gridGb, (int8_t *) inputLscGrid.gridGb, size);
        STDCOPY((int8_t *) resizeLscGrid.gridB,  (int8_t *) inputLscGrid.gridB,  size);
    }

    return reFormatLensShadingMap(resizeLscGrid, dstLscGridRGGB);
}

int AiqCore::processSAResults(cca::cca_sa_results *saResults, float *lensShadingMap) {
    LOG3A("@%s, saResults:%p, lensShadingMap:%p", __func__, saResults, lensShadingMap);
    CheckError(!saResults || !lensShadingMap, BAD_VALUE, "@%s, Bad input values", __func__);

    if (mLensShadingMapMode == LENS_SHADING_MAP_MODE_OFF) return OK;

    if (saResults->lsc_update) {
        ColorOrder co_ind = {};
        int ret = checkColorOrder(saResults->color_order, &co_ind);
        CheckError(ret != OK, BAD_VALUE, "Failed to checkColorOrder, ret: %d", ret);

        LSCGrid inputGrid;
        inputGrid.gridB = saResults->lsc_grid[co_ind.b[0]][co_ind.b[1]];
        inputGrid.gridR = saResults->lsc_grid[co_ind.r[0]][co_ind.r[1]];
        inputGrid.gridGr = saResults->lsc_grid[co_ind.gr[0]][co_ind.gr[1]];
        inputGrid.gridGb = saResults->lsc_grid[co_ind.gb[0]][co_ind.gb[1]];
        inputGrid.width = saResults->width;
        inputGrid.height = saResults->height;

        LSCGrid resizeGrid;
        resizeGrid.gridB = mResizeLscGridB;
        resizeGrid.gridR = mResizeLscGridR;
        resizeGrid.gridGr = mResizeLscGridGr;
        resizeGrid.gridGb = mResizeLscGridGb;
        resizeGrid.width = mLensShadingMapSize.x;
        resizeGrid.height = mLensShadingMapSize.y;

        storeLensShadingMap(inputGrid, resizeGrid, mLscGridRGGB);

        // resizeGrid's width and height should be equal to inputGrid's width and height
        mLscGridRGGBLen = resizeGrid.width * resizeGrid.height * 4;
        size_t errCount = 0;
        for (size_t i = 0; i < mLscGridRGGBLen; i++) {
            if (mLscGridRGGB[i] < 1.0f) {
                mLscGridRGGB[i] = 1.0f;
                errCount++;
            }
        }
        if (errCount) {
            LOGW("Error - SA produced too small values (%d/%d)!", errCount, mLscGridRGGBLen);
        }
    }

    float *lsm = (mShadingMode != SHADING_MODE_OFF) ? mLscGridRGGB : mLscOffGrid;
    for (size_t i = 0; i < mLscGridRGGBLen; i++) {
        lensShadingMap[i] = lsm[i];
    }

    return OK;
}

IntelCca* AiqCore::getIntelCca(TuningMode tuningMode) {
    CheckError(tuningMode >= TUNING_MODE_MAX, nullptr,
               "@%s, wrong tuningMode:%d", __func__, tuningMode);

    return IntelCca::getInstance(mCameraId, tuningMode);
}

} /* namespace icamera */
