/*
 * Copyright (C) 2018 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.
 */

#include "ReliableSurface.h"

#include <log/log_main.h>
#include <private/android/AHardwareBufferHelpers.h>
// TODO: this should be including apex instead.
#include <system/window.h>
#include <vndk/window.h>

namespace android::uirenderer::renderthread {

// TODO: Re-enable after addressing more of the TODO's
// With this disabled we won't have a good up-front signal that the surface is no longer valid,
// however we can at least handle that reactively post-draw. There's just not a good mechanism
// to propagate this error back to the caller
constexpr bool DISABLE_BUFFER_PREFETCH = true;

ReliableSurface::ReliableSurface(ANativeWindow* window) : mWindow(window) {
    LOG_ALWAYS_FATAL_IF(!mWindow, "Error, unable to wrap a nullptr");
    ANativeWindow_acquire(mWindow);
}

ReliableSurface::~ReliableSurface() {
    clearReservedBuffer();
    // Clear out the interceptors for proper hygiene.
    // As a concrete example, if the underlying ANativeWindow is associated with
    // an EGLSurface that is still in use, then if we don't clear out the
    // interceptors then we walk into undefined behavior.
    ANativeWindow_setCancelBufferInterceptor(mWindow, nullptr, nullptr);
    ANativeWindow_setDequeueBufferInterceptor(mWindow, nullptr, nullptr);
    ANativeWindow_setQueueBufferInterceptor(mWindow, nullptr, nullptr);
    ANativeWindow_setPerformInterceptor(mWindow, nullptr, nullptr);
    ANativeWindow_setQueryInterceptor(mWindow, nullptr, nullptr);
    ANativeWindow_release(mWindow);
}

void ReliableSurface::init() {
    int result = ANativeWindow_setCancelBufferInterceptor(mWindow, hook_cancelBuffer, this);
    LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set cancelBuffer interceptor: error = %d",
                        result);

    result = ANativeWindow_setDequeueBufferInterceptor(mWindow, hook_dequeueBuffer, this);
    LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set dequeueBuffer interceptor: error = %d",
                        result);

    result = ANativeWindow_setQueueBufferInterceptor(mWindow, hook_queueBuffer, this);
    LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set queueBuffer interceptor: error = %d",
                        result);

    result = ANativeWindow_setPerformInterceptor(mWindow, hook_perform, this);
    LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set perform interceptor: error = %d",
                        result);

    result = ANativeWindow_setQueryInterceptor(mWindow, hook_query, this);
    LOG_ALWAYS_FATAL_IF(result != NO_ERROR, "Failed to set query interceptor: error = %d",
                        result);
}

int ReliableSurface::reserveNext() {
    if constexpr (DISABLE_BUFFER_PREFETCH) {
        return OK;
    }
    {
        std::lock_guard _lock{mMutex};
        if (mReservedBuffer) {
            ALOGW("reserveNext called but there was already a buffer reserved?");
            return OK;
        }
        if (mBufferQueueState != OK) {
            return UNKNOWN_ERROR;
        }
        if (mHasDequeuedBuffer) {
            return OK;
        }
    }

    // TODO: Update this to better handle when requested dimensions have changed
    // Currently the driver does this via query + perform but that's after we've already
    // reserved a buffer. Should we do that logic instead? Or should we drop
    // the backing Surface to the ground and go full manual on the IGraphicBufferProducer instead?

    int fenceFd = -1;
    ANativeWindowBuffer* buffer = nullptr;

    // Note that this calls back into our own hooked method.
    int result = ANativeWindow_dequeueBuffer(mWindow, &buffer, &fenceFd);

    {
        std::lock_guard _lock{mMutex};
        LOG_ALWAYS_FATAL_IF(mReservedBuffer, "race condition in reserveNext");
        mReservedBuffer = buffer;
        mReservedFenceFd.reset(fenceFd);
    }

    return result;
}

void ReliableSurface::clearReservedBuffer() {
    ANativeWindowBuffer* buffer = nullptr;
    int releaseFd = -1;
    {
        std::lock_guard _lock{mMutex};
        if (mReservedBuffer) {
            ALOGW("Reserved buffer %p was never used", mReservedBuffer);
            buffer = mReservedBuffer;
            releaseFd = mReservedFenceFd.release();
        }
        mReservedBuffer = nullptr;
        mReservedFenceFd.reset();
        mHasDequeuedBuffer = false;
    }
    if (buffer) {
        // Note that clearReservedBuffer may be reentrant here, so
        // mReservedBuffer must be cleared once we reach here to avoid recursing
        // forever.
        ANativeWindow_cancelBuffer(mWindow, buffer, releaseFd);
    }
}

