// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//#define LOG_NDEBUG 0
#define LOG_TAG "VideoFramePool"

#include <v4l2_codec2/components/VideoFramePool.h>

#include <stdint.h>
#include <memory>

#include <android/hardware/graphics/common/1.0/types.h>
#include <base/bind.h>
#include <base/memory/ptr_util.h>
#include <base/time/time.h>
#include <log/log.h>

#include <v4l2_codec2/common/VideoTypes.h>
#include <v4l2_codec2/plugin_store/C2VdaBqBlockPool.h>
#include <v4l2_codec2/plugin_store/C2VdaPooledBlockPool.h>
#include <v4l2_codec2/plugin_store/V4L2AllocatorId.h>

using android::hardware::graphics::common::V1_0::BufferUsage;

namespace android {

// static
std::optional<uint32_t> VideoFramePool::getBufferIdFromGraphicBlock(C2BlockPool& blockPool,
                                                                    const C2Block2D& block) {
    ALOGV("%s() blockPool.getAllocatorId() = %u", __func__, blockPool.getAllocatorId());

    if (blockPool.getAllocatorId() == android::V4L2AllocatorId::V4L2_BUFFERPOOL) {
        return C2VdaPooledBlockPool::getBufferIdFromGraphicBlock(block);
    } else if (blockPool.getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
        C2VdaBqBlockPool* bqPool = static_cast<C2VdaBqBlockPool*>(&blockPool);
        return bqPool->getBufferIdFromGraphicBlock(block);
    }

    ALOGE("%s(): unknown allocator ID: %u", __func__, blockPool.getAllocatorId());
    return std::nullopt;
}

// static
c2_status_t VideoFramePool::requestNewBufferSet(C2BlockPool& blockPool, int32_t bufferCount,
                                                const ui::Size& size, uint32_t format,
                                                C2MemoryUsage usage) {
    ALOGV("%s() blockPool.getAllocatorId() = %u", __func__, blockPool.getAllocatorId());

    if (blockPool.getAllocatorId() == android::V4L2AllocatorId::V4L2_BUFFERPOOL) {
        C2VdaPooledBlockPool* bpPool = static_cast<C2VdaPooledBlockPool*>(&blockPool);
        return bpPool->requestNewBufferSet(bufferCount);
    } else if (blockPool.getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
        C2VdaBqBlockPool* bqPool = static_cast<C2VdaBqBlockPool*>(&blockPool);
        return bqPool->requestNewBufferSet(bufferCount, size.width, size.height, format, usage);
    }

    ALOGE("%s(): unknown allocator ID: %u", __func__, blockPool.getAllocatorId());
    return C2_BAD_VALUE;
}

// static
bool VideoFramePool::setNotifyBlockAvailableCb(C2BlockPool& blockPool, ::base::OnceClosure cb) {
    ALOGV("%s() blockPool.getAllocatorId() = %u", __func__, blockPool.getAllocatorId());

    if (blockPool.getAllocatorId() == C2PlatformAllocatorStore::BUFFERQUEUE) {
        C2VdaBqBlockPool* bqPool = static_cast<C2VdaBqBlockPool*>(&blockPool);
        return bqPool->setNotifyBlockAvailableCb(std::move(cb));
    }
    return false;
}

// static
std::unique_ptr<VideoFramePool> VideoFramePool::Create(
        std::shared_ptr<C2BlockPool> blockPool, const size_t numBuffers, const ui::Size& size,
        HalPixelFormat pixelFormat, bool isSecure,
        scoped_refptr<::base::SequencedTaskRunner> taskRunner) {
    ALOG_ASSERT(blockPool != nullptr);

    uint64_t usage = static_cast<uint64_t>(BufferUsage::VIDEO_DECODER);
    if (isSecure) {
        usage |= C2MemoryUsage::READ_PROTECTED;
    } else if (blockPool->getAllocatorId() == android::V4L2AllocatorId::V4L2_BUFFERPOOL) {
        // CPU access to buffers is only required in byte buffer mode.
        usage |= C2MemoryUsage::CPU_READ;
    }
    const C2MemoryUsage memoryUsage(usage);

    if (requestNewBufferSet(*blockPool, numBuffers, size, static_cast<uint32_t>(pixelFormat),
                            memoryUsage) != C2_OK) {
        return nullptr;
    }

    std::unique_ptr<VideoFramePool> pool = ::base::WrapUnique(new VideoFramePool(
            std::move(blockPool), size, pixelFormat, memoryUsage, std::move(taskRunner)));
    if (!pool->initialize()) return nullptr;
    return pool;
}

VideoFramePool::VideoFramePool(std::shared_ptr<C2BlockPool> blockPool, const ui::Size& size,
                               HalPixelFormat pixelFormat, C2MemoryUsage memoryUsage,
                               scoped_refptr<::base::SequencedTaskRunner> taskRunner)
      : mBlockPool(std::move(blockPool)),
        mSize(size),
        mPixelFormat(pixelFormat),
        mMemoryUsage(memoryUsage),
        mClientTaskRunner(std::move(taskRunner)) {
    ALOGV("%s(size=%dx%d)", __func__, size.width, size.height);
    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());
    DCHECK(mBlockPool);
    DCHECK(mClientTaskRunner);
}

bool VideoFramePool::initialize() {
    if (!mFetchThread.Start()) {
        ALOGE("Fetch thread failed to start.");
        return false;
    }
    mFetchTaskRunner = mFetchThread.task_runner();

    mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
    mFetchWeakThis = mFetchWeakThisFactory.GetWeakPtr();

    return true;
}

