/*
 * Copyright (C) 2018-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 SyncManager

#include "SyncManager.h"

#include <math.h>
#include <sys/sysinfo.h>

#include "iutils/CameraLog.h"

namespace icamera {
SyncManager* SyncManager::sInstance = nullptr;
Mutex SyncManager::sLock;

#define SEC_TO_MS(sec) ((sec) * (1000))
#define USEC_TO_MS(usec) ((usec) / (1000))

const int max_vc_sync_count = 128;

SyncManager* SyncManager::getInstance() {
    AutoMutex lock(sLock);
    if (sInstance == nullptr) {
        sInstance = new SyncManager();
    }

    return sInstance;
}

void SyncManager::releaseInstance() {
    AutoMutex lock(sLock);

    if (sInstance) {
        delete sInstance;
        sInstance = nullptr;
    }
}

SyncManager::SyncManager() {
    LOG1("@%s", __func__);
    AutoMutex lock(mLock);
    for (int i = 0; i < MAX_CAMERA_NUMBER; i++) {
        for (int j = 0; j < MAX_BUFFER_COUNT; j++) {
            mCameraBufInfo[i][j].sequence = -1;
            CLEAR(mCameraBufInfo[i][j].sof_ts);
        }
    }

    mTotalSyncCamNum = 0;
    for (int i = 0; i < MAX_CAMERA_NUMBER; i++) mVcSyncCount[i] = 0;
}

SyncManager::~SyncManager() {
    LOG1("@%s", __func__);
}

bool SyncManager::isSynced(int cameraId, int64_t sequence) {
    LOG2("@%s", __func__);
    const int TIME_DIFF_MS = 2;
    bool sync = true;
    int index = sequence % MAX_BUFFER_COUNT;

    AutoMutex lock(mLock);
    camera_buf_info bufInfo = mCameraBufInfo[cameraId][index];

    int syncNum = 0;
    bool isSync[MAX_CAMERA_NUMBER];
    long frameSyncedMs[MAX_CAMERA_NUMBER];
    long curFrameMs = USEC_TO_MS(bufInfo.sof_ts.tv_usec) + SEC_TO_MS(bufInfo.sof_ts.tv_sec);

    // first step: To check whether the current frame is synced with others camera channel
    //            if timestamp difference <= 2ms, then think the frame is synced
    for (int i = 0; i < MAX_CAMERA_NUMBER; i++) {
        isSync[i] = false;
        frameSyncedMs[i] = 0;
        if (mCameraBufInfo[i][0].sequence == -1 || i == cameraId) {
            continue;
        }
        for (int j = 0; j < MAX_BUFFER_COUNT; j++) {
            if (mCameraBufInfo[i][j].sequence >= 0) {
                camera_buf_info& temp = mCameraBufInfo[i][j];
                long tempFrameMs = USEC_TO_MS(temp.sof_ts.tv_usec) + SEC_TO_MS(temp.sof_ts.tv_sec);
                if (abs(tempFrameMs - curFrameMs) <= TIME_DIFF_MS) {
                    isSync[syncNum] = true;
                    frameSyncedMs[syncNum] = tempFrameMs;
                    syncNum++;
                    break;
                }
            }
        }
    }
    // second step: if current frame is synced with frames from other cameraID,
    //             to check whether other 3 channel frames synced or not
    if (syncNum >= mTotalSyncCamNum - 1) {
        for (int i = 0; i < mTotalSyncCamNum - 1; i++) {
            if (isSync[i]) {
                if ((i + 1 < mTotalSyncCamNum - 1) &&
                    abs(frameSyncedMs[i] - frameSyncedMs[i + 1]) <= TIME_DIFF_MS) {
                    sync &= true;
                } else if ((i + 1 == mTotalSyncCamNum - 1) &&
                           abs(frameSyncedMs[i] - frameSyncedMs[0]) <= TIME_DIFF_MS) {
                    sync &= true;
                } else {
                    sync &= false;
                }
            }
        }
    } else {
        sync = false;
    }
    LOG2("Id:%d, sof_ts:%ldms, sequence:%ld sync %d", cameraId, curFrameMs, sequence, sync);
    return sync;
}

void SyncManager::updateCameraBufInfo(int cameraId, camera_buf_info* info) {
    LOG2("@%s", __func__);
    int index = info->sequence % MAX_BUFFER_COUNT;
    AutoMutex lock(mLock);
    mCameraBufInfo[cameraId][index] = *info;
}

void SyncManager::updateSyncCamNum() {
    AutoMutex l(mLock);
    CheckAndLogError(mTotalSyncCamNum >= MAX_CAMERA_NUMBER, VOID_VALUE, "Too many cameras");
    mTotalSyncCamNum++;
}

bool SyncManager::vcSynced(int vc) {
    CheckAndLogError(vc >= MAX_CAMERA_NUMBER, false, "vc %d error", vc);

    AutoMutex l(mVcSyncLock);
    int count = mVcSyncCount[vc];
    int minCount = INT_MAX;
    int maxCount = 0;

    for (int i = 0; i < mTotalSyncCamNum; i++) {
        minCount = std::min(minCount, mVcSyncCount[i]);
        maxCount = std::max(maxCount, mVcSyncCount[i]);
    }

    // Check again if status is circling back to 0.
    // Most of time handling code won't be executed because condition is false.
    if (maxCount - minCount > max_vc_sync_count / 2) {
        minCount = max_vc_sync_count;
        maxCount = 0;
        for (int i = 0; i < mTotalSyncCamNum; i++) {
            count =
                (mVcSyncCount[i] + max_vc_sync_count) % (max_vc_sync_count + max_vc_sync_count / 4);
            minCount = std::min(minCount, count);
            maxCount = std::max(maxCount, count);
        }
        count =
            (mVcSyncCount[vc] + max_vc_sync_count) % (max_vc_sync_count + max_vc_sync_count / 4);
    }

    if (count > minCount) {
        LOG2("vc %d ready: false", vc);
        return false;
    }

    return true;
}

void SyncManager::updateVcSyncCount(int vc) {
    CheckAndLogError(vc >= MAX_CAMERA_NUMBER, VOID_VALUE, "vc %d error", vc);
    AutoMutex l(mVcSyncLock);
    mVcSyncCount[vc] = (mVcSyncCount[vc] + 1) % (max_vc_sync_count + 1);
}

void SyncManager::printVcSyncCount(void) {
    AutoMutex l(mVcSyncLock);
    for (int i = 0; i < mTotalSyncCamNum; i++) LOG2("[%d]", mVcSyncCount[i]);
}

}  // namespace icamera
