/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

#define LOG_TAG "QCameraDisplay"

// To remove
#include <cutils/properties.h>

// Camera dependencies
#include "QCamera2HWI.h"
#include "QCameraDisplay.h"

extern "C" {
#include "mm_camera_dbg.h"
}

#define CAMERA_VSYNC_WAIT_MS               33 // Used by vsync thread to wait for vsync timeout.
#define DISPLAY_EVENT_RECEIVER_ARRAY_SIZE  1
#define DISPLAY_DEFAULT_FPS                60

namespace qcamera {

/*===========================================================================
 * FUNCTION   : vsyncEventReceiverCamera
 *
 * DESCRIPTION: Computes average vsync interval. Called by display
 *              event handler for every vsync event.
 *
 * PARAMETERS :
 *   @fd      : file descriptor
 *   @events  : events
 *   @data    : pointer to user data provided during call back registration.
 *
 * RETURN     : always returns 1
 *==========================================================================*/
int QCameraDisplay::vsyncEventReceiverCamera(__unused int fd,
                                             __unused int events, void* data) {
    android::DisplayEventReceiver::Event buffer[DISPLAY_EVENT_RECEIVER_ARRAY_SIZE];
    QCameraDisplay* pQCameraDisplay = (QCameraDisplay *) data;
    ssize_t n;

    while ((n = pQCameraDisplay->mDisplayEventReceiver.getEvents(buffer,
            DISPLAY_EVENT_RECEIVER_ARRAY_SIZE)) > 0) {
        for (int i = 0 ; i < n ; i++) {
            if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
                pQCameraDisplay->computeAverageVsyncInterval(buffer[i].header.timestamp);
            }
        }
    }
    return 1;
}

/*===========================================================================
 * FUNCTION   : vsyncThreadCamera
 *
 * DESCRIPTION: Thread registers a call back function for every vsync event
 *              waits on the looper for the next vsync.
 *
 * PARAMETERS :
 *   @data    : receives vsync_info_t structure.
 *
 * RETURN     : NULL.Just to fullfill the type requirement of thread function.
 *==========================================================================*/
void* QCameraDisplay::vsyncThreadCamera(void * data)
{
    QCameraDisplay* pQCameraDisplay = (QCameraDisplay *) data;
    android::sp<Looper> looper;

    looper = new android::Looper(false);
    status_t status = pQCameraDisplay->mDisplayEventReceiver.initCheck();
    if (status != NO_ERROR) {
        LOGE("Initialization of DisplayEventReceiver failed with status: %d", status);
        return NULL;
    }
    looper->addFd(pQCameraDisplay->mDisplayEventReceiver.getFd(), 0, ALOOPER_EVENT_INPUT,
            QCameraDisplay::vsyncEventReceiverCamera, pQCameraDisplay);
    pQCameraDisplay->mDisplayEventReceiver.setVsyncRate(1);
    while(pQCameraDisplay->mThreadExit == 0)
    {
        looper->pollOnce(CAMERA_VSYNC_WAIT_MS);
    }
    return NULL;
}

/*===========================================================================
 * FUNCTION   : ~QCameraDisplay
 *
 * DESCRIPTION: constructor of QCameraDisplay
 *
 * PARAMETERS : none
 *
 * RETURN     : none
 *==========================================================================*/
