// Copyright (C) 2016 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 "RendererImpl.h"

#include <assert.h>

#include <algorithm>
#include <utility>
#include <variant>

#include "FrameBuffer.h"
#include "RenderChannelImpl.h"
#include "RenderThread.h"
#include "aemu/base/system/System.h"
#include "aemu/base/threads/WorkerThread.h"
#include "gl/EmulatedEglFenceSync.h"
#include "host-common/logging.h"
#include "snapshot/common.h"

namespace gfxstream {

// kUseSubwindowThread is used to determine whether the RenderWindow should use
// a separate thread to manage its subwindow GL/GLES context.
// For now, this feature is disabled entirely for the following
// reasons:
//
// - It must be disabled on Windows at all times, otherwise the main window
//   becomes unresponsive after a few seconds of user interaction (e.g. trying
//   to move it over the desktop). Probably due to the subtle issues around
//   input on this platform (input-queue is global, message-queue is
//   per-thread). Also, this messes considerably the display of the
//   main window when running the executable under Wine.
//
// - On Linux/XGL and OSX/Cocoa, this used to be necessary to avoid corruption
//   issues with the GL state of the main window when using the SDL UI.
//   After the switch to Qt, this is no longer necessary and may actually cause
//   undesired interactions between the UI thread and the RenderWindow thread:
//   for example, in a multi-monitor setup the context might be recreated when
//   dragging the window between monitors, triggering a Qt-specific callback
//   in the context of RenderWindow thread, which will become blocked on the UI
//   thread, which may in turn be blocked on something else.
static const bool kUseSubwindowThread = false;

// This object manages the cleanup of guest process resources when the process
// exits. It runs the cleanup in a separate thread to never block the main
// render thread for a low-priority task.
class RendererImpl::ProcessCleanupThread {
public:
    ProcessCleanupThread()
        : mCleanupWorker([](Cmd cmd) {
            using android::base::WorkerProcessingResult;
            struct {
                WorkerProcessingResult operator()(CleanProcessResources resources) {
                    FrameBuffer::getFB()->cleanupProcGLObjects(resources.puid);
                    // resources.resource are destroyed automatically when going out of the scope.
                    return WorkerProcessingResult::Continue;
                }
                WorkerProcessingResult operator()(Exit) {
                    return WorkerProcessingResult::Stop;
                }
            } visitor;
            return std::visit(visitor, std::move(cmd));
          }) {
        mCleanupWorker.start();
    }

    ~ProcessCleanupThread() {
        mCleanupWorker.enqueue(Exit{});
    }

    void cleanup(uint64_t processId, std::unique_ptr<ProcessResources> resource) {
        mCleanupWorker.enqueue(CleanProcessResources{
            .puid = processId,
            .resource = std::move(resource),
        });
    }

    void stop() {
        mCleanupWorker.enqueue(Exit{});
        mCleanupWorker.join();
    }

    void waitForCleanup() {
        mCleanupWorker.waitQueuedItems();
    }

private:
    struct CleanProcessResources {
        uint64_t puid;
        std::unique_ptr<ProcessResources> resource;
    };
    struct Exit {};
    using Cmd = std::variant<CleanProcessResources, Exit>;
    DISALLOW_COPY_AND_ASSIGN(ProcessCleanupThread);

