/*
 * Copyright 2019 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 "B2HGraphicBufferProducer@2.0"

#include <android-base/logging.h>

#include <android/hardware/graphics/bufferqueue/2.0/types.h>
#include <android/hardware/graphics/common/1.2/types.h>
#include <gui/bufferqueue/2.0/H2BProducerListener.h>
#include <gui/bufferqueue/2.0/B2HGraphicBufferProducer.h>
#include <gui/bufferqueue/2.0/types.h>
#include <ui/GraphicBuffer.h>
#include <ui/Rect.h>
#include <ui/Region.h>
#include <vndk/hardware_buffer.h>

namespace android {
namespace hardware {
namespace graphics {
namespace bufferqueue {
namespace V2_0 {
namespace utils {

namespace /* unnamed */ {

using BQueueBufferInput = ::android::
        IGraphicBufferProducer::QueueBufferInput;
using HQueueBufferInput = ::android::hardware::graphics::bufferqueue::V2_0::
        IGraphicBufferProducer::QueueBufferInput;
using BQueueBufferOutput = ::android::
        IGraphicBufferProducer::QueueBufferOutput;
using HQueueBufferOutput = ::android::hardware::graphics::bufferqueue::V2_0::
        IGraphicBufferProducer::QueueBufferOutput;

using ::android::hardware::graphics::bufferqueue::V2_0::utils::b2h;
using ::android::hardware::graphics::bufferqueue::V2_0::utils::h2b;

bool b2h(BQueueBufferOutput const& from, HQueueBufferOutput* to) {
    to->width = from.width;
    to->height = from.height;
    to->transformHint = static_cast<int32_t>(from.transformHint);
    to->numPendingBuffers = from.numPendingBuffers;
    to->nextFrameNumber = from.nextFrameNumber;
    to->bufferReplaced = from.bufferReplaced;
    return true;
}

} // unnamed namespace

// B2HGraphicBufferProducer
// ========================

B2HGraphicBufferProducer::B2HGraphicBufferProducer(
        sp<BGraphicBufferProducer> const& base)
      : mBase{base} {
}