QCameraDisplay::QCameraDisplay()
    : mVsyncTimeStamp(0),
      mAvgVsyncInterval(0),
      mOldTimeStamp(0),
      mVsyncHistoryIndex(0),
      mAdditionalVsyncOffsetForWiggle(0),
      mThreadExit(0),
      mNum_vsync_from_vfe_isr_to_presentation_timestamp(0),
      mSet_timestamp_num_ms_prior_to_vsync(0),
      mVfe_and_mdp_freq_wiggle_filter_max_ms(0),
      mVfe_and_mdp_freq_wiggle_filter_min_ms(0)
{
    int rc = NO_ERROR;

    memset(&mVsyncIntervalHistory, 0, sizeof(mVsyncIntervalHistory));
    rc = pthread_create(&mVsyncThreadCameraHandle, NULL, vsyncThreadCamera, (void *)this);
    if (rc == NO_ERROR) {
        char    value[PROPERTY_VALUE_MAX];
        nsecs_t default_vsync_interval;
        pthread_setname_np(mVsyncThreadCameraHandle, "CAM_Vsync_Thread");
        // Read a list of properties used for tuning
        property_get("persist.camera.disp.num_vsync", value, "4");
        mNum_vsync_from_vfe_isr_to_presentation_timestamp = atoi(value);
        property_get("persist.camera.disp.ms_to_vsync", value, "2");
        mSet_timestamp_num_ms_prior_to_vsync = atoi(value);
        property_get("persist.camera.disp.filter_max", value, "2");
        mVfe_and_mdp_freq_wiggle_filter_max_ms = atoi(value);
        property_get("persist.camera.disp.filter_min", value, "4");
        mVfe_and_mdp_freq_wiggle_filter_min_ms = atoi(value);
        property_get("persist.camera.disp.fps", value, "60");
        if (atoi(value) > 0) {
            default_vsync_interval= s2ns(1) / atoi(value);
        } else {
            default_vsync_interval= s2ns(1) / DISPLAY_DEFAULT_FPS;
        }
        for (int i=0; i < CAMERA_NUM_VSYNC_INTERVAL_HISTORY; i++) {
            mVsyncIntervalHistory[i] = default_vsync_interval;
        }
        LOGD("display jitter num_vsync_from_vfe_isr_to_presentation_timestamp %u \
                set_timestamp_num_ms_prior_to_vsync %u",
                mNum_vsync_from_vfe_isr_to_presentation_timestamp,
                mSet_timestamp_num_ms_prior_to_vsync);
        LOGD("display jitter vfe_and_mdp_freq_wiggle_filter_max_ms %u \
                vfe_and_mdp_freq_wiggle_filter_min_ms %u",
                mVfe_and_mdp_freq_wiggle_filter_max_ms,
                mVfe_and_mdp_freq_wiggle_filter_min_ms);
    } else {
        mVsyncThreadCameraHandle = 0;
    }
}

/*===========================================================================
 * FUNCTION   : ~QCameraDisplay
 *
 * DESCRIPTION: destructor of QCameraDisplay
 *
 * PARAMETERS : none
 *
 * RETURN     : none
 *==========================================================================*/
QCameraDisplay::~QCameraDisplay()
{
    mThreadExit = 1;
    if (mVsyncThreadCameraHandle != 0) {
        pthread_join(mVsyncThreadCameraHandle, NULL);
    }
}

/*===========================================================================
 * FUNCTION   : computeAverageVsyncInterval
 *
 * DESCRIPTION: Computes average vsync interval using current and previously
 *              stored vsync data.
 *
 * PARAMETERS : current vsync time stamp
 *
 * RETURN     : none
 *==========================================================================*/
