/*
 * 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 <private/android/AHardwareBufferHelpers.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;

// TODO: Make surface less protected
// This exists because perform is a varargs, and ANativeWindow has no va_list perform.
// So wrapping/chaining that is hard. Telling the compiler to ignore protected is easy, so we do
// that instead
struct SurfaceExposer : Surface {
    // Make warnings happy
    SurfaceExposer() = delete;

    using Surface::cancelBuffer;
    using Surface::dequeueBuffer;
    using Surface::lockBuffer_DEPRECATED;
    using Surface::perform;
    using Surface::queueBuffer;
    using Surface::setBufferCount;
    using Surface::setSwapInterval;
};

#define callProtected(surface, func, ...) ((*surface).*&SurfaceExposer::func)(__VA_ARGS__)

ReliableSurface::ReliableSurface(sp<Surface>&& surface) : mSurface(std::move(surface)) {
    LOG_ALWAYS_FATAL_IF(!mSurface, "Error, unable to wrap a nullptr");

    ANativeWindow::setSwapInterval = hook_setSwapInterval;
    ANativeWindow::dequeueBuffer = hook_dequeueBuffer;
    ANativeWindow::cancelBuffer = hook_cancelBuffer;
    ANativeWindow::queueBuffer = hook_queueBuffer;
    ANativeWindow::query = hook_query;
    ANativeWindow::perform = hook_perform;

    ANativeWindow::dequeueBuffer_DEPRECATED = hook_dequeueBuffer_DEPRECATED;
    ANativeWindow::cancelBuffer_DEPRECATED = hook_cancelBuffer_DEPRECATED;
    ANativeWindow::lockBuffer_DEPRECATED = hook_lockBuffer_DEPRECATED;
    ANativeWindow::queueBuffer_DEPRECATED = hook_queueBuffer_DEPRECATED;
}

ReliableSurface::~ReliableSurface() {
    clearReservedBuffer();
}

void ReliableSurface::perform(int operation, va_list args) {
    std::lock_guard _lock{mMutex};

    switch (operation) {
        case NATIVE_WINDOW_SET_USAGE:
            mUsage = va_arg(args, uint32_t);
            break;
        case NATIVE_WINDOW_SET_USAGE64:
            mUsage = va_arg(args, uint64_t);
            break;
        case NATIVE_WINDOW_SET_BUFFERS_GEOMETRY:
            /* width */ va_arg(args, uint32_t);
            /* height */ va_arg(args, uint32_t);
            mFormat = va_arg(args, PixelFormat);
            break;
        case NATIVE_WINDOW_SET_BUFFERS_FORMAT:
            mFormat = va_arg(args, PixelFormat);
            break;
    }
}

int ReliableSurface::reserveNext() {
    {
        std::lock_guard _lock{mMutex};
        if (mReservedBuffer) {
            ALOGW("reserveNext called but there was already a buffer reserved?");
            return OK;
        }
        if (mInErrorState) {
            return UNKNOWN_ERROR;
        }
        if (mHasDequeuedBuffer) {
            return OK;
        }
        if constexpr (DISABLE_BUFFER_PREFETCH) {
            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;
    int result = callProtected(mSurface, dequeueBuffer, &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) {
        callProtected(mSurface, cancelBuffer, buffer, releaseFd);
    }
}

int ReliableSurface::cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd) {
    clearReservedBuffer();
    if (isFallbackBuffer(buffer)) {
        if (fenceFd > 0) {
            close(fenceFd);
        }
        return OK;
    }
    int result = callProtected(mSurface, cancelBuffer, buffer, fenceFd);
    return result;
}

int ReliableSurface::dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd) {
    {
        std::lock_guard _lock{mMutex};
        if (mReservedBuffer) {
            *buffer = mReservedBuffer;
            *fenceFd = mReservedFenceFd.release();
            mReservedBuffer = nullptr;
            return OK;
        }
    }

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

int ReliableSurface::queueBuffer(ANativeWindowBuffer* buffer, int fenceFd) {
    clearReservedBuffer();

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

    int result = callProtected(mSurface, queueBuffer, buffer, fenceFd);
    return result;
}

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() {
    std::lock_guard _lock{mMutex};
    mInErrorState = true;

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

    AHardwareBuffer_Desc desc;
    desc.usage = mUsage;
    desc.format = mFormat;
    desc.width = 1;
    desc.height = 1;
    desc.layers = 1;
    desc.rfu0 = 0;
    desc.rfu1 = 0;
    AHardwareBuffer* newBuffer = nullptr;
    int err = AHardwareBuffer_allocate(&desc, &newBuffer);
    if (err) {
        // Allocate failed, that sucks
        ALOGW("Failed to allocate scratch buffer, error=%d", err);
        return nullptr;
    }
    mScratchBuffer.reset(newBuffer);
    return AHardwareBuffer_to_ANativeWindowBuffer(newBuffer);
}

Surface* ReliableSurface::getWrapped(const ANativeWindow* window) {
    return getSelf(window)->mSurface.get();
}

int ReliableSurface::hook_setSwapInterval(ANativeWindow* window, int interval) {
    return callProtected(getWrapped(window), setSwapInterval, interval);
}

int ReliableSurface::hook_dequeueBuffer(ANativeWindow* window, ANativeWindowBuffer** buffer,
                                        int* fenceFd) {
    return getSelf(window)->dequeueBuffer(buffer, fenceFd);
}

int ReliableSurface::hook_cancelBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
                                       int fenceFd) {
    return getSelf(window)->cancelBuffer(buffer, fenceFd);
}

int ReliableSurface::hook_queueBuffer(ANativeWindow* window, ANativeWindowBuffer* buffer,
                                      int fenceFd) {
    return getSelf(window)->queueBuffer(buffer, fenceFd);
}

int ReliableSurface::hook_dequeueBuffer_DEPRECATED(ANativeWindow* window,
                                                   ANativeWindowBuffer** buffer) {
    ANativeWindowBuffer* buf;
    int fenceFd = -1;
    int result = window->dequeueBuffer(window, &buf, &fenceFd);
    if (result != OK) {
        return result;
    }
    sp<Fence> fence(new Fence(fenceFd));
    int waitResult = fence->waitForever("dequeueBuffer_DEPRECATED");
    if (waitResult != OK) {
        ALOGE("dequeueBuffer_DEPRECATED: Fence::wait returned an error: %d", waitResult);
        window->cancelBuffer(window, buf, -1);
        return waitResult;
    }
    *buffer = buf;
    return result;
}

int ReliableSurface::hook_cancelBuffer_DEPRECATED(ANativeWindow* window,
                                                  ANativeWindowBuffer* buffer) {
    return window->cancelBuffer(window, buffer, -1);
}

int ReliableSurface::hook_lockBuffer_DEPRECATED(ANativeWindow* window,
                                                ANativeWindowBuffer* buffer) {
    // This method is a no-op in Surface as well
    return OK;
}

int ReliableSurface::hook_queueBuffer_DEPRECATED(ANativeWindow* window,
                                                 ANativeWindowBuffer* buffer) {
    return window->queueBuffer(window, buffer, -1);
}

int ReliableSurface::hook_query(const ANativeWindow* window, int what, int* value) {
    return getWrapped(window)->query(what, value);
}

int ReliableSurface::hook_perform(ANativeWindow* window, int operation, ...) {
    // 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?
    getSelf(window)->clearReservedBuffer();
    va_list args;
    va_start(args, operation);
    int result = callProtected(getWrapped(window), perform, operation, args);
    va_end(args);

    va_start(args, operation);
    getSelf(window)->perform(operation, args);
    va_end(args);

    return result;
}

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