bool ReliableSurface::isFallbackBuffer(const ANativeWindowBuffer* windowBuffer) const {
    if (!mScratchBuffer || !windowBuffer) {
        return false;
    }
    ANativeWindowBuffer* scratchBuffer =
            AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
    return windowBuffer == scratchBuffer;
}

ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) {
    std::lock_guard _lock{mMutex};
    mBufferQueueState = error;

    if (mScratchBuffer) {
        return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
    }

    AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
            .width = 1,
            .height = 1,
            .layers = 1,
            .format = mFormat,
            .usage = mUsage,
            .rfu0 = 0,
            .rfu1 = 0,
    };

    AHardwareBuffer* newBuffer;
    int result = AHardwareBuffer_allocate(&desc, &newBuffer);

    if (result != NO_ERROR) {
        // Allocate failed, that sucks
        ALOGW("Failed to allocate scratch buffer, error=%d", result);
        return nullptr;
    }

    mScratchBuffer.reset(newBuffer);
    return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer);
}

int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window,
                                        ANativeWindow_dequeueBufferFn dequeueBuffer, void* data,
                                        ANativeWindowBuffer** buffer, int* fenceFd) {
    ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data);
    {
        std::lock_guard _lock{rs->mMutex};
        if (rs->mReservedBuffer) {
            *buffer = rs->mReservedBuffer;
            *fenceFd = rs->mReservedFenceFd.release();
            rs->mReservedBuffer = nullptr;
            return OK;
        }
    }

    int result = dequeueBuffer(window, buffer, fenceFd);
    if (result != OK) {
        ALOGW("dequeueBuffer failed, error = %d; switching to fallback", result);
        *buffer = rs->acquireFallbackBuffer(result);
        *fenceFd = -1;
        return *buffer ? OK : INVALID_OPERATION;
    } else {
        std::lock_guard _lock{rs->mMutex};
        rs->mHasDequeuedBuffer = true;
    }
    return OK;
}

int ReliableSurface::hook_cancelBuffer(ANativeWindow* window,
                                       ANativeWindow_cancelBufferFn cancelBuffer, void* data,
                                       ANativeWindowBuffer* buffer, int fenceFd) {
    ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data);
    rs->clearReservedBuffer();
    if (rs->isFallbackBuffer(buffer)) {
        if (fenceFd > 0) {
            close(fenceFd);
        }
        return OK;
    }
    return cancelBuffer(window, buffer, fenceFd);
}

int ReliableSurface::hook_queueBuffer(ANativeWindow* window,
                                      ANativeWindow_queueBufferFn queueBuffer, void* data,
                                      ANativeWindowBuffer* buffer, int fenceFd) {
    ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data);
    rs->clearReservedBuffer();

    if (rs->isFallbackBuffer(buffer)) {
        if (fenceFd > 0) {
            close(fenceFd);
        }
        return OK;
    }

    return queueBuffer(window, buffer, fenceFd);
}

int ReliableSurface::hook_perform(ANativeWindow* window, ANativeWindow_performFn perform,
                                  void* data, int operation, va_list args) {
    // Drop the reserved buffer if there is one since this (probably) mutated buffer dimensions
    // TODO: Filter to things that only affect the reserved buffer
    // TODO: Can we mutate the reserved buffer in some cases?
    ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data);
    rs->clearReservedBuffer();

    va_list argsCopy;
    va_copy(argsCopy, args);
    int result = perform(window, operation, argsCopy);

    {
        std::lock_guard _lock{rs->mMutex};

        switch (operation) {
            case ANATIVEWINDOW_PERFORM_SET_USAGE:
                rs->mUsage = va_arg(args, uint32_t);
                break;
            case ANATIVEWINDOW_PERFORM_SET_USAGE64:
                rs->mUsage = va_arg(args, uint64_t);
                break;
            case ANATIVEWINDOW_PERFORM_SET_BUFFERS_GEOMETRY:
                /* width */ va_arg(args, uint32_t);
                /* height */ va_arg(args, uint32_t);
                rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
                break;
            case ANATIVEWINDOW_PERFORM_SET_BUFFERS_FORMAT:
                rs->mFormat = static_cast<AHardwareBuffer_Format>(va_arg(args, int32_t));
                break;
            case NATIVE_WINDOW_SET_BUFFER_COUNT:
                size_t bufferCount = va_arg(args, size_t);
                if (bufferCount >= rs->mExpectedBufferCount) {
                    rs->mDidSetExtraBuffers = true;
                } else {
                    ALOGD("HOOK FAILED! Expected %zd got = %zd", rs->mExpectedBufferCount, bufferCount);
                }
                break;
        }
    }
    return result;
}

int ReliableSurface::hook_query(const ANativeWindow *window, ANativeWindow_queryFn query,
        void *data, int what, int *value) {
    ReliableSurface* rs = reinterpret_cast<ReliableSurface*>(data);
    int result = query(window, what, value);
    if (what == ANATIVEWINDOW_QUERY_MIN_UNDEQUEUED_BUFFERS && result == OK) {
        std::lock_guard _lock{rs->mMutex};
        rs->mExpectedBufferCount = *value + 2;
    }
    return result;
}

};  // namespace android::uirenderer::renderthread