void QCameraDisplay::computeAverageVsyncInterval(nsecs_t currentVsyncTimeStamp)
{
    nsecs_t sum;
    nsecs_t vsyncMaxOutlier;
    nsecs_t vsyncMinOutlier;

    mVsyncTimeStamp = currentVsyncTimeStamp;
    if (mOldTimeStamp) {
        // Compute average vsync interval using current and previously stored vsync data.
        // Leave the max and min vsync interval from history in computing the average.
        mVsyncIntervalHistory[mVsyncHistoryIndex] = currentVsyncTimeStamp - mOldTimeStamp;
        mVsyncHistoryIndex++;
        mVsyncHistoryIndex = mVsyncHistoryIndex % CAMERA_NUM_VSYNC_INTERVAL_HISTORY;
        sum = mVsyncIntervalHistory[0];
        vsyncMaxOutlier = mVsyncIntervalHistory[0];
        vsyncMinOutlier = mVsyncIntervalHistory[0];
        for (int j=1; j<CAMERA_NUM_VSYNC_INTERVAL_HISTORY; j++) {
            sum += mVsyncIntervalHistory[j];
            if (vsyncMaxOutlier < mVsyncIntervalHistory[j]) {
                vsyncMaxOutlier = mVsyncIntervalHistory[j];
            } else if (vsyncMinOutlier > mVsyncIntervalHistory[j]) {
                vsyncMinOutlier = mVsyncIntervalHistory[j];
            }
        }
        sum = sum - vsyncMaxOutlier - vsyncMinOutlier;
        mAvgVsyncInterval = sum / (CAMERA_NUM_VSYNC_INTERVAL_HISTORY - 2);
    }
    mOldTimeStamp = currentVsyncTimeStamp;
}

/*===========================================================================
 * FUNCTION   : computePresentationTimeStamp
 *
 * DESCRIPTION: Computes presentation time stamp using vsync interval
 *              and last vsync time stamp and few other tunable variables
 *              to place the time stamp at the expected future vsync
 *
 * PARAMETERS : current frame time stamp set by VFE when buffer copy done.
 *
 * RETURN     : time stamp in future or 0 in case of failure.
 *==========================================================================*/
nsecs_t QCameraDisplay::computePresentationTimeStamp(nsecs_t frameTimeStamp)
{
    nsecs_t moveToNextVsync;
    nsecs_t keepInCurrentVsync;
    nsecs_t timeDifference        = 0;
    nsecs_t presentationTimeStamp = 0;
    int     expectedVsyncOffset   = 0;
    int     vsyncOffset;

    if ( (mAvgVsyncInterval != 0) && (mVsyncTimeStamp != 0) ) {
        // Compute presentation time stamp in future as per the following formula
        // future time stamp = vfe time stamp +  N *  average vsync interval
        // Adjust the time stamp so that it is placed few milliseconds before
        // the expected vsync.
        // Adjust the time stamp for the period where vsync time stamp and VFE
        // timstamp cross over due difference in fps.
        presentationTimeStamp = frameTimeStamp +
                (mNum_vsync_from_vfe_isr_to_presentation_timestamp * mAvgVsyncInterval);
        if (presentationTimeStamp > mVsyncTimeStamp) {
            timeDifference      = presentationTimeStamp - mVsyncTimeStamp;
            moveToNextVsync     = mAvgVsyncInterval - mVfe_and_mdp_freq_wiggle_filter_min_ms;
            keepInCurrentVsync  = mAvgVsyncInterval - mVfe_and_mdp_freq_wiggle_filter_max_ms;
            vsyncOffset         = timeDifference % mAvgVsyncInterval;
            expectedVsyncOffset = mAvgVsyncInterval -
                    mSet_timestamp_num_ms_prior_to_vsync - vsyncOffset;
            if (vsyncOffset > moveToNextVsync) {
                mAdditionalVsyncOffsetForWiggle = mAvgVsyncInterval;
            } else if (vsyncOffset < keepInCurrentVsync) {
                mAdditionalVsyncOffsetForWiggle = 0;
            }
            LOGD("vsyncTimeStamp: %llu presentationTimeStamp: %llu expectedVsyncOffset: %d \
                    timeDifference: %llu vsyncffset: %d avgvsync: %llu \
                    additionalvsyncOffsetForWiggle: %llu",
                    mVsyncTimeStamp, presentationTimeStamp, expectedVsyncOffset,
                    timeDifference, vsyncOffset, mAvgVsyncInterval,
                    mAdditionalVsyncOffsetForWiggle);
        }
        presentationTimeStamp = presentationTimeStamp + expectedVsyncOffset +
                mAdditionalVsyncOffsetForWiggle;
    }
    return presentationTimeStamp;
}

}; // namespace qcamera