/*
 * Copyright 2023 The Android Open Source Project
 *
 * 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_NDEBUG 0
#define LOG_TAG "Codec2-MultiAccessUnitHelper"
#include <android-base/logging.h>

#include <com_android_media_codec_flags.h>

#include <codec2/common/MultiAccessUnitHelper.h>
#include <android-base/properties.h>

#include <C2BufferPriv.h>
#include <C2Debug.h>
#include <C2PlatformSupport.h>

static inline constexpr  uint32_t MAX_SUPPORTED_SIZE = ( 10 * 512000 * 8 * 2u);
namespace android {

static C2R MultiAccessUnitParamsSetter(
        bool mayBlock, C2InterfaceHelper::C2P<C2LargeFrame::output> &me) {
    (void)mayBlock;
    C2R res = C2R::Ok();
    if (!me.F(me.v.maxSize).supportsAtAll(me.v.maxSize)) {
        res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.maxSize)));
    } else if (!me.F(me.v.thresholdSize).supportsAtAll(me.v.thresholdSize)) {
        res = res.plus(C2SettingResultBuilder::BadValue(me.F(me.v.thresholdSize)));
    } else if (me.v.maxSize < me.v.thresholdSize) {
        me.set().maxSize = me.v.thresholdSize;
    }
    std::vector<std::unique_ptr<C2SettingResult>> failures;
    res.retrieveFailures(&failures);
    if (!failures.empty()) {
        me.set().maxSize = 0;
        me.set().thresholdSize = 0;
    }
    return res;
}

MultiAccessUnitInterface::MultiAccessUnitInterface(
        const std::shared_ptr<C2ComponentInterface>& interface,
        std::shared_ptr<C2ReflectorHelper> helper)
        : C2InterfaceHelper(helper), mC2ComponentIntf(interface) {
    setDerivedInstance(this);
    addParameter(
            DefineParam(mLargeFrameParams, C2_PARAMKEY_OUTPUT_LARGE_FRAME)
            .withDefault(new C2LargeFrame::output(0u, 0, 0))
            .withFields({
                C2F(mLargeFrameParams, maxSize).inRange(
                        0, c2_min(UINT_MAX, MAX_SUPPORTED_SIZE)),
                C2F(mLargeFrameParams, thresholdSize).inRange(
                        0, c2_min(UINT_MAX, MAX_SUPPORTED_SIZE))
            })
            .withSetter(MultiAccessUnitParamsSetter)
            .build());
    std::vector<std::shared_ptr<C2ParamDescriptor>> supportedParams;
    querySupportedParams(&supportedParams);
    // Adding to set to do intf seperation in query/config
    for (std::shared_ptr<C2ParamDescriptor> &desc : supportedParams) {
        mSupportedParamIndexSet.insert(desc->index());
    }
    mParamFields.emplace_back(mLargeFrameParams.get(), &(mLargeFrameParams.get()->maxSize));
    mParamFields.emplace_back(mLargeFrameParams.get(), &(mLargeFrameParams.get()->thresholdSize));

    if (mC2ComponentIntf) {
        c2_status_t err = mC2ComponentIntf->query_vb({&mKind}, {}, C2_MAY_BLOCK, nullptr);
    }
}

bool MultiAccessUnitInterface::isValidField(const C2ParamField &field) const {
    return (std::find(mParamFields.begin(), mParamFields.end(), field) != mParamFields.end());
}
bool MultiAccessUnitInterface::isParamSupported(C2Param::Index index) {
    return (mSupportedParamIndexSet.count(index) != 0);
}

C2LargeFrame::output MultiAccessUnitInterface::getLargeFrameParam() const {
    return *mLargeFrameParams;
}

C2Component::kind_t MultiAccessUnitInterface::kind() const {
    return (C2Component::kind_t)(mKind.value);
}

bool MultiAccessUnitInterface::getDecoderSampleRateAndChannelCount(
        uint32_t * const sampleRate_, uint32_t * const channelCount_) const {
    if (sampleRate_ == nullptr || channelCount_ == nullptr) {
        return false;
    }
    if (mC2ComponentIntf) {
        C2StreamSampleRateInfo::output sampleRate;
        C2StreamChannelCountInfo::output channelCount;
        c2_status_t res = mC2ComponentIntf->query_vb(
                {&sampleRate, &channelCount}, {}, C2_MAY_BLOCK, nullptr);
        if (res == C2_OK && sampleRate.value > 0 && channelCount.value > 0) {
            *sampleRate_ = sampleRate.value;
            *channelCount_ = channelCount.value;
            return true;
        }
    }
    return false;
}

bool MultiAccessUnitInterface::getMaxInputSize(
        C2StreamMaxBufferSizeInfo::input* const maxInputSize) const {
    if (maxInputSize == nullptr || mC2ComponentIntf == nullptr) {
        return false;
    }
    c2_status_t err = mC2ComponentIntf->query_vb({maxInputSize}, {}, C2_MAY_BLOCK, nullptr);
    if (err != OK) {
        return false;
    }
    return true;
}

//C2MultiAccessUnitBuffer
class C2MultiAccessUnitBuffer : public C2Buffer {
    public:
        explicit C2MultiAccessUnitBuffer(
                const std::vector<C2ConstLinearBlock> &blocks):
                C2Buffer(blocks) {
        }
};

//MultiAccessUnitHelper
MultiAccessUnitHelper::MultiAccessUnitHelper(
        const std::shared_ptr<MultiAccessUnitInterface>& intf,
        std::shared_ptr<C2BlockPool>& linearPool):
        mMultiAccessOnOffAllowed(true),
        mInit(false),
        mInterface(intf),
        mLinearPool(linearPool) {
    if (mLinearPool) {
        mInit = true;
    }
}

MultiAccessUnitHelper::~MultiAccessUnitHelper() {
    std::unique_lock<std::mutex> l(mLock);
    mFrameHolder.clear();
}

bool MultiAccessUnitHelper::isEnabledOnPlatform() {
    bool result = com::android::media::codec::flags::provider_->large_audio_frame();
    if (!result) {
        return false;
    }
    //TODO: remove this before launch
    result = ::android::base::GetBoolProperty("debug.media.c2.large.audio.frame", true);
    LOG(DEBUG) << "MultiAccessUnitHelper " << (result ? "enabled" : "disabled");
    return result;
}

bool MultiAccessUnitHelper::tryReconfigure(const std::unique_ptr<C2Param> &param) {
    C2LargeFrame::output *lfp = C2LargeFrame::output::From(param.get());
    if (lfp == nullptr) {
        return false;
    }
    bool isDecoder = (mInterface->kind() == C2Component::KIND_DECODER) ? true : false;
    if (!isDecoder) {
        C2StreamMaxBufferSizeInfo::input maxInputSize(0);
        if (!mInterface->getMaxInputSize(&maxInputSize)) {
            LOG(ERROR) << "Error in reconfigure: "
                    << "Encoder failed to respond with a valid max input size";
            return false;
        }
        // This is assuming a worst case compression ratio of 1:1
        // In no case the encoder should give an output more than
        // what is being provided to the encoder in a single call.
        if (lfp->maxSize < maxInputSize.value) {
            lfp->maxSize = maxInputSize.value;
        }
    }
    lfp->maxSize =
            (lfp->maxSize > MAX_SUPPORTED_SIZE) ? MAX_SUPPORTED_SIZE :
                    (lfp->maxSize < 0) ? 0 : lfp->maxSize;
    lfp->thresholdSize =
            (lfp->thresholdSize > MAX_SUPPORTED_SIZE) ? MAX_SUPPORTED_SIZE :
                    (lfp->thresholdSize < 0) ? 0 : lfp->thresholdSize;
    C2LargeFrame::output currentConfig = mInterface->getLargeFrameParam();
    if ((currentConfig.maxSize == lfp->maxSize)
            && (currentConfig.thresholdSize == lfp->thresholdSize)) {
        // no need to update
        return false;
    }
    if (isDecoder) {
        bool isOnOffTransition =
                (currentConfig.maxSize == 0 && lfp->maxSize != 0)
                || (currentConfig.maxSize != 0 && lfp->maxSize == 0);
            if (isOnOffTransition && !mMultiAccessOnOffAllowed) {
                LOG(ERROR) << "Setting new configs not allowed"
                        << " MaxSize: " << lfp->maxSize
                        << " ThresholdSize: " << lfp->thresholdSize;
                return false;
            }
    }
    std::vector<C2Param*> config{lfp};
    std::vector<std::unique_ptr<C2SettingResult>> failures;
    if (C2_OK != mInterface->config(config, C2_MAY_BLOCK, &failures)) {
        LOG(ERROR) << "Dynamic config not applied for"
                << " MaxSize: " << lfp->maxSize
                << " ThresholdSize: " << lfp->thresholdSize;
        return false;
    }
    LOG(DEBUG) << "Updated from param maxSize "
            << lfp->maxSize
            << " ThresholdSize " << lfp->thresholdSize;
    return true;
}

std::shared_ptr<MultiAccessUnitInterface> MultiAccessUnitHelper::getInterface() {
    return mInterface;
}

bool MultiAccessUnitHelper::getStatus() {
    return mInit;
}

void MultiAccessUnitHelper::reset() {
    std::lock_guard<std::mutex> l(mLock);
    mFrameHolder.clear();
    mMultiAccessOnOffAllowed = true;
}

c2_status_t MultiAccessUnitHelper::error(
        std::list<std::unique_ptr<C2Work>> * const worklist) {
    if (worklist == nullptr) {
        LOG(ERROR) << "Provided null worklist for error()";
        mFrameHolder.clear();
        return C2_OK;
    }
    std::unique_lock<std::mutex> l(mLock);
    for (auto frame = mFrameHolder.begin(); frame != mFrameHolder.end(); frame++) {
        if (frame->mLargeWork) {
            finalizeWork(*frame, 0, true);
            worklist->push_back(std::move(frame->mLargeWork));
            frame->reset();
        }
    }
    mFrameHolder.clear();
    mMultiAccessOnOffAllowed = true;
    return C2_OK;
}

c2_status_t MultiAccessUnitHelper::flush(
        std::list<std::unique_ptr<C2Work>>* const c2flushedWorks) {
    c2_status_t c2res = C2_OK;
    std::lock_guard<std::mutex> l(mLock);
    for (auto iterWork = c2flushedWorks->begin() ; iterWork != c2flushedWorks->end(); ) {
        bool foundFlushedFrame = false;
        std::list<MultiAccessUnitInfo>::iterator frame =
                mFrameHolder.begin();
        while (frame != mFrameHolder.end() && !foundFlushedFrame) {
            auto it = frame->mComponentFrameIds.find(
                    (*iterWork)->input.ordinal.frameIndex.peekull());
            if (it != frame->mComponentFrameIds.end()) {
                LOG(DEBUG) << "Multi access-unit flush "
                        << (*iterWork)->input.ordinal.frameIndex.peekull()
                        << " with " << frame->inOrdinal.frameIndex.peekull();
                (*iterWork)->input.ordinal.frameIndex = frame->inOrdinal.frameIndex;
                frame = mFrameHolder.erase(frame);
                foundFlushedFrame = true;
            } else {
                ++frame;
            }
        }
        if (!foundFlushedFrame) {
            iterWork = c2flushedWorks->erase(iterWork);
        } else {
            ++iterWork;
        }
    }
    return c2res;
}

c2_status_t MultiAccessUnitHelper::scatter(
        std::list<std::unique_ptr<C2Work>> &largeWork,
        std::list<std::list<std::unique_ptr<C2Work>>>* const processedWork) {
    LOG(DEBUG) << "Multiple access-unit: scatter";
    if (processedWork == nullptr) {
        LOG(ERROR) << "MultiAccessUnitHelper provided with no work list";
        return C2_CORRUPTED;
    }
    for (std::unique_ptr<C2Work>& w : largeWork) {
        std::list<std::unique_ptr<C2Work>> sliceWork;
        C2WorkOrdinalStruct inputOrdinal = w->input.ordinal;
        // To hold correspondence and processing bits b/w input and output
        MultiAccessUnitInfo frameInfo(inputOrdinal);
        std::set<uint64_t>& frameSet = frameInfo.mComponentFrameIds;
        uint64_t newFrameIdx = mFrameIndex++;
        // TODO: Do not split buffers if component inherantly supports MultipleFrames.
        // if thats case, only replace frameindex.
        auto cloneInputWork = [&frameInfo, &newFrameIdx, this]
                (std::unique_ptr<C2Work>& inWork, uint32_t flags) -> std::unique_ptr<C2Work> {
            std::unique_ptr<C2Work> newWork(new C2Work);
            newWork->input.flags = (C2FrameData::flags_t)flags;
            newWork->input.ordinal = inWork->input.ordinal;
            newWork->input.ordinal.frameIndex = newFrameIdx;
            if (!inWork->input.configUpdate.empty()) {
                for (std::unique_ptr<C2Param>& param : inWork->input.configUpdate) {
                    if (param->index() == C2LargeFrame::output::PARAM_TYPE) {
                        if (tryReconfigure(param)) {
                            frameInfo.mConfigUpdate.push_back(std::move(param));
                        }
                    } else {
                        newWork->input.configUpdate.push_back(std::move(param));
                    }
                }
                inWork->input.configUpdate.clear();
            }
            newWork->input.infoBuffers = (inWork->input.infoBuffers);
            if (!inWork->worklets.empty() && inWork->worklets.front() != nullptr) {
                newWork->worklets.emplace_back(new C2Worklet);
                newWork->worklets.front()->component = inWork->worklets.front()->component;
                std::vector<std::unique_ptr<C2Tuning>> tunings;
                for (std::unique_ptr<C2Tuning>& tuning : inWork->worklets.front()->tunings) {
                    tunings.push_back(
                            std::unique_ptr<C2Tuning>(
                                    static_cast<C2Tuning*>(
                                            C2Param::Copy(*(tuning.get())).release())));
                }
                newWork->worklets.front()->tunings = std::move(tunings);
            }
            return newWork;
        };
        if (w->input.buffers.empty()
                || (w->input.buffers.front() == nullptr)
                || (!w->input.buffers.front()->hasInfo(
                        C2AccessUnitInfos::input::PARAM_TYPE))) {
            LOG(DEBUG) << "Empty or MultiAU info buffer scatter frames with frameIndex "
                    << inputOrdinal.frameIndex.peekull()
                    << ") -> newFrameIndex " << newFrameIdx
                    <<" : input ts " << inputOrdinal.timestamp.peekull();
            sliceWork.push_back(cloneInputWork(w, w->input.flags));
            if (!w->input.buffers.empty() && w->input.buffers.front() != nullptr) {
                sliceWork.back()->input.buffers = std::move(w->input.buffers);
            }
            frameSet.insert(newFrameIdx);
            processedWork->push_back(std::move(sliceWork));
        }  else {
            const std::vector<std::shared_ptr<C2Buffer>>& inBuffers = w->input.buffers;
            if (inBuffers.front()->data().linearBlocks().size() == 0) {
                LOG(ERROR) << "ERROR: Work has Large frame info but has no linear blocks.";
                return C2_CORRUPTED;
            }
            frameInfo.mInputC2Ref = inBuffers;
            const std::vector<C2ConstLinearBlock>& multiAU =
                    inBuffers.front()->data().linearBlocks();
            std::shared_ptr<const C2AccessUnitInfos::input> auInfo =
                    std::static_pointer_cast<const C2AccessUnitInfos::input>(
                    w->input.buffers.front()->getInfo(C2AccessUnitInfos::input::PARAM_TYPE));
            uint32_t offset = 0; uint32_t multiAUSize = multiAU.front().size();
            bool sendEos = false;
            for (int idx = 0; idx < auInfo->flexCount(); ++idx) {
                std::vector<C2ConstLinearBlock> au;
                const C2AccessUnitInfosStruct &info = auInfo->m.values[idx];
                sendEos |= (info.flags & C2FrameData::FLAG_END_OF_STREAM);
                std::unique_ptr<C2Work> newWork = cloneInputWork(w, info.flags);
                frameSet.insert(newFrameIdx);
                newFrameIdx = mFrameIndex++;
                newWork->input.ordinal.timestamp = info.timestamp;
                au.push_back(multiAU.front().subBlock(offset, info.size));
                if ((offset + info.size) > multiAUSize) {
                    LOG(ERROR) << "ERROR: access-unit offset > buffer size"
                            << " current offset " << (offset + info.size)
                            << " buffer size " << multiAUSize;
                    return C2_CORRUPTED;
                }
                newWork->input.buffers.push_back(
                        std::shared_ptr<C2Buffer>(new C2MultiAccessUnitBuffer(au)));
                LOG(DEBUG) << "Frame scatter queuing frames WITH info in ordinal "
                        << inputOrdinal.frameIndex.peekull()
                        << " info.size " << info.size
                        << " : TS " << newWork->input.ordinal.timestamp.peekull()
                        << " with index " << newFrameIdx - 1;
                // add to worklist
                sliceWork.push_back(std::move(newWork));
                processedWork->push_back(std::move(sliceWork));
                offset += info.size;
            }
            mFrameIndex--;
            if (!sendEos && (w->input.flags & C2FrameData::FLAG_END_OF_STREAM)) {
                if (!processedWork->empty()) {
                    std::list<std::unique_ptr<C2Work>> &sliceWork = processedWork->back();
                    if (!sliceWork.empty()) {
                        std::unique_ptr<C2Work> &work = sliceWork.back();
                        if (work) {
                            work->input.flags = C2FrameData::FLAG_END_OF_STREAM;
                        }
                    }
                }
            }
        }
        if (!processedWork->empty()) {
            C2LargeFrame::output multiAccessParams = mInterface->getLargeFrameParam();
            frameInfo.mLargeFrameTuning = multiAccessParams;
            std::lock_guard<std::mutex> l(mLock);
            mFrameHolder.push_back(std::move(frameInfo));
            mMultiAccessOnOffAllowed = false;
        }
    }
    return C2_OK;
}

c2_status_t MultiAccessUnitHelper::gather(
        std::list<std::unique_ptr<C2Work>> &c2workItems,
        std::list<std::unique_ptr<C2Work>>* const processedWork) {
    LOG(DEBUG) << "Multi access-unit gather process";
    if (processedWork == nullptr) {
        LOG(ERROR) << "Nothing provided for processed work";
        return C2_CORRUPTED;
    }
    auto addOutWork = [&processedWork](std::unique_ptr<C2Work>& work) {
        processedWork->push_back(std::move(work));
    };
    {
        std::lock_guard<std::mutex> l(mLock);
        for (auto& work : c2workItems) {
            LOG(DEBUG) << "FrameHolder Size: " << mFrameHolder.size();
            uint64_t thisFrameIndex = work->input.ordinal.frameIndex.peekull();
            bool removeEntry = work->worklets.empty()
                    || !work->worklets.front()
                    || (work->worklets.front()->output.flags
                        & C2FrameData::FLAG_INCOMPLETE) == 0;
            bool foundFrame = false;
            std::list<MultiAccessUnitInfo>::iterator frame =
                    mFrameHolder.begin();
            while (!foundFrame && frame != mFrameHolder.end()) {
                c2_status_t res = C2_OK;
                auto it = frame->mComponentFrameIds.find(thisFrameIndex);
                if (it != frame->mComponentFrameIds.end()) {
                    foundFrame = true;
                    LOG(DEBUG) << "onWorkDone (frameIndex " << thisFrameIndex
                            << " worklstsSze " << work->worklets.size()
                            << ") -> frameIndex " << frame->inOrdinal.frameIndex.peekull();
                    if (work->result != C2_OK
                            || work->worklets.empty()
                            || !work->worklets.front()
                            || frame->mLargeFrameTuning.maxSize == 0) {
                        if (removeEntry) {
                            frame->mComponentFrameIds.erase(it);
                            removeEntry = false;
                        }
                        if (frame->mLargeWork) {
                            finalizeWork(*frame);
                            addOutWork(frame->mLargeWork);
                            frame->reset();
                        }
                        c2_status_t workResult = work->result;
                        frame->mLargeWork = std::move(work);
                        frame->mLargeWork->input.ordinal.frameIndex =
                                frame->inOrdinal.frameIndex;
                        finalizeWork(*frame);
                        addOutWork(frame->mLargeWork);
                        frame->reset();
                        if (workResult != C2_OK) {
                            frame->mComponentFrameIds.clear();
                            removeEntry = false;
                        }
                    } else if (C2_OK != (res = processWorklets(*frame, work, addOutWork))) {
                        // Upon error in processing worklets, we return the work with
                        // result set to the error. This should indicate the error to the
                        // framework and thus doing what is necessary to handle the
                        // error.
                        LOG(DEBUG) << "Error while processing worklets";
                        if (frame->mLargeWork == nullptr) {
                            frame->mLargeWork.reset(new C2Work);
                            frame->mLargeWork->input.ordinal = frame->inOrdinal;
                            frame->mLargeWork->input.ordinal.frameIndex =
                                    frame->inOrdinal.frameIndex;
                        }
                        frame->mLargeWork->result = res;
                        finalizeWork(*frame);
                        addOutWork(frame->mLargeWork);
                        frame->reset();
                        frame->mComponentFrameIds.clear();
                        removeEntry = false;
                    }
                    if (removeEntry) {
                        LOG(DEBUG) << "Removing entry: " << thisFrameIndex
                                << " -> " << frame->inOrdinal.frameIndex.peekull();
                        frame->mComponentFrameIds.erase(it);
                    }
                    // This is to take care of the last bytes and to decide to send with
                    // FLAG_INCOMPLETE or not.
                    if ((frame->mWview
                            && (frame->mWview->offset() >= frame->mLargeFrameTuning.thresholdSize))
                            || frame->mComponentFrameIds.empty()) {
                        if (frame->mLargeWork) {
                            frame->mLargeWork->result = C2_OK;
                            finalizeWork(*frame);
                            addOutWork(frame->mLargeWork);
                            frame->reset();
                        }
                    }
                    if (frame->mComponentFrameIds.empty()) {
                        LOG(DEBUG) << "This frame is finished ID " << thisFrameIndex;
                        frame = mFrameHolder.erase(frame);
                        continue;
                    }
                } else {
                    LOG(DEBUG) << "Received an out-of-order output " << thisFrameIndex
                            << " expected: " <<mFrameHolder.front().inOrdinal.frameIndex.peekull();
                }
                frame++;
            }
            if (!foundFrame) {
                LOG(ERROR) <<" Error: Frame Holder reports no frame " << thisFrameIndex;
            }
        }
    }
    return C2_OK;
}

c2_status_t MultiAccessUnitHelper::createLinearBlock(MultiAccessUnitInfo &frame) {
    if (!mInit) {
        LOG(ERROR) << "Large buffer allocator failed";
        return C2_NO_MEMORY;
    }
    C2MemoryUsage usage = { C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE };
    uint32_t maxOutSize = frame.mLargeFrameTuning.maxSize;
    c2_status_t err = mLinearPool->fetchLinearBlock(maxOutSize, usage, &frame.mBlock);
    LOG(DEBUG) << "Allocated block with offset : " << frame.mBlock->offset()
            << " size " << frame.mBlock->size() << " Capacity " << frame.mBlock->capacity();
    if (err != C2_OK) {
        LOG(ERROR) << "Error allocating Multi access-unit Buffer";
        return err;
    }
    frame.mWview = std::make_shared<C2WriteView>(frame.mBlock->map().get());
    LOG(DEBUG) << "Allocated buffer : requested size : " <<
            frame.mLargeFrameTuning.maxSize
            << " alloc size " << frame.mWview->size();
    return C2_OK;
}

/*
 * For every work from the component, we try to do aggregation of work here.
*/
c2_status_t MultiAccessUnitHelper::processWorklets(MultiAccessUnitInfo &frame,
        std::unique_ptr<C2Work>& work,
        const std::function <void(std::unique_ptr<C2Work>&)>& addWork) {
    // This will allocate work, worklet, c2Block
    auto allocateWork = [&](MultiAccessUnitInfo &frame,
            bool allocateWorket = false,
            bool allocateBuffer = false) {
        c2_status_t ret = C2_OK;
        if (frame.mLargeWork == nullptr) {
            frame.mLargeWork.reset(new C2Work);
            frame.mLargeWork->result = C2_OK;
            frame.mLargeWork->input.flags = (C2FrameData::flags_t)0;
            frame.mLargeWork->input.ordinal = frame.inOrdinal;
            frame.mLargeWork->input.ordinal.frameIndex = frame.inOrdinal.frameIndex;
        }
        if (allocateWorket) {
            if (frame.mLargeWork->worklets.size() == 0) {
                frame.mLargeWork->worklets.emplace_back(new C2Worklet);
                frame.mLargeWork->worklets.back()->output.flags = (C2FrameData::flags_t)0;
            }
        }
        if (allocateBuffer) {
            if (frame.mWview == nullptr) {
                ret = createLinearBlock(frame);
            }
        }
        return ret;
    };
    // we will only have one worklet.
    bool foundEndOfStream = false;
    for (auto worklet = work->worklets.begin();
             worklet != work->worklets.end() && (*worklet) != nullptr; ++worklet) {
        uint32_t flagsForNoCopy = C2FrameData::FLAG_DROP_FRAME
                | C2FrameData::FLAG_DISCARD_FRAME
                | C2FrameData::FLAG_CORRUPT;
        if ((*worklet)->output.flags & flagsForNoCopy) {
            if (frame.mLargeWork) {
                finalizeWork(frame);
                addWork(frame.mLargeWork);
                frame.reset();
            }
            frame.mLargeWork = std::move(work);
            frame.mLargeWork->input.ordinal.frameIndex = frame.inOrdinal.frameIndex;
            finalizeWork(frame, (*worklet)->output.flags, true);
            addWork(frame.mLargeWork);
            frame.reset();
            return C2_OK;
        }
        int64_t sampleTimeUs = 0;
        uint32_t frameSize = 0;
        uint32_t sampleRate = 0;
        uint32_t channelCount = 0;
        if (mInterface->getDecoderSampleRateAndChannelCount(&sampleRate, &channelCount)) {
            sampleTimeUs = (1000000u) / (sampleRate * channelCount * 2);
            frameSize = channelCount * 2;
            if (mInterface->kind() == C2Component::KIND_DECODER) {
                frame.mLargeFrameTuning.maxSize =
                        (frame.mLargeFrameTuning.maxSize / frameSize) * frameSize;
                frame.mLargeFrameTuning.thresholdSize =
                        (frame.mLargeFrameTuning.thresholdSize / frameSize) * frameSize;
            }
        }
        c2_status_t c2ret = allocateWork(frame, true);
        if (c2ret != C2_OK) {
            return c2ret;
        }
        uint32_t flags = work->input.flags;
        flags |= frame.mLargeWork->input.flags;
        frame.mLargeWork->input.flags = (C2FrameData::flags_t)flags;
        C2FrameData& outputFramedata = frame.mLargeWork->worklets.front()->output;
        if (!(*worklet)->output.configUpdate.empty()) {
            for (auto& configUpdate : (*worklet)->output.configUpdate) {
                outputFramedata.configUpdate.push_back(std::move(configUpdate));
            }
            (*worklet)->output.configUpdate.clear();
        }
        outputFramedata.infoBuffers.insert(outputFramedata.infoBuffers.begin(),
                (*worklet)->output.infoBuffers.begin(),
                (*worklet)->output.infoBuffers.end());

        LOG(DEBUG) << "maxOutSize " << frame.mLargeFrameTuning.maxSize
                << " threshold " << frame.mLargeFrameTuning.thresholdSize;
        LOG(DEBUG) << "This worklet has " << (*worklet)->output.buffers.size() << " buffers"
                << " ts: " << (*worklet)->output.ordinal.timestamp.peekull();
        int64_t workletTimestamp = (*worklet)->output.ordinal.timestamp.peekull();
        int64_t timestamp = workletTimestamp;
        uint32_t flagsForCopy =  ((*worklet)->output.flags) & C2FrameData::FLAG_CODEC_CONFIG;
        for (int bufIdx = 0; bufIdx < (*worklet)->output.buffers.size(); ++bufIdx) {
            std::shared_ptr<C2Buffer>& buffer = (*worklet)->output.buffers[bufIdx];
            if (!buffer || buffer->data().linearBlocks().empty()) {
                continue;
            }
            const std::vector<C2ConstLinearBlock>& blocks = buffer->data().linearBlocks();
            if (blocks.size() > 0) {
                uint32_t inputOffset = 0;
                uint32_t inputSize = blocks.front().size();
                frame.mInfos.insert(frame.mInfos.end(),
                        buffer->info().begin(), buffer->info().end());
                if (frameSize != 0 && (mInterface->kind() == C2Component::KIND_DECODER)) {
                    // For decoders we only split multiples of 16bChannelCount*2
                    inputSize -= (inputSize % frameSize);
                }
                while (inputOffset < inputSize) {
                    if ((frame.mWview != nullptr)
                            && (frame.mWview->offset() >= frame.mLargeFrameTuning.thresholdSize)) {
                        frame.mLargeWork->result = C2_OK;
                        finalizeWork(frame, flagsForCopy);
                        addWork(frame.mLargeWork);
                        frame.reset();
                    }
                    if (mInterface->kind() == C2Component::KIND_ENCODER) {
                        if (inputSize > frame.mLargeFrameTuning.maxSize) {
                            LOG(WARNING) << "WARNING Encoder:"
                                    << " Output buffer too small for configuration"
                                    << " configured max size " << frame.mLargeFrameTuning.maxSize
                                    << " access unit size " << inputSize;
                            if (frame.mLargeWork && (frame.mWview && frame.mWview->offset() > 0)) {
                                frame.mLargeWork->result = C2_OK;
                                finalizeWork(frame, flagsForCopy);
                                addWork(frame.mLargeWork);
                                frame.reset();
                            }
                            frame.mLargeFrameTuning.maxSize = inputSize;
                        } else if ((frame.mWview != nullptr)
                                && (inputSize > frame.mWview->size())) {
                            LOG(DEBUG) << "Enc: Large frame hitting bufer limit, current size "
                                << frame.mWview->offset();
                            if (frame.mWview->offset() > 0) {
                                frame.mLargeWork->result = C2_OK;
                                finalizeWork(frame, flagsForCopy);
                                addWork(frame.mLargeWork);
                                frame.reset();
                            }
                        }
                    }
                    allocateWork(frame, true, true);
                    uint32_t flags = work->input.flags;
                    flags |= frame.mLargeWork->input.flags;
                    frame.mLargeWork->input.flags = (C2FrameData::flags_t)flags;
                    C2ReadView rView = blocks.front().map().get();
                    if (rView.error()) {
                        LOG(ERROR) << "Buffer read view error";
                        frame.mLargeWork->result = rView.error();
                        frame.mLargeWork->worklets.clear();
                        finalizeWork(frame, 0, true);
                        addWork(frame.mLargeWork);
                        frame.reset();
                        return C2_NO_MEMORY;
                    }
                    uint32_t toCopy = 0;
                    if (mInterface->kind() == C2Component::KIND_ENCODER) {
                        toCopy = inputSize;
                    } else {
                        toCopy = c2_min(frame.mWview->size(), (inputSize - inputOffset));
                        timestamp = workletTimestamp + inputOffset * sampleTimeUs;
                        LOG(DEBUG) << "ts " << timestamp
                                << " copiedOutput " << inputOffset
                                << " sampleTimeUs " << sampleTimeUs;
                    }
                    LOG(DEBUG) << " Copy size " << toCopy
                            << " ts " << timestamp;
                    memcpy(frame.mWview->data(), rView.data() + inputOffset, toCopy);
                    frame.mWview->setOffset(frame.mWview->offset() + toCopy);
                    inputOffset += toCopy;
                    mergeAccessUnitInfo(frame, flagsForCopy, toCopy, timestamp);
                }
            } else {
                frame.mLargeWork->worklets.front()->output.buffers.push_back(std::move(buffer));
                LOG(DEBUG) << "Copying worklets without linear buffer";
            }
        }
        uint32_t flagsForCsdOrEnd = (*worklet)->output.flags
                & (C2FrameData::FLAG_END_OF_STREAM | C2FrameData::FLAG_CODEC_CONFIG);
        if (flagsForCsdOrEnd) {
            LOG(DEBUG) << "Output worklet has CSD/EOS data";
            frame.mLargeWork->result = C2_OK;
            // we can assign timestamp as this will be evaluated in finalizeWork
            frame.mLargeWork->worklets.front()->output.ordinal.timestamp = timestamp;
            finalizeWork(frame, flagsForCsdOrEnd, true);
            addWork(frame.mLargeWork);
            frame.reset();
        }
    }
    return C2_OK;
}

