/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.

 * ubs-io is licensed under the Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *      http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include "flow.h"
#include "bio_trace.h"
#include "bio_tracepoint_helper.h"
#include "bio_types.h"
#include "flow_manager.h"

namespace ock {
namespace bio {
BResult Flow::GetAddrByOffset(uint64_t offset, uint32_t len, std::vector<FlowAddr> &flowAddr)
{
    LOG_TRACE("Flow:" << mFlowId << ", type:" << mType << ", offset:" << offset << ", len:" << len);
    BResult ret = ValidateAndPreloadRange(offset, len);
    if (ret != BIO_OK) {
        LOG_ERROR("Validate and preload range failed, ret: " << ret << ".");
        return ret;
    }

    ret = BuildFlowAddrs(offset, len, flowAddr);
    if (ret != BIO_OK) {
        LOG_ERROR("Build flow address failed, ret: " << ret << ".");
        return ret;
    }

    PreLoadSchedule();
    return BIO_OK;
}

BResult Flow::ValidateAndPreloadRange(uint64_t offset, uint32_t len)
{
    bool isInvalidRange = false;
    BIO_TP_START(WCACHE_FLOW_OFFSET_FAIL, &isInvalidRange, true);
    isInvalidRange = (offset < mTruncateOffset);
    BIO_TP_END;
    if (UNLIKELY(isInvalidRange)) {
        LOG_ERROR("Invalid offset:" << offset << ", flowId:" << mFlowId << ", truncate:" << mTruncateOffset);
        return BIO_ERR;
    }

    if (UINT64_MAX - offset < len) {
        LOG_ERROR("Invalid offset:" << offset << ", flowId:" << mFlowId << ", len:" << len);
        return BIO_ERR;
    }

    if (offset + len > mPreLoadOffset) {
        BIO_TRACE_START(FLOW_TRACE_PRELOAD_MEMORY);
        BResult ret = BIO_INNER_ERR;
        BIO_TP_START(WCACHE_HOLD_WAIT_FAIL, &ret, BIO_ERR);
        ret = HoldWait(offset + len);
        BIO_TP_END;
        if (ret != BIO_OK) {
            BIO_TRACE_END(FLOW_TRACE_PRELOAD_MEMORY, ret);
            return ret;
        }
        BIO_TRACE_END(FLOW_TRACE_PRELOAD_MEMORY, 0);
    }

    {
        WriteLocker<ReadWriteLock> lock(&mLock);
        if (mWritenOffset < offset + len) {
            mWritenOffset = offset + len;
        }
    }
    return BIO_OK;
}

BResult Flow::BuildFlowAddrs(uint64_t offset, uint32_t len, std::vector<FlowAddr> &flowAddr)
{
    BIO_TRACE_START(FLOW_TRACE_GETADDR);

    mLock.LockRead();
    uint64_t remainLen = len;
    uint64_t curOffset = offset - (mTruncateOffset / mChunkSize * mChunkSize);
    uint64_t curLen;
    uint64_t idx;

    while (remainLen > 0) {
        idx = curOffset / mChunkSize;
        if (UNLIKELY(idx >= mChunkList.size())) {
            mLock.UnLock();
            LOG_ERROR("Address out of range! idx: " << idx << "mChunkList size: " << mChunkList.size() << ".");
            BIO_TRACE_END(FLOW_TRACE_GETADDR, BIO_INNER_ERR);
            return BIO_INNER_ERR;
        }

        curLen = mChunkSize - curOffset % mChunkSize;
        curLen = (remainLen > curLen) ? curLen : remainLen;

        flowAddr.emplace_back(mChunkList[curOffset / mChunkSize], curOffset % mChunkSize, curLen);

        curOffset += curLen;
        remainLen -= curLen;
    }

    mLock.UnLock();
    BIO_TRACE_END(FLOW_TRACE_GETADDR, 0);
    return BIO_OK;
}

BResult Flow::TruncateOffset(uint64_t offset)
{
    std::vector<uint64_t> cleanList;

    LOG_DEBUG("Flow truncate offset, Flow:" << mFlowId << ", type:" << mType << ", truncate:" << offset);

    if (offset > mPreLoadOffset || offset > mWritenOffset) {
        LOG_ERROR("Invalid offset:" << offset << ", preLoad:" << mPreLoadOffset << ", writen:" << mWritenOffset);
        return BIO_ERR;
    }

    mLock.LockWrite();
    if (mTruncateOffset >= offset) {
        mLock.UnLock();
        return BIO_OK;
    }

    BIO_TRACE_START(FLOW_TRACE_TRUNCATE);
    uint64_t startOffset = mTruncateOffset / mChunkSize * mChunkSize;
    while (startOffset + mChunkSize <= offset && mChunkList.size() != 0) {
        cleanList.push_back(mChunkList[0]);
        mChunkList.erase(mChunkList.begin());
        startOffset += mChunkSize;
    }
    mTruncateOffset = offset;
    mLock.UnLock();

    for (uint32_t i = 0; i < cleanList.size(); i++) {
        uint64_t chunkId = cleanList[i];
        FlowManager::MediaFree(mRole, mType, mMediaId, mChunkSize, chunkId, mFlowId);
    }
    BIO_TRACE_END(FLOW_TRACE_TRUNCATE, 0);
    return BIO_OK;
}

BResult Flow::Seal()
{
    BResult ret = BIO_OK;
    BIO_TP_START(FLOW_SEAL_ERR, &ret, BIO_ERR);
    if (mSealed) {
        return ret;
    }
    mSealed = true;

    LOG_INFO("Seal flow:" << mFlowId);

    BIO_TRACE_START(FLOW_TRACE_SEAL);
    uint64_t writenOffset = mPreLoadOffset;
    mWritenOffset = writenOffset;
    ret = TruncateOffset(mWritenOffset);
    if (ret != BIO_OK) {
        LOG_ERROR("Truncate offset failed, ret " << ret);
    }
    BIO_TRACE_END(FLOW_TRACE_SEAL, 0);
    BIO_TP_END;
    BIO_TP_START(FLOW_SEAL_OK, &ret, BIO_OK);
    BIO_TP_END;
    return ret;
}

void Flow::PreLoadHandle()
{
    uint64_t chunkId;
    uint64_t offset;
    bool isReady;
    do {
        BIO_TRACE_START(FLOW_TRACE_PRELOAD_ALLOC);
        auto ret = FlowManager::MediaAlloc(mType, mMediaId, mFlowId, mPreLoadOffset, mChunkSize, &chunkId);
        if (ret != BIO_OK) {
            LOG_ERROR("Media alloc failed:" << ret << ", type:" << mType << ", mediaId:" << mMediaId
                                            << ", chunkSize:" << mChunkSize);
            HoldClean(NO_MAX_VALUE64, BIO_INNER_RETRY, false);
            BIO_TRACE_END(FLOW_TRACE_PRELOAD_ALLOC, ret);
            break;
        }
        BIO_TRACE_END(FLOW_TRACE_PRELOAD_ALLOC, ret);
        mLock.LockWrite();
        mChunkList.push_back(chunkId);
        mPreLoadOffset += mChunkSize;
        isReady = (mPreLoadOffset >= mWritenOffset + mPreLoadSize);
        offset = mPreLoadOffset;
        mLock.UnLock();
        HoldClean(offset, BIO_OK, !isReady);
    } while (!isReady);
}

void Flow::PreLoadSchedule()
{
    mLock.LockRead();
    if (mPreLoadOffset >= mWritenOffset + mPreLoadSize) {
        mLock.UnLock();
        return;
    }
    mLock.UnLock();

    mLock.LockWrite();
    bool preloadFlag = mPreLoadFlag;
    if (!mPreLoadFlag) {
        mPreLoadFlag = true;
    }
    mLock.UnLock();
    if (preloadFlag) {
        LOG_DEBUG("PreLoadSchedule: not ready:" << mType << ", Flow:" << mFlowId);
        return;
    }
    IncreaseRef();
    std::function<void()> func = [&]() {
        PreLoadHandle();
        DecreaseRef();
    };
    auto ret = FlowManager::Instance()->PreLoadObject(mType, func);
    if (ret != BIO_OK) {
        LOG_ERROR("Preload failed:" << ret << ", flowId:" << mFlowId);
        mPreLoadFlag = false;
        DecreaseRef();
        return;
    }
}

void Flow::HoldClean(uint64_t realOffset, int32_t ret, bool preLoadFlag)
{
    IoHoldCtx *waitCtx = nullptr;
    mLock.LockWrite();
    mPreLoadFlag = preLoadFlag;
    while (!mHoldList.empty()) {
        waitCtx = mHoldList.front();
        if (waitCtx->needOffset <= realOffset) {
            mHoldList.pop_front();
            waitCtx->ret = ret;
            sem_post(&waitCtx->sem);
        } else {
            break;
        }
    }
    mLock.UnLock();
}

BResult Flow::HoldWait(uint64_t needOffset)
{
    IoHoldCtx ioHoldCtx;

    BIO_TRACE_START(FLOW_TRACE_HOLDWAIT);
    sem_init(&ioHoldCtx.sem, 0, 0);
    ioHoldCtx.needOffset = needOffset;

    mLock.LockWrite();
    if (mWritenOffset < needOffset) {
        mWritenOffset = needOffset;
    }
    if (mPreLoadOffset >= needOffset) {
        mLock.UnLock();
        sem_destroy(&ioHoldCtx.sem);
        BIO_TRACE_END(FLOW_TRACE_HOLDWAIT, 0);
        return BIO_OK;
    }
    mHoldList.push_back(&ioHoldCtx);
    mLock.UnLock();
    PreLoadSchedule();

    sem_wait(&ioHoldCtx.sem);
    sem_destroy(&ioHoldCtx.sem);
    BIO_TRACE_END(FLOW_TRACE_HOLDWAIT, 0);

    return ioHoldCtx.ret;
}

BResult Flow::RecoverChunk(uint64_t offset, uint64_t chunkId)
{
    if (mRecoverList.find(offset) != mRecoverList.end()) {
        LOG_ERROR("Repeat confict, flowId:" << mFlowId << ", flowOffset:" << offset << ".");
        return BIO_ERR;
    }
    LOG_TRACE("Recover chunk: flowId:" << mFlowId << ", flowOffset:" << offset << ".");
    mRecoverList[offset] = chunkId;
    return BIO_OK;
}

BResult Flow::RecoverCheck()
{
    if (mRecoverList.empty()) {
        return BIO_OK;
    }
    bool isFirst = true;
    for (auto &elem : mRecoverList) {
        mChunkList.push_back(elem.second);
        if (isFirst) {
            mTruncateOffset = elem.first;
            mPreLoadOffset = elem.first + mChunkSize;
            isFirst = false;
            continue;
        }
        if (mPreLoadOffset != elem.first) {
            LOG_ERROR("Recover check fail, preLoad:" << mPreLoadOffset << ", current offset:" << elem.first);
            return BIO_ERR;
        }
        mPreLoadOffset = elem.first + mChunkSize;
    }
    LOG_INFO("Recover succeed, flowId:" << mFlowId << ", truncate:" << mTruncateOffset
                                        << ", preLoad:" << mPreLoadOffset);
    return BIO_OK;
}
} // namespace bio
} // namespace ock