    android::base::WorkerThread<Cmd> mCleanupWorker;
};

RendererImpl::RendererImpl() {
    mCleanupThread.reset(new ProcessCleanupThread());
}

RendererImpl::~RendererImpl() {
    stop(true);
    // We can't finish until the loader render thread has
    // completed else can get a crash at the end of the destructor.
    if (mLoaderRenderThread) {
        mLoaderRenderThread->wait();
    }
    mRenderWindow.reset();
}

bool RendererImpl::initialize(int width, int height, bool useSubWindow, bool egl2egl) {
    if (android::base::getEnvironmentVariable("ANDROID_EMUGL_VERBOSE") == "1") {
        // base_enable_verbose_logs();
    }

    if (mRenderWindow) {
        return false;
    }

    std::unique_ptr<RenderWindow> renderWindow(new RenderWindow(
            width, height, kUseSubwindowThread, useSubWindow, egl2egl));
    if (!renderWindow) {
        ERR("Could not create rendering window class\n");
        GL_LOG("Could not create rendering window class");
        return false;
    }
    if (!renderWindow->isValid()) {
        ERR("Could not initialize emulated framebuffer\n");
        return false;
    }

    mRenderWindow = std::move(renderWindow);
    GL_LOG("OpenGL renderer initialized successfully");

    // This render thread won't do anything but will only preload resources
    // for the real threads to start faster.
    mLoaderRenderThread.reset(new RenderThread(nullptr));
    mLoaderRenderThread->start();

    return true;
}

void RendererImpl::stop(bool wait) {
    android::base::AutoLock lock(mChannelsLock);
    mStopped = true;
    auto channels = std::move(mChannels);
    lock.unlock();

    if (const auto fb = FrameBuffer::getFB()) {
        fb->setShuttingDown();
    }
    for (const auto& c : channels) {
        c->stopFromHost();
    }
    // We're stopping the renderer, so there's no need to clean up resources
    // of some pending processes: we'll destroy everything soon.
    mCleanupThread->stop();

    mStoppedChannels.insert(mStoppedChannels.end(),
                            std::make_move_iterator(channels.begin()),
                            std::make_move_iterator(channels.end()));

    if (!wait) {
        return;
    }

    // Each render channel is referenced in the corresponing pipe object, so
    // even if we clear the |channels| vector they could still be alive
    // for a while. This means we need to make sure to wait for render thread
    // exit explicitly.
    for (const auto& c : mStoppedChannels) {
        c->renderThread()->wait();
    }
    mStoppedChannels.clear();
}

void RendererImpl::finish() {
    {
        android::base::AutoLock lock(mChannelsLock);
        mRenderWindow->setPaused(true);
    }
    cleanupRenderThreads();
    {
        android::base::AutoLock lock(mChannelsLock);
        mRenderWindow->setPaused(false);
    }
}

void RendererImpl::cleanupRenderThreads() {
    android::base::AutoLock lock(mChannelsLock);
    const auto channels = std::move(mChannels);
    assert(mChannels.empty());
    lock.unlock();
    for (const auto& c : channels) {
        // Please DO NOT notify the guest about this event (DO NOT call
        // stopFromHost() ), because this is used to kill old threads when
        // loading from a snapshot, and the newly loaded guest should not
        // be notified for those behavior.
        c->stop();
    }
    for (const auto& c : channels) {
        c->renderThread()->wait();
    }
}

void RendererImpl::waitForProcessCleanup() {
    mCleanupThread->waitForCleanup();
    // Recreate it to make sure we've started from scratch and that we've
    // finished all in-progress cleanups as well.
    mCleanupThread.reset(new ProcessCleanupThread());
}

RenderChannelPtr RendererImpl::createRenderChannel(
        android::base::Stream* loadStream) {
    const auto channel = std::make_shared<RenderChannelImpl>(loadStream);
    {
        android::base::AutoLock lock(mChannelsLock);

        if (mStopped) {
            return nullptr;
        }

        // Clean up the stopped channels.
        mChannels.erase(
                std::remove_if(mChannels.begin(), mChannels.end(),
                               [](const std::shared_ptr<RenderChannelImpl>& c) {
                                   return c->renderThread()->isFinished();
                               }),
                mChannels.end());
        mChannels.emplace_back(channel);

        // Take the time to check if our loader thread is done as well.
        if (mLoaderRenderThread && mLoaderRenderThread->isFinished()) {
            mLoaderRenderThread->wait();
            mLoaderRenderThread.reset();
        }

        GL_LOG("Started new RenderThread (total %" PRIu64 ") @%p",
               static_cast<uint64_t>(mChannels.size()), channel->renderThread());
    }

    return channel;
}

void RendererImpl::addListener(FrameBufferChangeEventListener* listener) {
    mRenderWindow->addListener(listener);
}

void RendererImpl::removeListener(FrameBufferChangeEventListener* listener) {
    mRenderWindow->removeListener(listener);
}

void* RendererImpl::addressSpaceGraphicsConsumerCreate(
    struct asg_context context,
    android::base::Stream* loadStream,
    android::emulation::asg::ConsumerCallbacks callbacks,
    uint32_t contextId, uint32_t capsetId,
    std::optional<std::string> nameOpt) {
    auto thread = new RenderThread(context, loadStream, callbacks, contextId,
                                   capsetId, std::move(nameOpt));
    thread->start();
    return (void*)thread;
}

void RendererImpl::addressSpaceGraphicsConsumerDestroy(void* consumer) {
    RenderThread* thread = (RenderThread*)consumer;
    thread->wait();
    delete thread;
}

void RendererImpl::addressSpaceGraphicsConsumerPreSave(void* consumer) {
    RenderThread* thread = (RenderThread*)consumer;
    thread->pausePreSnapshot();
}

void RendererImpl::addressSpaceGraphicsConsumerSave(void* consumer, android::base::Stream* stream) {
    RenderThread* thread = (RenderThread*)consumer;
    thread->save(stream);
}

void RendererImpl::addressSpaceGraphicsConsumerPostSave(void* consumer) {
    RenderThread* thread = (RenderThread*)consumer;
    thread->resume();
}

void RendererImpl::addressSpaceGraphicsConsumerRegisterPostLoadRenderThread(void* consumer) {
    RenderThread* thread = (RenderThread*)consumer;
    mAdditionalPostLoadRenderThreads.push_back(thread);
}

void RendererImpl::pauseAllPreSave() {
    android::base::AutoLock lock(mChannelsLock);
    if (mStopped) {
        return;
    }
    for (const auto& c : mChannels) {
        c->renderThread()->pausePreSnapshot();
    }
    lock.unlock();
    waitForProcessCleanup();
}

void RendererImpl::resumeAll() {
    {
        android::base::AutoLock lock(mChannelsLock);
        if (mStopped) {
            return;
        }
        for (const auto& c : mChannels) {
            c->renderThread()->resume();
        }

        for (const auto t: mAdditionalPostLoadRenderThreads) {
            t->resume();
        }
        mAdditionalPostLoadRenderThreads.clear();
    }

    repaintOpenGLDisplay();
}

void RendererImpl::save(android::base::Stream* stream,
                        const android::snapshot::ITextureSaverPtr& textureSaver) {
    stream->putByte(mStopped);
    if (mStopped) {
        return;
    }
    auto fb = FrameBuffer::getFB();
    assert(fb);
    fb->onSave(stream, textureSaver);
}

bool RendererImpl::load(android::base::Stream* stream,
                        const android::snapshot::ITextureLoaderPtr& textureLoader) {

#ifdef SNAPSHOT_PROFILE
    android::base::System::Duration startTime =
            android::base::System::get()->getUnixTimeUs();
#endif
    waitForProcessCleanup();
#ifdef SNAPSHOT_PROFILE
    printf("Previous session cleanup time: %lld ms\n",
           (long long)(android::base::System::get()
                               ->getUnixTimeUs() -
                       startTime) /
                   1000);
#endif

    mStopped = stream->getByte();
    if (mStopped) {
        return true;
    }
    auto fb = FrameBuffer::getFB();
    assert(fb);

    bool res = true;

    res = fb->onLoad(stream, textureLoader);
    gl::EmulatedEglFenceSync::onLoad(stream);

    return res;
}

void RendererImpl::fillGLESUsages(android_studio::EmulatorGLESUsages* usages) {
    auto fb = FrameBuffer::getFB();
    if (fb) fb->fillGLESUsages(usages);
}

int RendererImpl::getScreenshot(unsigned int nChannels, unsigned int* width, unsigned int* height,
                                uint8_t* pixels, size_t* cPixels, int displayId = 0,
                                int desiredWidth = 0, int desiredHeight = 0,
                                int desiredRotation = 0, Rect rect = {{0, 0}, {0, 0}}) {
    auto fb = FrameBuffer::getFB();
    if (fb) {
        return fb->getScreenshot(nChannels, width, height, pixels, cPixels,
                                 displayId, desiredWidth, desiredHeight,
                                 desiredRotation, rect);
    }
    *cPixels = 0;
    return -1;
}

void RendererImpl::setMultiDisplay(uint32_t id,
                                   int32_t x,
                                   int32_t y,
                                   uint32_t w,
                                   uint32_t h,
                                   uint32_t dpi,
                                   bool add) {
    auto fb = FrameBuffer::getFB();
    if (fb) {
        if (add) {
            fb->createDisplay(&id);
            fb->setDisplayPose(id, x, y, w, h, dpi);
        } else {
            fb->destroyDisplay(id);
        }
    }
}

void RendererImpl::setMultiDisplayColorBuffer(uint32_t id, uint32_t cb) {
    auto fb = FrameBuffer::getFB();
    if (fb) {
        fb->setDisplayColorBuffer(id, cb);
    }
}

RendererImpl::HardwareStrings RendererImpl::getHardwareStrings() {
    assert(mRenderWindow);

    const char* vendor = nullptr;
    const char* renderer = nullptr;
    const char* version = nullptr;
    if (!mRenderWindow->getHardwareStrings(&vendor, &renderer, &version)) {
        return {};
    }
    HardwareStrings res;
    res.vendor = vendor ? vendor : "";
    res.renderer = renderer ? renderer : "";
    res.version = version ? version : "";
    return res;
}

void RendererImpl::setPostCallback(RendererImpl::OnPostCallback onPost,
                                   void* context,
                                   bool useBgraReadback,
                                   uint32_t displayId) {
    assert(mRenderWindow);
    mRenderWindow->setPostCallback(onPost, context, displayId, useBgraReadback);
}

bool RendererImpl::asyncReadbackSupported() {
    assert(mRenderWindow);
    return mRenderWindow->asyncReadbackSupported();
}

RendererImpl::ReadPixelsCallback
RendererImpl::getReadPixelsCallback() {
    assert(mRenderWindow);
    return mRenderWindow->getReadPixelsCallback();
}

RendererImpl::FlushReadPixelPipeline
RendererImpl::getFlushReadPixelPipeline() {
    assert(mRenderWindow);
    return mRenderWindow->getFlushReadPixelPipeline();
}

bool RendererImpl::showOpenGLSubwindow(FBNativeWindowType window,
                                       int wx,
                                       int wy,
                                       int ww,
                                       int wh,
                                       int fbw,
                                       int fbh,
                                       float dpr,
                                       float zRot,
                                       bool deleteExisting,
                                       bool hideWindow) {
    assert(mRenderWindow);
    return mRenderWindow->setupSubWindow(window, wx, wy, ww, wh, fbw, fbh, dpr,
                                         zRot, deleteExisting, hideWindow);
}

bool RendererImpl::destroyOpenGLSubwindow() {
    assert(mRenderWindow);
    return mRenderWindow->removeSubWindow();
}

void RendererImpl::setOpenGLDisplayRotation(float zRot) {
    assert(mRenderWindow);
    mRenderWindow->setRotation(zRot);
}

void RendererImpl::setOpenGLDisplayTranslation(float px, float py) {
    assert(mRenderWindow);
    mRenderWindow->setTranslation(px, py);
}

void RendererImpl::repaintOpenGLDisplay() {
    assert(mRenderWindow);
    mRenderWindow->repaint();
}

bool RendererImpl::hasGuestPostedAFrame() {
    if (mRenderWindow) {
        return mRenderWindow->hasGuestPostedAFrame();
    }
    return false;
}

void RendererImpl::resetGuestPostedAFrame() {
    if (mRenderWindow) {
        mRenderWindow->resetGuestPostedAFrame();
    }
}

void RendererImpl::setScreenMask(int width, int height, const unsigned char* rgbaData) {
    assert(mRenderWindow);
    mRenderWindow->setScreenMask(width, height, rgbaData);
}

void RendererImpl::onGuestGraphicsProcessCreate(uint64_t puid) {
    FrameBuffer::getFB()->createGraphicsProcessResources(puid);
}

void RendererImpl::cleanupProcGLObjects(uint64_t puid) {
    std::unique_ptr<ProcessResources> resource =
        FrameBuffer::getFB()->removeGraphicsProcessResources(puid);
    mCleanupThread->cleanup(puid, std::move(resource));
}

static struct AndroidVirtioGpuOps sVirtioGpuOps = {
    .create_buffer_with_handle =
        [](uint64_t size, uint32_t handle) {
            FrameBuffer::getFB()->createBufferWithHandle(size, handle);
        },
    .create_color_buffer_with_handle =
        [](uint32_t width, uint32_t height, uint32_t format, uint32_t fwkFormat, uint32_t handle) {
            FrameBuffer::getFB()->createColorBufferWithHandle(width, height, (GLenum)format,
                                                              (FrameworkFormat)fwkFormat, handle);
        },
    .open_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->openColorBuffer(handle); },
    .close_buffer = [](uint32_t handle) { FrameBuffer::getFB()->closeBuffer(handle); },
    .close_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->closeColorBuffer(handle); },
    .update_buffer =
        [](uint32_t handle, uint64_t offset, uint64_t size, void* bytes) {
            FrameBuffer::getFB()->updateBuffer(handle, offset, size, bytes);
        },
    .update_color_buffer =
        [](uint32_t handle, int x, int y, int width, int height, uint32_t format, uint32_t type,
           void* pixels) {
            FrameBuffer::getFB()->updateColorBuffer(handle, x, y, width, height, format, type,
                                                    pixels);
        },
    .read_buffer =
        [](uint32_t handle, uint64_t offset, uint64_t size, void* bytes) {
            FrameBuffer::getFB()->readBuffer(handle, offset, size, bytes);
        },
    .read_color_buffer =
        [](uint32_t handle, int x, int y, int width, int height, uint32_t format, uint32_t type,
           void* pixels) {
            FrameBuffer::getFB()->readColorBuffer(handle, x, y, width, height, format, type,
                                                  pixels);
        },
    .read_color_buffer_yuv =
        [](uint32_t handle, int x, int y, int width, int height, void* pixels,
           uint32_t pixels_size) {
            FrameBuffer::getFB()->readColorBufferYUV(handle, x, y, width, height, pixels,
                                                     pixels_size);
        },
    .post_color_buffer = [](uint32_t handle) { FrameBuffer::getFB()->post(handle); },
    .async_post_color_buffer =
        [](uint32_t handle, CpuCompletionCallback cb) {
            FrameBuffer::getFB()->postWithCallback(handle, cb);
        },
    .repost = []() { FrameBuffer::getFB()->repost(); },
    .create_yuv_textures =
        [](uint32_t type, uint32_t count, int width, int height, uint32_t* output) {
            FrameBuffer::getFB()->createYUVTextures(type, count, width, height, output);
        },
    .destroy_yuv_textures =
        [](uint32_t type, uint32_t count, uint32_t* textures) {
            FrameBuffer::getFB()->destroyYUVTextures(type, count, textures);
        },
    .update_yuv_textures =
        [](uint32_t type, uint32_t* textures, void* privData, void* func) {
            FrameBuffer::getFB()->updateYUVTextures(type, textures, privData, func);
        },
    .swap_textures_and_update_color_buffer =
        [](uint32_t colorbufferhandle, int x, int y, int width, int height, uint32_t format,
           uint32_t type, uint32_t texture_type, uint32_t* textures, void* metadata) {
            FrameBuffer::getFB()->swapTexturesAndUpdateColorBuffer(
                colorbufferhandle, x, y, width, height, format, type, texture_type, textures);
        },
    .get_last_posted_color_buffer =
        []() { return FrameBuffer::getFB()->getLastPostedColorBuffer(); },
    .bind_color_buffer_to_texture =
        [](uint32_t handle) { FrameBuffer::getFB()->bindColorBufferToTexture2(handle); },
    .get_global_egl_context = []() { return FrameBuffer::getFB()->getGlobalEGLContext(); },
    .wait_for_gpu = [](uint64_t eglsync) { FrameBuffer::getFB()->waitForGpu(eglsync); },
    .wait_for_gpu_vulkan =
        [](uint64_t device, uint64_t fence) {
            FrameBuffer::getFB()->waitForGpuVulkan(device, fence);
        },
    .set_guest_managed_color_buffer_lifetime =
        [](bool guestManaged) {
            FrameBuffer::getFB()->setGuestManagedColorBufferLifetime(guestManaged);
        },
    .async_wait_for_gpu_with_cb =
        [](uint64_t eglsync, FenceCompletionCallback cb) {
            FrameBuffer::getFB()->asyncWaitForGpuWithCb(eglsync, cb);
        },
    .async_wait_for_gpu_vulkan_with_cb =
        [](uint64_t device, uint64_t fence, FenceCompletionCallback cb) {
            FrameBuffer::getFB()->asyncWaitForGpuVulkanWithCb(device, fence, cb);
        },
    .async_wait_for_gpu_vulkan_qsri_with_cb =
        [](uint64_t image, FenceCompletionCallback cb) {
            FrameBuffer::getFB()->asyncWaitForGpuVulkanQsriWithCb(image, cb);
        },
    .wait_for_gpu_vulkan_qsri =
        [](uint64_t image) { FrameBuffer::getFB()->waitForGpuVulkanQsri(image); },
    .update_color_buffer_from_framework_format =
        [](uint32_t handle, int x, int y, int width, int height, uint32_t fwkFormat,
           uint32_t format, uint32_t type, void* pixels, void* pMetadata) {
            FrameBuffer::getFB()->updateColorBufferFromFrameworkFormat(
                handle, x, y, width, height, (FrameworkFormat)fwkFormat, format, type, pixels,
                pMetadata);
        },
    .platform_import_resource =
        [](uint32_t handle, uint32_t info, void* resource) {
            return FrameBuffer::getFB()->platformImportResource(handle, info, resource);
        },
    .platform_resource_info =
        [](uint32_t handle, int32_t* width, int32_t* height, int32_t* internal_format) {
            return FrameBuffer::getFB()->getColorBufferInfo(handle, width, height, internal_format);
        },
    .platform_create_shared_egl_context =
        []() { return FrameBuffer::getFB()->platformCreateSharedEglContext(); },
    .platform_destroy_shared_egl_context =
        [](void* context) {
            return FrameBuffer::getFB()->platformDestroySharedEglContext(context);
        },
};