c2_status_t MultiAccessUnitHelper::finalizeWork(
        MultiAccessUnitInfo& frame, uint32_t inFlags, bool forceComplete) {
    if (frame.mLargeWork == nullptr) {
        return C2_OK;
    }
    //prepare input ordinal
    frame.mLargeWork->input.ordinal = frame.inOrdinal;
    // remove this
    int64_t timeStampUs = frame.inOrdinal.timestamp.peekull();
    if (!frame.mAccessUnitInfos.empty()) {
        timeStampUs = frame.mAccessUnitInfos.front().timestamp;
    } else if (!frame.mLargeWork->worklets.empty()) {
        std::unique_ptr<C2Worklet> &worklet = frame.mLargeWork->worklets.front();
        if (worklet) {
            timeStampUs = worklet->output.ordinal.timestamp.peekull();
        }
    }
    LOG(DEBUG) << "Finalizing work with input Idx "
            << frame.mLargeWork->input.ordinal.frameIndex.peekull()
            << " timestamp " << timeStampUs
            << " inFlags " << inFlags;
    uint32_t finalFlags = 0;
    if ((!forceComplete)
            && (frame.mLargeWork->result == C2_OK)
            && (!frame.mComponentFrameIds.empty())) {
        finalFlags |= C2FrameData::FLAG_INCOMPLETE;
    }
    if (frame.mLargeWork->result == C2_OK) {
        finalFlags |= inFlags;
    }
    // update worklet if present
    if (!frame.mLargeWork->worklets.empty() &&
            frame.mLargeWork->worklets.front() != nullptr) {
        frame.mLargeWork->workletsProcessed = 1;
        C2FrameData& outFrameData = frame.mLargeWork->worklets.front()->output;
        outFrameData.ordinal.frameIndex = frame.inOrdinal.frameIndex.peekull();
        outFrameData.ordinal.timestamp = timeStampUs;
        finalFlags |= frame.mLargeWork->worklets.front()->output.flags;
        outFrameData.flags = (C2FrameData::flags_t)finalFlags;
        // update buffers
        if (frame.mBlock && (frame.mWview->offset() > 0)) {
            size_t size = frame.mWview->offset();
            LOG(DEBUG) << "Finalize : Block: Large frame size set as " << size
                    << " timestamp as " << timeStampUs
                    << "frameIndex " << outFrameData.ordinal.frameIndex.peekull();
            frame.mWview->setOffset(0);
            std::shared_ptr<C2Buffer> c2Buffer = C2Buffer::CreateLinearBuffer(
                    frame.mBlock->share(0, size, ::C2Fence()));
            frame.mLargeWork->worklets.front()->output.buffers.push_back(std::move(c2Buffer));
        }
        if (frame.mLargeWork->worklets.front()->output.buffers.size() > 0) {
            std::shared_ptr<C2Buffer>& c2Buffer =
                frame.mLargeWork->worklets.front()->output.buffers.front();
            if (c2Buffer != nullptr) {
                if (frame.mAccessUnitInfos.size() > 0) {
                    if (finalFlags & C2FrameData::FLAG_END_OF_STREAM) {
                        frame.mAccessUnitInfos.back().flags |= C2FrameData::FLAG_END_OF_STREAM;
                    }
                    std::shared_ptr<C2AccessUnitInfos::output> largeFrame =
                            C2AccessUnitInfos::output::AllocShared(
                                    frame.mAccessUnitInfos.size(), 0u, frame.mAccessUnitInfos);
                    frame.mInfos.push_back(largeFrame);
                    frame.mAccessUnitInfos.clear();
                }
                for (auto &info : frame.mInfos) {
                    c2Buffer->setInfo(std::const_pointer_cast<C2Info>(info));
                }
            }
        }
        if (frame.mConfigUpdate.size() > 0) {
            outFrameData.configUpdate.insert(
                    outFrameData.configUpdate.end(),
                    make_move_iterator(frame.mConfigUpdate.begin()),
                    make_move_iterator(frame.mConfigUpdate.end()));
        }
    }
    frame.mConfigUpdate.clear();
    frame.mInfos.clear();
    frame.mBlock.reset();
    frame.mWview.reset();

    LOG(DEBUG) << "Multi access-unitflag setting as " << finalFlags;
    return C2_OK;
}