Return<HStatus> B2HGraphicBufferProducer::setMaxDequeuedBufferCount(
        int32_t maxDequeuedBuffers) {
    HStatus hStatus{};
    bool converted = b2h(
            mBase->setMaxDequeuedBufferCount(
                static_cast<int>(maxDequeuedBuffers)),
            &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<void> B2HGraphicBufferProducer::requestBuffer(
        int32_t slot,
        requestBuffer_cb _hidl_cb) {
    sp<GraphicBuffer> bBuffer;
    HStatus hStatus{};
    HardwareBuffer hBuffer{};
    uint32_t hGenerationNumber{};
    bool converted =
            b2h(mBase->requestBuffer(
                    static_cast<int>(slot), &bBuffer),
                &hStatus) &&
            b2h(bBuffer, &hBuffer, &hGenerationNumber);
    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR,
             hBuffer, hGenerationNumber);
    return {};
}

Return<HStatus> B2HGraphicBufferProducer::setAsyncMode(bool async) {
    HStatus hStatus{};
    bool converted = b2h(mBase->setAsyncMode(async), &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<void> B2HGraphicBufferProducer::dequeueBuffer(
        DequeueBufferInput const& input,
        dequeueBuffer_cb _hidl_cb) {
    int bSlot{};
    sp<BFence> bFence;
    HStatus hStatus{};
    DequeueBufferOutput hOutput{};
    HFenceWrapper hFenceWrapper;
    bool converted =
            b2h(mBase->dequeueBuffer(
                    &bSlot,
                    &bFence,
                    input.width,
                    input.height,
                    static_cast<PixelFormat>(input.format),
                    input.usage,
                    &hOutput.bufferAge,
                    nullptr /* outTimestamps */),
                &hStatus,
                &hOutput.bufferNeedsReallocation,
                &hOutput.releaseAllBuffers) &&
            b2h(bFence, &hFenceWrapper);
    hOutput.fence = hFenceWrapper.getHandle();
    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR,
             static_cast<int32_t>(bSlot),
             hOutput);
    return {};
}

Return<HStatus> B2HGraphicBufferProducer::detachBuffer(int32_t slot) {
    HStatus hStatus{};
    bool converted = b2h(
            mBase->detachBuffer(static_cast<int>(slot)), &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<void> B2HGraphicBufferProducer::detachNextBuffer(
        detachNextBuffer_cb _hidl_cb) {
    sp<GraphicBuffer> bBuffer;
    sp<BFence> bFence;
    HStatus hStatus{};
    HardwareBuffer hBuffer{};
    HFenceWrapper hFenceWrapper;
    bool converted =
            b2h(mBase->detachNextBuffer(&bBuffer, &bFence), &hStatus) &&
            b2h(bBuffer, &hBuffer) &&
            b2h(bFence, &hFenceWrapper);
    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR,
             hBuffer,
             hFenceWrapper.getHandle());
    return {};
}

Return<void> B2HGraphicBufferProducer::attachBuffer(
        HardwareBuffer const& hBuffer,
        uint32_t generationNumber,
        attachBuffer_cb _hidl_cb) {
    sp<GraphicBuffer> bBuffer;
    if (!h2b(hBuffer, &bBuffer) || !bBuffer) {
        _hidl_cb(HStatus::UNKNOWN_ERROR,
                 static_cast<int32_t>(SlotIndex::INVALID),
                 false);
        return {};
    }
    bBuffer->setGenerationNumber(generationNumber);

    int bSlot{};
    HStatus hStatus{};
    bool releaseAllBuffers{};
    bool converted = b2h(
            mBase->attachBuffer(&bSlot, bBuffer), &hStatus,
            nullptr /* bufferNeedsReallocation */,
            &releaseAllBuffers);
    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR,
             static_cast<int32_t>(bSlot),
             releaseAllBuffers);
    return {};
}

Return<void> B2HGraphicBufferProducer::queueBuffer(
        int32_t slot,
        QueueBufferInput const& hInput,
        queueBuffer_cb _hidl_cb) {
    BQueueBufferInput bInput{
            hInput.timestamp,
            hInput.isAutoTimestamp,
            static_cast<android_dataspace>(hInput.dataSpace),
            {}, /* crop */
            0 /* scalingMode = NATIVE_WINDOW_SCALING_MODE_FREEZE */,
            static_cast<uint32_t>(hInput.transform),
            {}, /* fence */
            static_cast<uint32_t>(hInput.stickyTransform),
            false /* getFrameTimestamps */};

    // Convert crop.
    if (!h2b(hInput.crop, &bInput.crop)) {
        _hidl_cb(HStatus::UNKNOWN_ERROR, QueueBufferOutput{});
        return {};
    }

    // Convert surfaceDamage.
    if (!h2b(hInput.surfaceDamage, &bInput.surfaceDamage)) {
        _hidl_cb(HStatus::UNKNOWN_ERROR, QueueBufferOutput{});
        return {};
    }

    // Convert fence.
    if (!h2b(hInput.fence, &bInput.fence)) {
        _hidl_cb(HStatus::UNKNOWN_ERROR, QueueBufferOutput{});
        return {};
    }

    BQueueBufferOutput bOutput{};
    HStatus hStatus{};
    QueueBufferOutput hOutput{};
    bool converted =
            b2h(
                mBase->queueBuffer(static_cast<int>(slot), bInput, &bOutput),
                &hStatus) &&
            b2h(bOutput, &hOutput);

    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR, hOutput);
    return {};
}

Return<HStatus> B2HGraphicBufferProducer::cancelBuffer(
        int32_t slot,
        hidl_handle const& fence) {
    sp<BFence> bFence;
    if (!h2b(fence.getNativeHandle(), &bFence)) {
        return {HStatus::UNKNOWN_ERROR};
    }
    HStatus hStatus{};
    bool converted = b2h(
            mBase->cancelBuffer(static_cast<int>(slot), bFence),
            &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<void> B2HGraphicBufferProducer::query(int32_t what, query_cb _hidl_cb) {
    int value{};
    int result = mBase->query(static_cast<int>(what), &value);
    _hidl_cb(static_cast<int32_t>(result), static_cast<int32_t>(value));
    return {};
}

struct Obituary : public hardware::hidl_death_recipient {
    wp<B2HGraphicBufferProducer> producer;
    sp<HProducerListener> listener;
    HConnectionType apiType;
    Obituary(const wp<B2HGraphicBufferProducer>& p,
             const sp<HProducerListener>& l,
             HConnectionType t)
        : producer(p), listener(l), apiType(t) {}

    void serviceDied(uint64_t /* cookie */,
            const wp<::android::hidl::base::V1_0::IBase>& /* who */) override {
        sp<B2HGraphicBufferProducer> dr = producer.promote();
        if (dr != nullptr) {
            (void)dr->disconnect(apiType);
        }
    }
};

Return<void> B2HGraphicBufferProducer::connect(
        sp<HProducerListener> const& hListener,
        HConnectionType hConnectionType,
        bool producerControlledByApp,
        connect_cb _hidl_cb) {
    sp<BProducerListener> bListener = new H2BProducerListener(hListener);
    int bConnectionType{};
    if (!bListener || !h2b(hConnectionType, &bConnectionType)) {
        _hidl_cb(HStatus::UNKNOWN_ERROR, QueueBufferOutput{});
        return {};
    }
    BQueueBufferOutput bOutput{};
    HStatus hStatus{};
    QueueBufferOutput hOutput{};
    bool converted =
            b2h(mBase->connect(bListener,
                               bConnectionType,
                               producerControlledByApp,
                               &bOutput),
                &hStatus) &&
            b2h(bOutput, &hOutput);
#ifdef NO_BINDER
    if (converted && hListener != nullptr) {
        mObituary = new Obituary(this, hListener, hConnectionType);
        hListener->linkToDeath(mObituary, 0);
    }
#endif // NO_BINDER
    _hidl_cb(converted ? hStatus : HStatus::UNKNOWN_ERROR, hOutput);
    return {};
}

Return<HStatus> B2HGraphicBufferProducer::disconnect(
        HConnectionType hConnectionType) {
    int bConnectionType;
    if (!h2b(hConnectionType, &bConnectionType)) {
        return {HStatus::UNKNOWN_ERROR};
    }
    HStatus hStatus{};
    bool converted = b2h(mBase->disconnect(bConnectionType), &hStatus);
#ifdef NO_BINDER
    if (mObituary != nullptr) {
        mObituary->listener->unlinkToDeath(mObituary);
        mObituary.clear();
    }
#endif // NO_BINDER
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<HStatus> B2HGraphicBufferProducer::allocateBuffers(
        uint32_t width, uint32_t height,
        uint32_t format, uint64_t usage) {
    mBase->allocateBuffers(
            width, height, static_cast<PixelFormat>(format), usage);
    return {HStatus::OK};
}

Return<HStatus> B2HGraphicBufferProducer::allowAllocation(bool allow) {
    HStatus hStatus{};
    bool converted = b2h(mBase->allowAllocation(allow), &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<HStatus> B2HGraphicBufferProducer::setGenerationNumber(
        uint32_t generationNumber) {
    HStatus hStatus{};
    bool converted = b2h(
            mBase->setGenerationNumber(generationNumber),
            &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<HStatus> B2HGraphicBufferProducer::setDequeueTimeout(
        int64_t timeoutNs) {
    HStatus hStatus{};
    bool converted = b2h(
            mBase->setDequeueTimeout(static_cast<nsecs_t>(timeoutNs)),
            &hStatus);
    return {converted ? hStatus : HStatus::UNKNOWN_ERROR};
}

Return<uint64_t> B2HGraphicBufferProducer::getUniqueId() {
    uint64_t outId{};
    HStatus hStatus{};
    bool converted = b2h(mBase->getUniqueId(&outId), &hStatus);
    return {converted ? outId : 0};
}

Return<void> B2HGraphicBufferProducer::getConsumerName(
        getConsumerName_cb _hidl_cb) {
    _hidl_cb(hidl_string{mBase->getConsumerName().c_str()});
    return {};
}

}  // namespace utils
}  // namespace V2_0
}  // namespace bufferqueue
}  // namespace graphics
}  // namespace hardware
}  // namespace android

