/*
 * Copyright (C) 2022 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_TAG "AidlBufferPoolCli"
//#define LOG_NDEBUG 0

#include <thread>
#include <aidlcommonsupport/NativeHandle.h>
#include <utils/Log.h>
#include "BufferPoolClient.h"
#include "Accessor.h"
#include "Connection.h"

namespace aidl::android::hardware::media::bufferpool2::implementation {

using aidl::android::hardware::media::bufferpool2::IConnection;
using aidl::android::hardware::media::bufferpool2::ResultStatus;
using FetchInfo = aidl::android::hardware::media::bufferpool2::IConnection::FetchInfo;
using FetchResult = aidl::android::hardware::media::bufferpool2::IConnection::FetchResult;

static constexpr int64_t kReceiveTimeoutMs = 2000; // 2s
static constexpr int kPostMaxRetry = 3;
static constexpr int kCacheTtlMs = 1000;
static constexpr size_t kMaxCachedBufferCount = 64;
static constexpr size_t kCachedBufferCountTarget = kMaxCachedBufferCount - 16;

class BufferPoolClient::Impl
        : public std::enable_shared_from_this<BufferPoolClient::Impl> {
public:
    explicit Impl(const std::shared_ptr<Accessor> &accessor,
                  const std::shared_ptr<IObserver> &observer);

    explicit Impl(const std::shared_ptr<IAccessor> &accessor,
                  const std::shared_ptr<IObserver> &observer);

    bool isValid() {
        return mValid;
    }

    bool isLocal() {
        return mValid && mLocal;
    }

    ConnectionId getConnectionId() {
        return mConnectionId;
    }

    std::shared_ptr<IAccessor> &getAccessor() {
        return mAccessor;
    }

    bool isActive(int64_t *lastTransactionMs, bool clearCache);

    void receiveInvalidation(uint32_t msgID);

    BufferPoolStatus flush();

    BufferPoolStatus allocate(const std::vector<uint8_t> &params,
                          native_handle_t **handle,
                          std::shared_ptr<BufferPoolData> *buffer);

    BufferPoolStatus receive(
            TransactionId transactionId, BufferId bufferId,
            int64_t timestampMs,
            native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer);

    void postBufferRelease(BufferId bufferId);

    bool postSend(
            BufferId bufferId, ConnectionId receiver,
            TransactionId *transactionId, int64_t *timestampMs);
private:

    bool postReceive(
            BufferId bufferId, TransactionId transactionId,
            int64_t timestampMs);

    bool postReceiveResult(
            BufferId bufferId, TransactionId transactionId, bool result, bool *needsSync);

    void trySyncFromRemote();

    bool syncReleased(uint32_t msgId = 0);

    void evictCaches(bool clearCache = false);

    void invalidateBuffer(BufferId id);

    void invalidateRange(BufferId from, BufferId to);

    BufferPoolStatus allocateBufferHandle(
            const std::vector<uint8_t>& params, BufferId *bufferId,
            native_handle_t **handle);

    BufferPoolStatus fetchBufferHandle(
            TransactionId transactionId, BufferId bufferId,
            native_handle_t **handle);

    struct BlockPoolDataDtor;
    struct ClientBuffer;

    bool mLocal;
    bool mValid;
    std::shared_ptr<IAccessor> mAccessor;
    std::shared_ptr<Connection> mLocalConnection;
    std::shared_ptr<IConnection> mRemoteConnection;
    uint32_t mSeqId;
    ConnectionId mConnectionId;
    int64_t mLastEvictCacheMs;
    std::unique_ptr<BufferInvalidationListener> mInvalidationListener;

    // CachedBuffers
    struct BufferCache {
        std::mutex mLock;
        bool mCreating;
        std::condition_variable mCreateCv;
        std::map<BufferId, std::unique_ptr<ClientBuffer>> mBuffers;
        int mActive;
        int64_t mLastChangeMs;

        BufferCache() : mCreating(false), mActive(0),
                mLastChangeMs(::android::elapsedRealtime()) {}

        void incActive_l() {
            ++mActive;
            mLastChangeMs = ::android::elapsedRealtime();
        }

        void decActive_l() {
            --mActive;
            mLastChangeMs = ::android::elapsedRealtime();
        }

        int cachedBufferCount() const {
            return mBuffers.size() - mActive;
        }
    } mCache;

    // FMQ - release notifier
    struct ReleaseCache {
        std::mutex mLock;
        std::list<BufferId> mReleasingIds;
        std::list<BufferId> mReleasedIds;
        uint32_t mInvalidateId; // TODO: invalidation ACK to bufferpool
        bool mInvalidateAck;
        std::unique_ptr<BufferStatusChannel> mStatusChannel;

        ReleaseCache() : mInvalidateId(0), mInvalidateAck(true) {}
    } mReleasing;

    // This lock is held during synchronization from remote side.
    // In order to minimize remote calls and locking duration, this lock is held
    // by best effort approach using try_lock().
    std::mutex mRemoteSyncLock;
};

struct BufferPoolClient::Impl::BlockPoolDataDtor {
    BlockPoolDataDtor(const std::shared_ptr<BufferPoolClient::Impl> &impl)
            : mImpl(impl) {}

    void operator()(BufferPoolData *buffer) {
        BufferId id = buffer->mId;
        delete buffer;

        auto impl = mImpl.lock();
        if (impl && impl->isValid()) {
            impl->postBufferRelease(id);
        }
    }
    const std::weak_ptr<BufferPoolClient::Impl> mImpl;
};

struct BufferPoolClient::Impl::ClientBuffer {
private:
    int64_t mExpireMs;
    bool mHasCache;
    ConnectionId mConnectionId;
    BufferId mId;
    native_handle_t *mHandle;
    std::weak_ptr<BufferPoolData> mCache;

    void updateExpire() {
        mExpireMs = ::android::elapsedRealtime() + kCacheTtlMs;
    }

public:
    ClientBuffer(
            ConnectionId connectionId, BufferId id, native_handle_t *handle)
            : mHasCache(false), mConnectionId(connectionId),
              mId(id), mHandle(handle) {
        mExpireMs = ::android::elapsedRealtime() + kCacheTtlMs;
    }

    ~ClientBuffer() {
        if (mHandle) {
            native_handle_close(mHandle);
            native_handle_delete(mHandle);
        }
    }

    BufferId id() const {
        return mId;
    }

    bool expire() const {
        int64_t now = ::android::elapsedRealtime();
        return now >= mExpireMs;
    }

    bool hasCache() const {
        return mHasCache;
    }

    std::shared_ptr<BufferPoolData> fetchCache(native_handle_t **pHandle) {
        if (mHasCache) {
            std::shared_ptr<BufferPoolData> cache = mCache.lock();
            if (cache) {
                *pHandle = mHandle;
            }
            return cache;
        }
        return nullptr;
    }

    std::shared_ptr<BufferPoolData> createCache(
            const std::shared_ptr<BufferPoolClient::Impl> &impl,
            native_handle_t **pHandle) {
        if (!mHasCache) {
            // Allocates a raw ptr in order to avoid sending #postBufferRelease
            // from deleter, in case of native_handle_clone failure.
            BufferPoolData *ptr = new BufferPoolData(mConnectionId, mId);
            if (ptr) {
                std::shared_ptr<BufferPoolData> cache(ptr, BlockPoolDataDtor(impl));
                if (cache) {
                    mCache = cache;
                    mHasCache = true;
                    *pHandle = mHandle;
                    return cache;
                }
            }
            if (ptr) {
                delete ptr;
            }
        }
        return nullptr;
    }

    bool onCacheRelease() {
        if (mHasCache) {
            // TODO: verify mCache is not valid;
            updateExpire();
            mHasCache = false;
            return true;
        }
        return false;
    }
};

BufferPoolClient::Impl::Impl(const std::shared_ptr<Accessor> &accessor,
                             const std::shared_ptr<IObserver> &observer)
    : mLocal(true), mValid(false), mAccessor(accessor), mSeqId(0),
      mLastEvictCacheMs(::android::elapsedRealtime()) {
    StatusDescriptor statusDesc;
    InvalidationDescriptor invDesc;
    BufferPoolStatus status = accessor->connect(
            observer, true,
            &mLocalConnection, &mConnectionId, &mReleasing.mInvalidateId,
            &statusDesc, &invDesc);
    if (status == ResultStatus::OK) {
        mReleasing.mStatusChannel =
                std::make_unique<BufferStatusChannel>(statusDesc);
        mInvalidationListener =
                std::make_unique<BufferInvalidationListener>(invDesc);
        mValid = mReleasing.mStatusChannel &&
                mReleasing.mStatusChannel->isValid() &&
                mInvalidationListener &&
                mInvalidationListener->isValid();
    }
}

BufferPoolClient::Impl::Impl(const std::shared_ptr<IAccessor> &accessor,
                             const std::shared_ptr<IObserver> &observer)
    : mLocal(false), mValid(false), mAccessor(accessor), mSeqId(0),
      mLastEvictCacheMs(::android::elapsedRealtime()) {
    IAccessor::ConnectionInfo conInfo;
    bool valid = false;
    if (accessor && accessor->connect(observer, &conInfo).isOk()) {
        auto channel = std::make_unique<BufferStatusChannel>(conInfo.toFmqDesc);
        auto observer = std::make_unique<BufferInvalidationListener>(conInfo.fromFmqDesc);

        if (channel && channel->isValid()
            && observer && observer->isValid()) {
            mRemoteConnection = conInfo.connection;
            mConnectionId = conInfo.connectionId;
            mReleasing.mInvalidateId = conInfo.msgId;
            mReleasing.mStatusChannel = std::move(channel);
            mInvalidationListener = std::move(observer);
            valid = true;
        }
    }
    mValid = valid;
}

bool BufferPoolClient::Impl::isActive(int64_t *lastTransactionMs, bool clearCache) {
    bool active = false;
    {
        std::lock_guard<std::mutex> lock(mCache.mLock);
        syncReleased();
        evictCaches(clearCache);
        *lastTransactionMs = mCache.mLastChangeMs;
        active = mCache.mActive > 0;
    }
    if (mValid && mLocal && mLocalConnection) {
        mLocalConnection->cleanUp(clearCache);
        return true;
    }
    return active;
}

void BufferPoolClient::Impl::receiveInvalidation(uint32_t messageId) {
    std::lock_guard<std::mutex> lock(mCache.mLock);
    syncReleased(messageId);
    // TODO: evict cache required?
}

BufferPoolStatus BufferPoolClient::Impl::flush() {
    if (!mLocal || !mLocalConnection || !mValid) {
        return ResultStatus::CRITICAL_ERROR;
    }
    {
        std::unique_lock<std::mutex> lock(mCache.mLock);
        syncReleased();
        evictCaches();
        return mLocalConnection->flush();
    }
}

BufferPoolStatus BufferPoolClient::Impl::allocate(
        const std::vector<uint8_t> &params,
        native_handle_t **pHandle,
        std::shared_ptr<BufferPoolData> *buffer) {
    if (!mLocal || !mLocalConnection || !mValid) {
        return ResultStatus::CRITICAL_ERROR;
    }
    BufferId bufferId;
    native_handle_t *handle = nullptr;
    buffer->reset();
    BufferPoolStatus status = allocateBufferHandle(params, &bufferId, &handle);
    if (status == ResultStatus::OK) {
        if (handle) {
            std::unique_lock<std::mutex> lock(mCache.mLock);
            syncReleased();
            evictCaches();
            auto cacheIt = mCache.mBuffers.find(bufferId);
            if (cacheIt != mCache.mBuffers.end()) {
                // TODO: verify it is recycled. (not having active ref)
                mCache.mBuffers.erase(cacheIt);
            }
            auto clientBuffer = std::make_unique<ClientBuffer>(
                    mConnectionId, bufferId, handle);
            if (clientBuffer) {
                auto result = mCache.mBuffers.insert(std::make_pair(
                        bufferId, std::move(clientBuffer)));
                if (result.second) {
                    *buffer = result.first->second->createCache(
                            shared_from_this(), pHandle);
                    if (*buffer) {
                        mCache.incActive_l();
                    }
                }
            }
        }
        if (!*buffer) {
            ALOGV("client cache creation failure %d: %lld",
                  handle != nullptr, (long long)mConnectionId);
            status = ResultStatus::NO_MEMORY;
            postBufferRelease(bufferId);
        }
    }
    return status;
}

BufferPoolStatus BufferPoolClient::Impl::receive(
        TransactionId transactionId, BufferId bufferId, int64_t timestampMs,
        native_handle_t **pHandle,
        std::shared_ptr<BufferPoolData> *buffer) {
    if (!mValid) {
        return ResultStatus::CRITICAL_ERROR;
    }
    if (timestampMs != 0) {
        timestampMs += kReceiveTimeoutMs;
    }
    if (!postReceive(bufferId, transactionId, timestampMs)) {
        return ResultStatus::CRITICAL_ERROR;
    }
    BufferPoolStatus status = ResultStatus::CRITICAL_ERROR;
    buffer->reset();
    while(1) {
        std::unique_lock<std::mutex> lock(mCache.mLock);
        syncReleased();
        evictCaches();
        auto cacheIt = mCache.mBuffers.find(bufferId);
        if (cacheIt != mCache.mBuffers.end()) {
            if (cacheIt->second->hasCache()) {
                *buffer = cacheIt->second->fetchCache(pHandle);
                if (!*buffer) {
                    // check transfer time_out
                    lock.unlock();
                    std::this_thread::yield();
                    continue;
                }
                ALOGV("client receive from reference %lld", (long long)mConnectionId);
                break;
            } else {
                *buffer = cacheIt->second->createCache(shared_from_this(), pHandle);
                if (*buffer) {
                    mCache.incActive_l();
                }
                ALOGV("client receive from cache %lld", (long long)mConnectionId);
                break;
            }
        } else {
            if (!mCache.mCreating) {
                mCache.mCreating = true;
                lock.unlock();
                native_handle_t* handle = nullptr;
                status = fetchBufferHandle(transactionId, bufferId, &handle);
                lock.lock();
                if (status == ResultStatus::OK) {
                    if (handle) {
                        auto clientBuffer = std::make_unique<ClientBuffer>(
                                mConnectionId, bufferId, handle);
                        if (clientBuffer) {
                            auto result = mCache.mBuffers.insert(
                                    std::make_pair(bufferId, std::move(
                                            clientBuffer)));
                            if (result.second) {
                                *buffer = result.first->second->createCache(
                                        shared_from_this(), pHandle);
                                if (*buffer) {
                                    mCache.incActive_l();
                                }
                            }
                        }
                    }
                    if (!*buffer) {
                        status = ResultStatus::NO_MEMORY;
                    }
                }
                mCache.mCreating = false;
                lock.unlock();
                mCache.mCreateCv.notify_all();
                break;
            }
            mCache.mCreateCv.wait(lock);
        }
    }
    bool needsSync = false;
    bool posted = postReceiveResult(bufferId, transactionId,
                                      *buffer ? true : false, &needsSync);
    ALOGV("client receive %lld - %u : %s (%d)", (long long)mConnectionId, bufferId,
          *buffer ? "ok" : "fail", posted);
    if (mValid && mLocal && mLocalConnection) {
        mLocalConnection->cleanUp(false);
    }
    if (needsSync && mRemoteConnection) {
        trySyncFromRemote();
    }
    if (*buffer) {
        if (!posted) {
            buffer->reset();
            return ResultStatus::CRITICAL_ERROR;
        }
        return ResultStatus::OK;
    }
    return status;
}


void BufferPoolClient::Impl::postBufferRelease(BufferId bufferId) {
    std::lock_guard<std::mutex> lock(mReleasing.mLock);
    mReleasing.mReleasingIds.push_back(bufferId);
    mReleasing.mStatusChannel->postBufferRelease(
            mConnectionId, mReleasing.mReleasingIds, mReleasing.mReleasedIds);
}

// TODO: revise ad-hoc posting data structure
bool BufferPoolClient::Impl::postSend(
        BufferId bufferId, ConnectionId receiver,
        TransactionId *transactionId, int64_t *timestampMs) {
    {
        // TODO: don't need to call syncReleased every time
        std::lock_guard<std::mutex> lock(mCache.mLock);
        syncReleased();
    }
    bool ret = false;
    bool needsSync = false;
    {
        std::lock_guard<std::mutex> lock(mReleasing.mLock);
        *timestampMs = ::android::elapsedRealtime();
        *transactionId = (mConnectionId << 32) | mSeqId++;
        // TODO: retry, add timeout, target?
        ret =  mReleasing.mStatusChannel->postBufferStatusMessage(
                *transactionId, bufferId, BufferStatus::TRANSFER_TO, mConnectionId,
                receiver, mReleasing.mReleasingIds, mReleasing.mReleasedIds);
        needsSync = !mLocal && mReleasing.mStatusChannel->needsSync();
    }
    if (mValid && mLocal && mLocalConnection) {
        mLocalConnection->cleanUp(false);
    }
    if (needsSync && mRemoteConnection) {
        trySyncFromRemote();
    }
    return ret;
}

bool BufferPoolClient::Impl::postReceive(
        BufferId bufferId, TransactionId transactionId, int64_t timestampMs) {
    for (int i = 0; i < kPostMaxRetry; ++i) {
        std::unique_lock<std::mutex> lock(mReleasing.mLock);
        int64_t now = ::android::elapsedRealtime();
        if (timestampMs == 0 || now < timestampMs) {
            bool result = mReleasing.mStatusChannel->postBufferStatusMessage(
                    transactionId, bufferId, BufferStatus::TRANSFER_FROM,
                    mConnectionId, -1, mReleasing.mReleasingIds,
                    mReleasing.mReleasedIds);
            if (result) {
                return true;
            }
            lock.unlock();
            std::this_thread::yield();
        } else {
            mReleasing.mStatusChannel->postBufferStatusMessage(
                    transactionId, bufferId, BufferStatus::TRANSFER_TIMEOUT,
                    mConnectionId, -1, mReleasing.mReleasingIds,
                    mReleasing.mReleasedIds);
            return false;
        }
    }
    return false;
}

bool BufferPoolClient::Impl::postReceiveResult(
        BufferId bufferId, TransactionId transactionId, bool result, bool *needsSync) {
    std::lock_guard<std::mutex> lock(mReleasing.mLock);
    // TODO: retry, add timeout
    bool ret = mReleasing.mStatusChannel->postBufferStatusMessage(
            transactionId, bufferId,
            result ? BufferStatus::TRANSFER_OK : BufferStatus::TRANSFER_ERROR,
            mConnectionId, -1, mReleasing.mReleasingIds,
            mReleasing.mReleasedIds);
    *needsSync = !mLocal && mReleasing.mStatusChannel->needsSync();
    return ret;
}

void BufferPoolClient::Impl::trySyncFromRemote() {
    if (mRemoteSyncLock.try_lock()) {
        bool needsSync = false;
        {
            std::lock_guard<std::mutex> lock(mReleasing.mLock);
            needsSync = mReleasing.mStatusChannel->needsSync();
        }
        if (needsSync) {
            if (!mRemoteConnection->sync().isOk()) {
                ALOGD("sync from client %lld failed: bufferpool process died.",
                      (long long)mConnectionId);
            }
        }
        mRemoteSyncLock.unlock();
    }
}

// should have mCache.mLock
bool BufferPoolClient::Impl::syncReleased(uint32_t messageId) {
    bool cleared = false;
    {
        std::lock_guard<std::mutex> lock(mReleasing.mLock);
        if (mReleasing.mReleasingIds.size() > 0) {
            mReleasing.mStatusChannel->postBufferRelease(
                    mConnectionId, mReleasing.mReleasingIds,
                    mReleasing.mReleasedIds);
        }
        if (mReleasing.mReleasedIds.size() > 0) {
            for (BufferId& id: mReleasing.mReleasedIds) {
                ALOGV("client release buffer %lld - %u", (long long)mConnectionId, id);
                auto found = mCache.mBuffers.find(id);
                if (found != mCache.mBuffers.end()) {
                    if (found->second->onCacheRelease()) {
                        mCache.decActive_l();
                    } else {
                        // should not happen!
                        ALOGW("client %lld cache release status inconsistent!",
                            (long long)mConnectionId);
                    }
                } else {
                    // should not happen!
                    ALOGW("client %lld cache status inconsistent!", (long long)mConnectionId);
                }
            }
            mReleasing.mReleasedIds.clear();
            cleared = true;
        }
    }
    std::vector<BufferInvalidationMessage> invalidations;
    mInvalidationListener->getInvalidations(invalidations);
    uint32_t lastMsgId = 0;
    if (invalidations.size() > 0) {
        for (auto it = invalidations.begin(); it != invalidations.end(); ++it) {
            if (it->messageId != 0) {
                lastMsgId = it->messageId;
            }
            if (it->fromBufferId == it->toBufferId) {
                // TODO: handle fromBufferId = UINT32_MAX
                invalidateBuffer(it->fromBufferId);
            } else {
                invalidateRange(it->fromBufferId, it->toBufferId);
            }
        }
    }
    {
        std::lock_guard<std::mutex> lock(mReleasing.mLock);
        if (lastMsgId != 0) {
            if (isMessageLater(lastMsgId, mReleasing.mInvalidateId)) {
                mReleasing.mInvalidateId = lastMsgId;
                mReleasing.mInvalidateAck = false;
            }
        } else if (messageId != 0) {
            // messages are drained.
            if (isMessageLater(messageId, mReleasing.mInvalidateId)) {
                mReleasing.mInvalidateId = messageId;
                mReleasing.mInvalidateAck = true;
            }
        }
        if (!mReleasing.mInvalidateAck) {
            // post ACK
            mReleasing.mStatusChannel->postBufferInvalidateAck(
                    mConnectionId,
                    mReleasing.mInvalidateId, &mReleasing.mInvalidateAck);
            ALOGV("client %lld invalidateion ack (%d) %u",
                (long long)mConnectionId,
                mReleasing.mInvalidateAck, mReleasing.mInvalidateId);
        }
    }
    return cleared;
}

// should have mCache.mLock
void BufferPoolClient::Impl::evictCaches(bool clearCache) {
    int64_t now = ::android::elapsedRealtime();
    if (now >= mLastEvictCacheMs + kCacheTtlMs ||
            clearCache || mCache.cachedBufferCount() > kMaxCachedBufferCount) {
        size_t evicted = 0;
        for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end();) {
            if (!it->second->hasCache() && (it->second->expire() ||
                        clearCache || mCache.cachedBufferCount() > kCachedBufferCountTarget)) {
                it = mCache.mBuffers.erase(it);
                ++evicted;
            } else {
                ++it;
            }
        }
        ALOGV("cache count %lld : total %zu, active %d, evicted %zu",
              (long long)mConnectionId, mCache.mBuffers.size(), mCache.mActive, evicted);
        mLastEvictCacheMs = now;
    }
}

// should have mCache.mLock
void BufferPoolClient::Impl::invalidateBuffer(BufferId id) {
    for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end(); ++it) {
        if (id == it->second->id()) {
            if (!it->second->hasCache()) {
                mCache.mBuffers.erase(it);
                ALOGV("cache invalidated %lld : buffer %u",
                      (long long)mConnectionId, id);
            } else {
                ALOGW("Inconsistent invalidation %lld : activer buffer!! %u",
                      (long long)mConnectionId, (unsigned int)id);
            }
            break;
        }
    }
}

// should have mCache.mLock
void BufferPoolClient::Impl::invalidateRange(BufferId from, BufferId to) {
    size_t invalidated = 0;
    for (auto it = mCache.mBuffers.begin(); it != mCache.mBuffers.end();) {
        if (!it->second->hasCache()) {
            BufferId bid = it->second->id();
            if (from < to) {
                if (from <= bid && bid < to) {
                    ++invalidated;
                    it = mCache.mBuffers.erase(it);
                    continue;
                }
            } else {
                if (from <= bid || bid < to) {
                    ++invalidated;
                    it = mCache.mBuffers.erase(it);
                    continue;
                }
            }
        }
        ++it;
    }
    ALOGV("cache invalidated %lld : # of invalidated %zu",
          (long long)mConnectionId, invalidated);
}

BufferPoolStatus BufferPoolClient::Impl::allocateBufferHandle(
        const std::vector<uint8_t>& params, BufferId *bufferId,
        native_handle_t** handle) {
    if (mLocalConnection) {
        const native_handle_t* allocHandle = nullptr;
        BufferPoolStatus status = mLocalConnection->allocate(
                params, bufferId, &allocHandle);
        if (status == ResultStatus::OK) {
            *handle = native_handle_clone(allocHandle);
        }
        ALOGV("client allocate result %lld %d : %u clone %p",
              (long long)mConnectionId, status == ResultStatus::OK,
              *handle ? *bufferId : 0 , *handle);
        return status;
    }
    return ResultStatus::CRITICAL_ERROR;
}

BufferPoolStatus BufferPoolClient::Impl::fetchBufferHandle(
        TransactionId transactionId, BufferId bufferId,
        native_handle_t **handle) {
    std::shared_ptr<IConnection> connection;
    if (mLocal) {
        connection = mLocalConnection;
    } else {
        connection = mRemoteConnection;
    }
    std::vector<FetchInfo> infos;
    std::vector<FetchResult> results;
    infos.emplace_back(FetchInfo{ToAidl(transactionId), ToAidl(bufferId)});
    ndk::ScopedAStatus status = connection->fetch(infos, &results);
    if (!status.isOk()) {
        BufferPoolStatus svcSpecific = status.getServiceSpecificError();
        return svcSpecific ? svcSpecific : ResultStatus::CRITICAL_ERROR;
    }
    if (results[0].getTag() == FetchResult::buffer) {
        if (results[0].get<FetchResult::buffer>().buffer.has_value()) {
            *handle = ::android::dupFromAidl(results[0].get<FetchResult::buffer>().buffer.value());
        } else {
            // TODO: Support HardwareBuffer
            ALOGW("handle nullptr");
            *handle = nullptr;
        }
        return ResultStatus::OK;
    }
    return results[0].get<FetchResult::failure>();
}


BufferPoolClient::BufferPoolClient(const std::shared_ptr<Accessor> &accessor,
                                   const std::shared_ptr<IObserver> &observer) {
    mImpl = std::make_shared<Impl>(accessor, observer);
}

BufferPoolClient::BufferPoolClient(const std::shared_ptr<IAccessor> &accessor,
                                   const std::shared_ptr<IObserver> &observer) {
    mImpl = std::make_shared<Impl>(accessor, observer);
}

BufferPoolClient::~BufferPoolClient() {
    // TODO: how to handle orphaned buffers?
}

bool BufferPoolClient::isValid() {
    return mImpl && mImpl->isValid();
}

bool BufferPoolClient::isLocal() {
    return mImpl && mImpl->isLocal();
}

bool BufferPoolClient::isActive(int64_t *lastTransactionMs, bool clearCache) {
    if (!isValid()) {
        *lastTransactionMs = 0;
        return false;
    }
    return mImpl->isActive(lastTransactionMs, clearCache);
}

ConnectionId BufferPoolClient::getConnectionId() {
    if (isValid()) {
        return mImpl->getConnectionId();
    }
    return -1;
}

BufferPoolStatus BufferPoolClient::getAccessor(std::shared_ptr<IAccessor> *accessor) {
    if (isValid()) {
        *accessor = mImpl->getAccessor();
        return ResultStatus::OK;
    }
    return ResultStatus::CRITICAL_ERROR;
}

void BufferPoolClient::receiveInvalidation(uint32_t msgId) {
    ALOGV("bufferpool2 client recv inv %u", msgId);
    if (isValid()) {
        mImpl->receiveInvalidation(msgId);
    }
}

BufferPoolStatus BufferPoolClient::flush() {
    if (isValid()) {
        return mImpl->flush();
    }
    return ResultStatus::CRITICAL_ERROR;
}

BufferPoolStatus BufferPoolClient::allocate(
        const std::vector<uint8_t> &params,
        native_handle_t **handle,
        std::shared_ptr<BufferPoolData> *buffer) {
    if (isValid()) {
        return mImpl->allocate(params, handle, buffer);
    }
    return ResultStatus::CRITICAL_ERROR;
}

BufferPoolStatus BufferPoolClient::receive(
        TransactionId transactionId, BufferId bufferId, int64_t timestampMs,
        native_handle_t **handle, std::shared_ptr<BufferPoolData> *buffer) {
    if (isValid()) {
        return mImpl->receive(transactionId, bufferId, timestampMs, handle, buffer);
    }
    return ResultStatus::CRITICAL_ERROR;
}

BufferPoolStatus BufferPoolClient::postSend(
        ConnectionId receiverId,
        const std::shared_ptr<BufferPoolData> &buffer,
        TransactionId *transactionId,
        int64_t *timestampMs) {
    if (isValid()) {
        bool result = mImpl->postSend(
                buffer->mId, receiverId, transactionId, timestampMs);
        return result ? ResultStatus::OK : ResultStatus::CRITICAL_ERROR;
    }
    return ResultStatus::CRITICAL_ERROR;
}

}  // namespace aidl::android::hardware::media::bufferpool2::implementation