void MultiAccessUnitHelper::mergeAccessUnitInfo(
        MultiAccessUnitInfo &frame,
        uint32_t flags_,
        uint32_t size,
        int64_t timestamp) {
    // Remove flags that are not part of Access unit info
    uint32_t flags = flags_ & ~(C2FrameData::FLAG_INCOMPLETE
            | C2FrameData::FLAG_DISCARD_FRAME
            | C2FrameData::FLAG_CORRUPT
            | C2FrameData::FLAG_CORRECTED);
    if (frame.mAccessUnitInfos.empty()) {
        frame.mAccessUnitInfos.emplace_back(flags, size, timestamp);
        return;
    }
    if ((mInterface->kind() == C2Component::KIND_DECODER) &&
            (frame.mAccessUnitInfos.back().flags == flags)) {
        // merge access units here
        C2AccessUnitInfosStruct &s = frame.mAccessUnitInfos.back();
        s.size += size; // don't have to update timestamp
    } else {
        frame.mAccessUnitInfos.emplace_back(flags, size, timestamp);
    }
}

void MultiAccessUnitHelper::MultiAccessUnitInfo::reset() {
    mBlock.reset();
    mWview.reset();
    mInfos.clear();
    mConfigUpdate.clear();
    mAccessUnitInfos.clear();
    mLargeWork.reset();
}

}  // namespace android
