/*
 * Copyright (C) 2020-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 ShareRefer

#include "ShareReferBufferPool.h"

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

using std::map;
using std::unique_ptr;
using std::vector;

namespace icamera {

#define CONSUMER_BUFFER_NUM 2

int64_t ShareReferBufferPool::constructReferId(int32_t streamId, int32_t pgId, int32_t portId) {
    return (((int64_t)streamId << 32) + ((int64_t)pgId << 16) + portId);
}

ShareReferBufferPool::~ShareReferBufferPool() {
    AutoMutex l(mPairLock);
    while (!mUserPairs.empty()) {
        UserPair* pair = mUserPairs.back();
        mUserPairs.pop_back();
        delete pair;
    }
}

int32_t ShareReferBufferPool::setReferPair(const std::string& producerPgName, int64_t producerId,
                                           const std::string& consumerPgName, int64_t consumerId) {
    CheckAndLogError(producerId == consumerId, BAD_VALUE, "same pair for producer/consumer %lx",
                     producerId);

    UserPair* pair = new UserPair;
    pair->producerPgName = producerPgName;
    pair->producerId = producerId;
    pair->consumerPgName = consumerPgName;
    pair->consumerId = consumerId;
    pair->busy = false;
    pair->active = true;
    LOG1("%s: %s:%lx -> %s:%lx", __func__, producerPgName.c_str(), producerId,
         consumerPgName.c_str(), consumerId);
    AutoMutex l(mPairLock);
    mUserPairs.push_back(pair);
    return OK;
}

int32_t ShareReferBufferPool::clearReferPair(int64_t id) {
    AutoMutex l(mPairLock);
    for (auto it = mUserPairs.begin(); it != mUserPairs.end(); it++) {
        UserPair* pair = *it;
        if (pair->producerId != id && pair->consumerId != id) continue;

        pair->bufferLock.lock();
        if (pair->busy) {
            pair->bufferLock.unlock();
            LOGE("Can't clear pair %lx because Q is busy!", id);
            return UNKNOWN_ERROR;
        }

        mUserPairs.erase(it);
        pair->bufferLock.unlock();
        delete pair;
        return OK;
    }

    return BAD_VALUE;
}

int32_t ShareReferBufferPool::getMinBufferNum(int64_t id) {
    AutoMutex l(mPairLock);
    for (auto pair : mUserPairs) {
        if (pair->producerId == id)
            return PlatformData::getMaxRawDataNum(mCameraId);
        else if (pair->consumerId == id)
            return CONSUMER_BUFFER_NUM;
    }
    return 0;
}

int32_t ShareReferBufferPool::registerReferBuffers(int64_t id, CIPR::Buffer* buffer) {
    CheckAndLogError(!buffer, BAD_VALUE, "%s, buffer is nullptr", __func__);

    AutoMutex l(mPairLock);
    UserPair* pair = findUserPair(id);
    CheckAndLogError(!pair, UNKNOWN_ERROR, "Can't find id %lx", id);

    ReferBuffer referBuf = {-1, buffer};
    AutoMutex m(pair->bufferLock);
    std::vector<ReferBuffer>& bufV =
        (id == pair->producerId) ? pair->mProducerBuffers : pair->mConsumerBuffers;
    bufV.push_back(referBuf);

    if (pair->active && !pair->mProducerBuffers.empty() && !pair->mConsumerBuffers.empty()) {
        int32_t srcSize = 0, dstSize = 0;
        pair->mProducerBuffers.front().buffer->getMemorySize(&srcSize);
        pair->mConsumerBuffers.front().buffer->getMemorySize(&dstSize);
        if (srcSize != dstSize) {
            LOG2("%s, disable share buffer pool due to different size. src: %d, dst: %d", __func__,
                 srcSize, dstSize);
            pair->active = false;
        }
    }

    return OK;
}

/**
 * example for buffer operations:
 * 1. sequence in buffer Q (len is 4)
 *     front->back: S6,   S7,   S8,   S9
 * 2. acquireBuffer before running for frame 10:
 *     front->back: (out),S7,   S8,   S9(in)     (pop s6 as new output)
 * 3. releaseBuffer after running:
 *     front->back:       S7,   S8,   S9,   S10   (push_back S10, new output)
 */