struct AndroidVirtioGpuOps* RendererImpl::getVirtioGpuOps() {
    return &sVirtioGpuOps;
}

void RendererImpl::snapshotOperationCallback(int op, int stage) {
    using namespace android::snapshot;
    switch (op) {
        case SNAPSHOTTER_OPERATION_LOAD:
            if (stage == SNAPSHOTTER_STAGE_START) {
#ifdef SNAPSHOT_PROFILE
             android::base::System::Duration startTime =
                     android::base::System::get()->getUnixTimeUs();
#endif
                mRenderWindow->setPaused(true);
                cleanupRenderThreads();
#ifdef SNAPSHOT_PROFILE
                printf("Previous session suspend time: %lld ms\n",
                       (long long)(android::base::System::get()
                                           ->getUnixTimeUs() -
                                   startTime) /
                               1000);
#endif
            }
            if (stage == SNAPSHOTTER_STAGE_END) {
                mRenderWindow->setPaused(false);
            }
            break;
        default:
            break;
    }
}

void RendererImpl::setVsyncHz(int vsyncHz) {
    if (mRenderWindow) {
        mRenderWindow->setVsyncHz(vsyncHz);
    }
}

void RendererImpl::setDisplayConfigs(int configId, int w, int h,
                                     int dpiX, int dpiY) {
    if (mRenderWindow) {
        mRenderWindow->setDisplayConfigs(configId, w, h, dpiX, dpiY);
    }
}

void RendererImpl::setDisplayActiveConfig(int configId) {
    if (mRenderWindow) {
        mRenderWindow->setDisplayActiveConfig(configId);
    }
}

const void* RendererImpl::getEglDispatch() {
    return FrameBuffer::getFB()->getEglDispatch();
}

const void* RendererImpl::getGles2Dispatch() {
    return FrameBuffer::getFB()->getGles2Dispatch();
}

}  // namespace gfxstream
