/*
 * 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 AiqSetting

#include <algorithm>

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

#include "AiqSetting.h"
#include "PlatformData.h"
#include "ParameterHelper.h"

namespace icamera {

AiqSetting::AiqSetting(int cameraId) :
    mCameraId(cameraId) {
}

AiqSetting::~AiqSetting() {
}

int AiqSetting::init(void) {
    AutoWMutex wlock(mParamLock);

    mAiqParam.reset();

    camera_info_t info = {};
    PlatformData::getCameraInfo(mCameraId, info);
    info.capability->getAeCompensationRange(mAiqParam.evRange);
    info.capability->getAeCompensationStep(mAiqParam.evStep);
    info.capability->getLensInfoShadingMapSize(mAiqParam.lensShadingMapSize);
    info.capability->getLensMinFocusDistance(mAiqParam.minFocusDistance);

    return OK;
}

int AiqSetting::deinit(void) {
    return OK;
}

int AiqSetting::configure(const stream_config_t *streamList) {
    AutoWMutex wlock(mParamLock);

    camera_resolution_t resolution = {streamList->streams[0].width, streamList->streams[0].height};
    for (int i = 0; i < streamList->num_streams; i++) {
        if (streamList->streams[i].usage == CAMERA_STREAM_PREVIEW) {
            resolution = {streamList->streams[i].width, streamList->streams[i].height};
            break;
        }
    }

    // Use pixel array as resolution to calculate region if it is set.
    camera_coordinate_system_t activePixelArray = PlatformData::getActivePixelArray(mCameraId);
    if ((activePixelArray.right > activePixelArray.left) &&
        (activePixelArray.bottom > activePixelArray.top)) {
        resolution.width = activePixelArray.right - activePixelArray.left;
        resolution.height = activePixelArray.bottom - activePixelArray.top;
    }

    updateFrameUsage(streamList);

    mAiqParam.tuningMode = TUNING_MODE_MAX;
    mAiqParam.resolution = resolution;

    mTuningModes.clear();
    std::vector<ConfigMode> configModes;
    PlatformData::getConfigModesByOperationMode(mCameraId, streamList->operation_mode, configModes);
    for (auto cfg : configModes) {
        TuningMode tuningMode;
        if (PlatformData::getTuningModeByConfigMode(mCameraId, cfg, tuningMode) == OK) {
            mTuningModes.push_back(tuningMode);
        }
    }
    if (!mTuningModes.empty()) {
        mAiqParam.tuningMode = mTuningModes[0];
    }
    LOG1("%s, tuningMode %d, configMode %x, fame usage %d, res %dx%d", __func__,
         mAiqParam.tuningMode, configModes[0], mAiqParam.frameUsage,
         mAiqParam.resolution.width, mAiqParam.resolution.height);

    return OK;
}

void AiqSetting::updateFrameUsage(const stream_config_t *streamList) {
    bool preview = false, still = false, video = false;
    for (int i = 0; i < streamList->num_streams; i++) {
        if (streamList->streams[i].usage == CAMERA_STREAM_VIDEO_CAPTURE) {
            video = true;
        } else if (streamList->streams[i].usage == CAMERA_STREAM_STILL_CAPTURE) {
            still = true;
        } else if (streamList->streams[i].usage == CAMERA_STREAM_PREVIEW
                   || streamList->streams[i].usage == CAMERA_STREAM_APP) {
            preview = true;
        }
    }

    mAiqParam.frameUsage = FRAME_USAGE_PREVIEW;
    if (video) {
        mAiqParam.frameUsage = FRAME_USAGE_VIDEO;
    } else if (preview && still) {
        mAiqParam.frameUsage = FRAME_USAGE_CONTINUOUS;
    } else if (still) {
        mAiqParam.frameUsage = FRAME_USAGE_STILL;
    }
}

int AiqSetting::setParameters(const Parameters& params) {
    AutoWMutex wlock(mParamLock);

    // Update AE related parameters
    params.getAeMode(mAiqParam.aeMode);
    params.getAeLock(mAiqParam.aeForceLock);
    params.getExposureTime(mAiqParam.manualExpTimeUs);
    params.getSensitivityGain(mAiqParam.manualGain);
    params.getSensitivityIso(mAiqParam.manualIso);
    params.getBlcAreaMode(mAiqParam.blcAreaMode);
    params.getAeRegions(mAiqParam.aeRegions);
    params.getAeConvergeSpeedMode(mAiqParam.aeConvergeSpeedMode);
    params.getAeConvergeSpeed(mAiqParam.aeConvergeSpeed);
    params.getRun3ACadence(mAiqParam.run3ACadence);
    if (mAiqParam.run3ACadence < 1) {
        LOGW("Invalid 3A cadence %d, use default 1.", mAiqParam.run3ACadence);
        mAiqParam.run3ACadence = 1;
    }
    params.getCallbackRgbs(&mAiqParam.callbackRgbs);
    params.getCallbackTmCurve(&mAiqParam.callbackTmCurve);

    int ev = 0;
    params.getAeCompensation(ev);
    if (mAiqParam.evStep.denominator == 0) {
        mAiqParam.evShift = 0.0;
    } else {
        ev = CLIP(ev, mAiqParam.evRange.max, mAiqParam.evRange.min);
        mAiqParam.evShift = static_cast<float>(ev) *
            mAiqParam.evStep.numerator / mAiqParam.evStep.denominator;
    }

    params.getFrameRate(mAiqParam.fps);
    params.getFpsRange(mAiqParam.aeFpsRange);
    params.getAntiBandingMode(mAiqParam.antibandingMode);
    // Update AWB related parameters
    params.getAwbMode(mAiqParam.awbMode);
    params.getAwbLock(mAiqParam.awbForceLock);
    params.getAwbCctRange(mAiqParam.cctRange);
    params.getAwbGains(mAiqParam.awbManualGain);
    params.getAwbWhitePoint(mAiqParam.whitePoint);
    params.getAwbGainShift(mAiqParam.awbGainShift);
    params.getColorTransform(mAiqParam.manualColorMatrix);
    params.getColorGains(mAiqParam.manualColorGains);
    params.getAwbConvergeSpeedMode(mAiqParam.awbConvergeSpeedMode);
    params.getAwbConvergeSpeed(mAiqParam.awbConvergeSpeed);

    // Update AF related parameters
    params.getAfMode(mAiqParam.afMode);
    params.getAfRegions(mAiqParam.afRegions);
    params.getAfTrigger(mAiqParam.afTrigger);

    params.getWeightGridMode(mAiqParam.weightGridMode);
    params.getSceneMode(mAiqParam.sceneMode);

    params.getAeDistributionPriority(mAiqParam.aeDistributionPriority);

    params.getWdrLevel(mAiqParam.ltmStrength);

    unsigned int length = sizeof(mAiqParam.customAicParam.data);
    if (params.getCustomAicParam(mAiqParam.customAicParam.data, &length) == OK) {
        mAiqParam.customAicParam.length = length;
    }

    params.getYuvColorRangeMode(mAiqParam.yuvColorRangeMode);

    params.getExposureTimeRange(mAiqParam.exposureTimeRange);
    params.getSensitivityGainRange(mAiqParam.sensitivityGainRange);

    params.getVideoStabilizationMode(mAiqParam.videoStabilizationMode);
    params.getLdcMode(mAiqParam.ldcMode);
    params.getRscMode(mAiqParam.rscMode);
    params.getFlipMode(mAiqParam.flipMode);
    params.getDigitalZoomRatio(mAiqParam.digitalZoomRatio);
    params.getTestPatternMode(mAiqParam.testPatternMode);

    int ret = params.getMakernoteMode(mAiqParam.makernoteMode);
    if (ret == NAME_NOT_FOUND) mAiqParam.makernoteMode = MAKERNOTE_MODE_OFF;

    const CameraMetadata& meta = ParameterHelper::getMetadata(params);
    uint32_t tag = CAMERA_LENS_INFO_MINIMUM_FOCUS_DISTANCE;
    icamera_metadata_ro_entry entry = meta.find(tag);
    if (entry.count == 1) {
        mAiqParam.minFocusDistance = entry.data.f[0];
    }

    params.getFocusDistance(mAiqParam.focusDistance);
    params.getShadingMode(mAiqParam.shadingMode);
    params.getLensShadingMapMode(mAiqParam.lensShadingMapMode);

    params.getTonemapMode(mAiqParam.tonemapMode);
    params.getTonemapPresetCurve(mAiqParam.tonemapPresetCurve);
    params.getTonemapGamma(mAiqParam.tonemapGamma);
    if (mAiqParam.tonemapMode == TONEMAP_MODE_CONTRAST_CURVE) {
        camera_tonemap_curves_t curves;
        if (params.getTonemapCurves(curves) == OK) {
            CheckWarningNoReturn(curves.rSize > DEFAULT_TONEMAP_CURVE_POINT_NUM,
                                 "user r curve size is too big %d", curves.rSize);
            CheckWarningNoReturn(curves.gSize > DEFAULT_TONEMAP_CURVE_POINT_NUM,
                                 "user g curve size is too big %d", curves.gSize);
            CheckWarningNoReturn(curves.bSize > DEFAULT_TONEMAP_CURVE_POINT_NUM,
                                 "user v curve size is too big %d", curves.bSize);
            int curveSize = sizeof(float) * DEFAULT_TONEMAP_CURVE_POINT_NUM;
            MEMCPY_S(&mAiqParam.tonemapCurveMem[0], curveSize,
                     curves.rCurve, sizeof(float) * curves.rSize);
            MEMCPY_S(&mAiqParam.tonemapCurveMem[DEFAULT_TONEMAP_CURVE_POINT_NUM], curveSize,
                     curves.gCurve, sizeof(float) * curves.gSize);
            MEMCPY_S(&mAiqParam.tonemapCurveMem[DEFAULT_TONEMAP_CURVE_POINT_NUM * 2], curveSize,
                     curves.bCurve, sizeof(float) * curves.bSize);
            mAiqParam.tonemapCurves.rSize = std::min(DEFAULT_TONEMAP_CURVE_POINT_NUM, curves.rSize);
            mAiqParam.tonemapCurves.gSize = std::min(DEFAULT_TONEMAP_CURVE_POINT_NUM, curves.gSize);
            mAiqParam.tonemapCurves.bSize = std::min(DEFAULT_TONEMAP_CURVE_POINT_NUM, curves.bSize);
        }
    } else {
        mAiqParam.tonemapCurves.rSize = 0;
        mAiqParam.tonemapCurves.gSize = 0;
        mAiqParam.tonemapCurves.bSize = 0;
    }

    uint8_t captureIntent = 0;
    if (params.getCaptureIntent(captureIntent) == OK) {
        switch (captureIntent) {
        case CAMERA_CONTROL_CAPTUREINTENT_STILL_CAPTURE:
            mAiqParam.frameUsage = FRAME_USAGE_STILL;
            break;
        case CAMERA_CONTROL_CAPTUREINTENT_VIDEO_RECORD:
        case CAMERA_CONTROL_CAPTUREINTENT_VIDEO_SNAPSHOT:
            mAiqParam.frameUsage = FRAME_USAGE_VIDEO;
            break;
        case CAMERA_CONTROL_CAPTUREINTENT_PREVIEW:
            mAiqParam.frameUsage = FRAME_USAGE_PREVIEW;
            break;
        default:
            mAiqParam.frameUsage = FRAME_USAGE_CONTINUOUS;
            break;
        }
    }

    params.getPowerMode(mAiqParam.powerMode);
    params.getTotalExposureTarget(mAiqParam.totalExposureTarget);

    mAiqParam.dump();

    return OK;
}

int AiqSetting::getAiqParameter(aiq_parameter_t &param) {
    AutoRMutex rlock(mParamLock);

    param = mAiqParam;
    return OK;
}

void aiq_parameter_t::reset() {
    frameUsage = FRAME_USAGE_VIDEO;
    aeMode = AE_MODE_AUTO;
    aeForceLock = false;
    awbMode = AWB_MODE_AUTO;
    awbForceLock = false;
    afMode = AF_MODE_AUTO;
    afTrigger = AF_TRIGGER_IDLE;
    sceneMode = SCENE_MODE_AUTO;
    manualExpTimeUs = -1;
    manualGain = -1;
    manualIso = 0;
    evShift = 0;
    evStep = {1, 3};
    evRange = {-6, 6};
    fps = 0;
    aeFpsRange = { 0.0, 0.0 };
    antibandingMode = ANTIBANDING_MODE_AUTO;
    cctRange = { 0, 0 };
    whitePoint = { 0, 0 };
    awbManualGain = { 0, 0, 0 };
    awbGainShift = { 0, 0, 0 };
    CLEAR(manualColorMatrix);
    CLEAR(manualColorGains);
    aeRegions.clear();
    blcAreaMode = BLC_AREA_MODE_OFF;
    aeConvergeSpeedMode = CONVERGE_SPEED_MODE_AIQ;
    awbConvergeSpeedMode = CONVERGE_SPEED_MODE_AIQ;
    aeConvergeSpeed = CONVERGE_NORMAL;
    awbConvergeSpeed = CONVERGE_NORMAL;
    run3ACadence = 1;
    ltmStrength = 0;
    weightGridMode = WEIGHT_GRID_AUTO;
    aeDistributionPriority = DISTRIBUTION_AUTO;
    CLEAR(customAicParam);
    yuvColorRangeMode = CAMERA_FULL_MODE_YUV_COLOR_RANGE;
    exposureTimeRange.min = -1;
    exposureTimeRange.max = -1;
    sensitivityGainRange.min = -1;
    sensitivityGainRange.max = -1;
    videoStabilizationMode = VIDEO_STABILIZATION_MODE_OFF;
    tuningMode = TUNING_MODE_MAX;
    ldcMode = LDC_MODE_OFF;
    rscMode = RSC_MODE_OFF;
    flipMode = FLIP_MODE_NONE;
    digitalZoomRatio = 1.0f;

    lensPosition = 0;
    lensMovementStartTimestamp = 0;
    makernoteMode = MAKERNOTE_MODE_OFF;
    minFocusDistance = 0.0f;
    focusDistance = 0.0f;
    shadingMode = SHADING_MODE_FAST;
    lensShadingMapMode = LENS_SHADING_MAP_MODE_OFF;
    lensShadingMapSize = {0, 0};
    testPatternMode = TEST_PATTERN_OFF;

    tonemapMode = TONEMAP_MODE_FAST;
    tonemapPresetCurve = TONEMAP_PRESET_CURVE_SRGB;
    tonemapGamma = 0.0f;
    tonemapCurves.rSize = 0;
    tonemapCurves.gSize = 0;
    tonemapCurves.bSize = 0;
    tonemapCurves.rCurve = &tonemapCurveMem[0];
    tonemapCurves.gCurve = &tonemapCurveMem[DEFAULT_TONEMAP_CURVE_POINT_NUM];
    tonemapCurves.bCurve = &tonemapCurveMem[DEFAULT_TONEMAP_CURVE_POINT_NUM * 2];
    callbackRgbs = false;
    callbackTmCurve = false;

    powerMode = CAMERA_HIGH_QUALITY;
    totalExposureTarget = 0;

    CLEAR(resolution);
}

void aiq_parameter_t::dump() {
    if (!Log::isLogTagEnabled(GET_FILE_SHIFT(AiqSetting), CAMERA_DEBUG_LOG_LEVEL3)) return;

    LOG3("Application parameters:");
    LOG3("3A mode: ae %d, awb %d, af %d, scene %d", aeMode, awbMode, afMode, sceneMode);
    LOG3("lock: ae %d, awb %d, af trigger:%d", aeForceLock, awbForceLock, afTrigger);
    LOG3("converge speed mode: ae %d, awb %d", aeConvergeSpeedMode, awbConvergeSpeedMode);
    LOG3("converge speed: ae %d, awb %d", aeConvergeSpeed, awbConvergeSpeed);

    LOG3("EV:%f, range (%f-%f), step %d/%d", evShift, evRange.min, evRange.max,
         evStep.numerator, evStep.denominator);
    LOG3("manualExpTimeUs:%ld, time range (%f-%f)", manualExpTimeUs,
         exposureTimeRange.min, exposureTimeRange.max);
    LOG3("manualGain %f, manualIso %d, gain range (%f-%f)", manualGain, manualIso,
         sensitivityGainRange.min, sensitivityGainRange.max);
    LOG3("FPS %f, range (%f-%f)", fps, aeFpsRange.min, aeFpsRange.max);
    for (auto &region : aeRegions) {
        LOG3("ae region (%d, %d, %d, %d, %d)",
             region.left, region.top, region.right, region.bottom, region.weight);
    }
    LOG3("Antibanding mode:%d", antibandingMode);
    LOG3("AE Distribution Priority:%d", aeDistributionPriority);

    LOG3("cctRange:(%f-%f)", cctRange.min, cctRange.max);
    LOG3("manual awb: white point:(%d,%d)", whitePoint.x, whitePoint.y);
    LOG3("manual awb gain:(%d,%d,%d), gain shift:(%d,%d,%d)",
         awbManualGain.r_gain, awbManualGain.g_gain, awbManualGain.b_gain,
         awbGainShift.r_gain, awbGainShift.g_gain, awbGainShift.b_gain);
    for (int i = 0; i < 3; i++) {
        LOG3("manual color matrix: [%.3f %.3f %.3f]",
             manualColorMatrix.color_transform[i][0],
             manualColorMatrix.color_transform[i][1],
             manualColorMatrix.color_transform[i][2]);
    }
    LOG3("manual color gains in rggb:(%.3f,%.3f,%.3f,%.3f)",
         manualColorGains.color_gains_rggb[0], manualColorGains.color_gains_rggb[1],
         manualColorGains.color_gains_rggb[2], manualColorGains.color_gains_rggb[3]);

    for (auto &region : afRegions) {
        LOG3("af region (%d, %d, %d, %d, %d)",
             region.left, region.top, region.right, region.bottom, region.weight);
    }
    LOG3("manual focus distance: %f, min focus distance: %f", focusDistance, minFocusDistance);
    LOG3("Focus position %d, start timestamp %llu", lensPosition, lensMovementStartTimestamp);

    LOG3("digitalZoomRatio %f", digitalZoomRatio);

    LOG3("custom AIC parameter length:%u", customAicParam.length);
    if (customAicParam.length > 0) {
        LOG3("custom AIC parameter data:%s", customAicParam.data);
    }
    if (tuningMode != TUNING_MODE_MAX) {
        LOG3("camera mode:%d", tuningMode);
    }
    LOG3("blc area mode:%d", blcAreaMode);
    LOG3("ltm strength:(%u)", ltmStrength);
    LOG3("weight grid mode:%d", weightGridMode);
    LOG3("Yuv Color Range Mode:%d", yuvColorRangeMode);
    LOG3("DVS mode %d", videoStabilizationMode);

    LOG3("makernoteMode %d", makernoteMode);
    LOG3("shadingMode %d, lensShadingMapMode %d, size %dx%d", shadingMode,
         lensShadingMapMode, lensShadingMapSize.x, lensShadingMapSize.y);

    LOG3("ldcMode %d, rscMode %d, flipMode %d", ldcMode, ldcMode, flipMode);

    LOG3("run3ACadence %d", run3ACadence);
    LOG3("tonemap mode %d, preset curve %d, gamma %f, curve points %d",
          tonemapMode, tonemapPresetCurve, tonemapGamma, tonemapCurves.gSize);
    LOG3("testPatternMode %d", testPatternMode);
    LOG3("power mode %d", powerMode);
    LOG3("totalExposureTarget %ld", totalExposureTarget);

    LOG3("callback RGBS stats %s", callbackRgbs ? "true" : "false");
    LOG3("callback Tonemap curve: %s", callbackTmCurve ? "true" : "false");
}

} /* namespace icamera */