int32_t ShareReferBufferPool::acquireBuffer(int64_t id, CIPR::Buffer** referIn,
                                            CIPR::Buffer** referOut, int64_t outSequence) {
    CheckAndLogError(!referIn || !referOut, BAD_VALUE, "nullptr input for refer buf pair");

    int64_t inSequence = outSequence - 1;
    UserPair* pair = nullptr;
    {
        AutoMutex l(mPairLock);
        pair = findUserPair(id);
        CheckAndLogError(!pair, UNKNOWN_ERROR, "Can't find id %lx", id);

        AutoMutex m(pair->bufferLock);
        std::vector<ReferBuffer>& bufV =
            (id == pair->producerId) ? pair->mProducerBuffers : pair->mConsumerBuffers;
        CheckAndLogError(bufV.empty(), BAD_VALUE, "no refer buffer for id %lx", id);

        *referOut = bufV.front().buffer;
        bufV.erase(bufV.begin());  // pop front (the oldest one) as new output
        *referIn = bufV.back().buffer;
        if (bufV.back().sequence == inSequence || inSequence < 0) {
            // Return if found required buffers or it is the 1st frame.
            LOG2("%lx acquire in seq %ld, got %ld, out seq %ld", id, inSequence,
                 bufV.back().sequence, outSequence);
            return OK;
        } else if (id == pair->producerId) {
            // Find required refer in buffer for producer.
            // In general, it happens in reprocessing case that producer want to run old frame
            for (auto& item : bufV) {
                if (item.sequence == inSequence) {
                    *referIn = item.buffer;
                    LOG2("%lx acquire in seq %ld for reprocessing", id, inSequence);
                    return OK;
                }
            }
            LOG1("%lx has no refer in seq %ld", id, inSequence);
            return UNKNOWN_ERROR;
        } else if (!pair->active) {
            return OK;
        }
        pair->busy = true;
    }

    LOG1("consumer %s try to get in seq %ld from %s", pair->consumerPgName.c_str(), inSequence,
         pair->producerPgName.c_str());
    CIPR::Buffer* srcBuf = nullptr;
    int ret = NOT_ENOUGH_DATA;
    int waitFrames = 3;  // wait 3 frames
    while (waitFrames-- && ret == NOT_ENOUGH_DATA) {
        ConditionLock lock(pair->bufferLock);
        ret = findReferBuffer(&pair->mProducerBuffers, inSequence, &srcBuf);

        if (ret == NOT_ENOUGH_DATA) {
            pair->bufferSignal.waitRelative(lock, kWaitDuration * SLOWLY_MULTIPLIER);
        }
    }

    if (ret == OK && srcBuf) {
        void* srcPtr = nullptr;
        int32_t srcSize = 0;
        srcBuf->getMemoryCpuPtr(&srcPtr);
        srcBuf->getMemorySize(&srcSize);
        void* dstPtr = nullptr;
        int32_t dstSize = 0;
        (*referIn)->getMemoryCpuPtr(&dstPtr);
        (*referIn)->getMemorySize(&dstSize);
        if (srcPtr && dstPtr) {
            MEMCPY_S(dstPtr, dstSize, srcPtr, srcSize);
        }
        LOG1("%s acquire in seq %ld (copy from %s), out seq %ld", pair->consumerPgName.c_str(),
             inSequence, pair->producerPgName.c_str(), outSequence);
    }

    AutoMutex m(pair->bufferLock);
    pair->busy = false;
    return ret;
}

int32_t ShareReferBufferPool::releaseBuffer(int64_t id, CIPR::Buffer* referIn,
                                            CIPR::Buffer* referOut, int64_t outSequence) {
    CheckAndLogError(!referIn || !referOut, BAD_VALUE, "nullptr for refer buf pair for release");

    AutoMutex l(mPairLock);
    UserPair* pair = findUserPair(id);
    CheckAndLogError(!pair, UNKNOWN_ERROR, "Can't find id %lx", id);

    AutoMutex m(pair->bufferLock);
    std::vector<ReferBuffer>& bufV =
        (id == pair->producerId) ? pair->mProducerBuffers : pair->mConsumerBuffers;
    if (outSequence < bufV.back().sequence) {
        // Drop old data (in reprocessing case)
        ReferBuffer referBuf = {-1, referOut};
        bufV.insert(bufV.begin(), referBuf);
    } else {
        ReferBuffer referBuf = {outSequence, referOut};
        bufV.push_back(referBuf);
    }
    pair->bufferSignal.signal();

    return OK;
}

ShareReferBufferPool::UserPair* ShareReferBufferPool::findUserPair(int64_t id) {
    for (auto pair : mUserPairs) {
        if (pair->consumerId == id || pair->producerId == id) {
            return pair;
        }
    }
    return nullptr;
}

int ShareReferBufferPool::findReferBuffer(std::vector<ReferBuffer>* bufV, int64_t sequence,
                                          CIPR::Buffer** out) {
    CheckAndLogError(!bufV, BAD_VALUE, "nullptr buffers");
    CheckAndLogError(!out, BAD_VALUE, "nullptr out buffer");

    if (bufV->empty() || bufV->back().sequence < sequence) return NOT_ENOUGH_DATA;

    for (auto item = bufV->rbegin(); item != bufV->rend(); item++) {
        if (item->sequence <= sequence) {
            *out = item->buffer;
            LOG2("%s: find seq %ld for required seq %ld", __func__, item->sequence, sequence);
            return OK;
        }
    }

    LOGE("No refer buffer with required seq %ld", sequence);
    return UNKNOWN_ERROR;
}

}  // namespace icamera
