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

#include "BufferQueue.h"

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

namespace icamera {

BufferProducer::BufferProducer(int memType) : mMemType(memType) {
    LOG1("@%s BufferProducer %p created mMemType: %d", __func__, this, mMemType);
}

BufferQueue::BufferQueue()
        : mBufferProducer(nullptr),
          mProcessThread(nullptr),
          mThreadRunning(false) {
    LOG1("@%s BufferQueue %p created", __func__, this);
}

BufferQueue::~BufferQueue() {}

int BufferQueue::queueInputBuffer(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) {
    // If it's not in mInputQueue, then it's not for this processor.
    if (mInputQueue.find(port) == mInputQueue.end()) {
        return OK;
    }

    LOG2("%s CameraBuffer %p for port:%d", __func__, camBuffer.get(), port);

    CameraBufQ& input = mInputQueue[port];
    bool needSignal = input.empty();
    input.push(camBuffer);
    if (needSignal) {
        mFrameAvailableSignal.signal();
    }

    return OK;
}

int BufferQueue::onFrameAvailable(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) {
    AutoMutex l(mBufferQueueLock);

    return queueInputBuffer(port, camBuffer);
}

void BufferQueue::setBufferProducer(BufferProducer* producer) {
    LOG1("%s producer %p", __func__, producer);

    AutoMutex l(mBufferQueueLock);
    mBufferProducer = producer;

    if (producer == nullptr) return;

    mBufferProducer->addFrameAvailableListener(this);
}

void BufferQueue::addFrameAvailableListener(BufferConsumer* listener) {
    LOG1("%s listener %p", __func__, listener);
    AutoMutex l(mBufferQueueLock);
    bool isAlreadyAdded = false;
    for (auto& consumer : mBufferConsumerList) {
        if (consumer == listener) {
            isAlreadyAdded = true;
            break;
        }
    }

    // If the listener has been already added, then we don't register it again.
    if (isAlreadyAdded) {
        return;
    }
    mBufferConsumerList.push_back(listener);
}

void BufferQueue::removeFrameAvailableListener(BufferConsumer* listener) {
    LOG1("%s listener %p", __func__, listener);
    AutoMutex l(mBufferQueueLock);

    for (auto it = mBufferConsumerList.begin(); it != mBufferConsumerList.end(); ++it) {
        if ((*it) == listener) {
            mBufferConsumerList.erase(it);
            break;
        }
    }
}

int BufferQueue::qbuf(Port port, const std::shared_ptr<CameraBuffer>& camBuffer) {
    LOG2("%s CameraBuffer %p for port:%d", __func__, camBuffer.get(), port);

    // Enqueue buffer to internal pool
    AutoMutex l(mBufferQueueLock);
    if (camBuffer != nullptr && camBuffer->getStreamType() == CAMERA_STREAM_INPUT) {
        return queueInputBuffer(port, camBuffer);
    }

    CheckAndLogError(mOutputQueue.find(port) == mOutputQueue.end(), BAD_VALUE,
                     "Not supported port:%d", port);

    CameraBufQ& output = mOutputQueue[port];
    bool needSignal = output.empty();
    output.push(camBuffer);
    if (needSignal) {
        mOutputAvailableSignal.signal();
    }

    return OK;
}

void BufferQueue::clearBufferQueues() {
    AutoMutex l(mBufferQueueLock);

    mInputQueue.clear();
    for (const auto& input : mInputFrameInfo) {
        mInputQueue[input.first] = CameraBufQ();
    }

    mOutputQueue.clear();
    for (const auto& output : mOutputFrameInfo) {
        mOutputQueue[output.first] = CameraBufQ();
    }
}

void BufferQueue::setFrameInfo(const std::map<Port, stream_t>& inputInfo,
                               const std::map<Port, stream_t>& outputInfo) {
    mInputFrameInfo = inputInfo;
    mOutputFrameInfo = outputInfo;

    clearBufferQueues();
}

void BufferQueue::getFrameInfo(std::map<Port, stream_t>& inputInfo,
                               std::map<Port, stream_t>& outputInfo) const {
    inputInfo = mInputFrameInfo;
    outputInfo = mOutputFrameInfo;
}

int BufferQueue::waitFreeBuffersInQueue(ConditionLock& lock,
                                        std::map<Port, std::shared_ptr<CameraBuffer> >& cInBuffer,
                                        std::map<Port, std::shared_ptr<CameraBuffer> >& cOutBuffer,
                                        int64_t timeout) {
    if (!mThreadRunning) {
        LOG1("@%s: Processor is not active.", __func__);
        return OK;
    }

    int ret = OK;
    timeout = (timeout ? timeout : kWaitDuration) * SLOWLY_MULTIPLIER;

    LOG2("@%s start waiting the input and output buffers", __func__);
    for (auto& input : mInputQueue) {
        Port port = input.first;
        CameraBufQ& inputQueue = input.second;
        while (inputQueue.empty()) {
            LOG2("%s: wait input port %d", __func__, port);
            ret = mFrameAvailableSignal.waitRelative(lock, timeout);

            // Thread was stopped during wait
            if (!mThreadRunning) {
                LOG1("@%s: Processor is not active while waiting for input buffers", __func__);
                return OK;
            }

            if (ret == TIMED_OUT) {
                return ret;
            }
        }
        // Wake up from the buffer available
        cInBuffer[port] = inputQueue.front();
    }

    for (auto& output : mOutputQueue) {
        Port port = output.first;
        CameraBufQ& outputQueue = output.second;
        while (outputQueue.empty()) {
            LOG2("%s: wait output port %d", __func__, port);
            ret = mOutputAvailableSignal.waitRelative(lock, timeout);

            // Thread was stopped during wait
            if (!mThreadRunning) {
                LOG1("@%s: Processor is not active while waiting for output buffers.", __func__);
                return OK;
            }

            if (ret == TIMED_OUT) {
                return ret;
            }
        }

        cOutBuffer[port] = outputQueue.front();
    }

    return ret;
}

int BufferQueue::allocProducerBuffers(int camId, int bufNum) {
    LOG1("%s: buffer queue size %d", __func__, bufNum);

    mInternalBuffers.clear();

    CheckAndLogError(!mBufferProducer, BAD_VALUE, "@%s: Buffer Producer is nullptr", __func__);

    for (const auto& item : mInputFrameInfo) {
        Port port = item.first;
        int srcFmt = item.second.format;
        int srcWidth = item.second.width;
        int srcHeight = item.second.height;

        LOG1("%s fmt:%s (%dx%d)", __func__, CameraUtils::format2string(srcFmt).c_str(), srcWidth,
             srcHeight);

        int32_t size = 0;
        bool isISYSCompression = PlatformData::getISYSCompression(camId);
        if (isISYSCompression)
            size = CameraUtils::getFrameSize(srcFmt, srcWidth, srcHeight, false, true, true);
        else
            size = CameraUtils::getFrameSize(srcFmt, srcWidth, srcHeight);
        int memType = mBufferProducer->getMemoryType();

        for (int i = 0; i < bufNum; i++) {
            std::shared_ptr<CameraBuffer> camBuffer;
            switch (memType) {
                case V4L2_MEMORY_USERPTR:
                    camBuffer =
                        CameraBuffer::create(camId, BUFFER_USAGE_PSYS_INPUT, V4L2_MEMORY_USERPTR,
                                             size, i, srcFmt, srcWidth, srcHeight);
                    CheckAndLogError(!camBuffer, NO_MEMORY,
                                     "Allocate producer userptr buffer failed");
                    break;

                case V4L2_MEMORY_MMAP:
                    camBuffer = std::make_shared<CameraBuffer>(camId, BUFFER_USAGE_PSYS_INPUT,
                                                               V4L2_MEMORY_MMAP, size, i, srcFmt);
                    CheckAndLogError(!camBuffer, NO_MEMORY, "Allocate producer mmap buffer failed");
                    camBuffer->setUserBufferInfo(srcFmt, srcWidth, srcHeight);
                    mBufferProducer->allocateMemory(port, camBuffer);
                    break;

                default:
                    LOGE("Not supported v4l2 memory type:%d", memType);
                    return BAD_VALUE;
            }

            mInternalBuffers[port].push_back(camBuffer);
            mBufferProducer->qbuf(port, camBuffer);
        }
    }

    return OK;
}

}  // namespace icamera