VideoFramePool::~VideoFramePool() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());

    mClientWeakThisFactory.InvalidateWeakPtrs();

    if (mFetchThread.IsRunning()) {
        mFetchTaskRunner->PostTask(FROM_HERE,
                                   ::base::BindOnce(&VideoFramePool::destroyTask, mFetchWeakThis));
        mFetchThread.Stop();
    }
}

void VideoFramePool::destroyTask() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());

    mFetchWeakThisFactory.InvalidateWeakPtrs();
}

bool VideoFramePool::getVideoFrame(GetVideoFrameCB cb) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());

    if (mOutputCb) {
        return false;
    }

    mOutputCb = std::move(cb);
    mFetchTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis));
    return true;
}

// static
void VideoFramePool::getVideoFrameTaskThunk(
        scoped_refptr<::base::SequencedTaskRunner> taskRunner,
        std::optional<::base::WeakPtr<VideoFramePool>> weakPool) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(weakPool);

    taskRunner->PostTask(FROM_HERE,
                         ::base::BindOnce(&VideoFramePool::getVideoFrameTask, *weakPool));
}

void VideoFramePool::getVideoFrameTask() {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mFetchTaskRunner->RunsTasksInCurrentSequence());

    // Variables used to exponential backoff retry when buffer fetching times out.
    constexpr size_t kFetchRetryDelayInit = 64;    // Initial delay: 64us
    constexpr size_t kFetchRetryDelayMax = 16384;  // Max delay: 16ms (1 frame at 60fps)
    constexpr size_t kFenceWaitTimeoutNs = 16000000;  // 16ms (1 frame at 60fps)
    static size_t sNumRetries = 0;
    static size_t sDelay = kFetchRetryDelayInit;

    C2Fence fence;
    std::shared_ptr<C2GraphicBlock> block;
    c2_status_t err = mBlockPool->fetchGraphicBlock(mSize.width, mSize.height,
                                                    static_cast<uint32_t>(mPixelFormat),
                                                    mMemoryUsage, &block, &fence);
    if (err == C2_BLOCKING) {
        err = fence.wait(kFenceWaitTimeoutNs);
        if (err == C2_OK) {
            ALOGV("%s(): fence wait succeded, retrying now", __func__);
            mFetchTaskRunner->PostTask(
                    FROM_HERE,
                    ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis));
            return;
        }
        ALOGV("%s(): fence wait unsucessful err=%d", __func__, err);
    } else if (err == C2_OMITTED) {
        // Fenced version is not supported, try legacy version.
        err = mBlockPool->fetchGraphicBlock(mSize.width, mSize.height,
                                            static_cast<uint32_t>(mPixelFormat), mMemoryUsage,
                                            &block);
    }

    if (err == C2_TIMED_OUT || err == C2_BLOCKING) {
        ALOGV("%s(): fetchGraphicBlock() timeout, waiting %zuus (%zu retry)", __func__, sDelay,
              sNumRetries + 1);
        mFetchTaskRunner->PostDelayedTask(
                FROM_HERE, ::base::BindOnce(&VideoFramePool::getVideoFrameTask, mFetchWeakThis),
                ::base::TimeDelta::FromMicroseconds(sDelay));

        sDelay = std::min(sDelay * 2, kFetchRetryDelayMax);  // Exponential backoff
        sNumRetries++;
        return;
    }

    // Reset to the default value.
    sNumRetries = 0;
    sDelay = kFetchRetryDelayInit;

    if (err != C2_OK) {
        ALOGE("%s(): Failed to fetch block, err=%d", __func__, err);
        return;
    }

    ALOG_ASSERT(block != nullptr);
    std::unique_ptr<VideoFrame> frame = VideoFrame::Create(std::move(block));
    std::optional<FrameWithBlockId> frameWithBlockId;
    std::optional<uint32_t> bufferId;

    if (bufferId && frame) {
        bufferId = getBufferIdFromGraphicBlock(*mBlockPool, *block);

        if (bufferId) {
            ALOGV("%s(): Got buffer with id = %u", __func__, *bufferId);
        }

        // Only pass the frame + id pair if both have successfully been obtained.
        // Otherwise exit the loop so a nullopt is passed to the client.
        frameWithBlockId = std::make_pair(std::move(frame), *bufferId);
    } else {
        ALOGE("%s(): Failed to generate VideoFrame or get the buffer id.", __func__);
    }

    mClientTaskRunner->PostTask(
            FROM_HERE, ::base::BindOnce(&VideoFramePool::onVideoFrameReady, mClientWeakThis,
                                        std::move(frameWithBlockId)));
}

void VideoFramePool::onVideoFrameReady(std::optional<FrameWithBlockId> frameWithBlockId) {
    ALOGV("%s()", __func__);
    ALOG_ASSERT(mClientTaskRunner->RunsTasksInCurrentSequence());

    if (!frameWithBlockId) {
        ALOGE("Failed to get GraphicBlock, abandoning all pending requests.");
        mClientWeakThisFactory.InvalidateWeakPtrs();
        mClientWeakThis = mClientWeakThisFactory.GetWeakPtr();
    }

    ALOG_ASSERT(mOutputCb);
    std::move(mOutputCb).Run(std::move(frameWithBlockId));
}

}  // namespace android