// Copyright 2020 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 "host-common/opengl/OpenglEsPipe.h"

#include "aemu/base/Optional.h"
#include "aemu/base/files/PathUtils.h"
#include "aemu/base/files/StreamSerializing.h"
#include "aemu/base/threads/FunctorThread.h"
#include "aemu/base/system/System.h"
#include "host-common/globals.h"
// #include "loadpng.h"
#include "host-common/opengl/GLProcessPipe.h"
#include "host-common/opengles-pipe.h"
#include "host-common/opengles.h"
// #include "snapshot/Loader.h"
// #include "snapshot/Saver.h"
// #include "snapshot/Snapshotter.h"

#include <atomic>

#include <assert.h>
#include <stdlib.h>
#include <string.h>

// Set to 1 or 2 for debug traces
#define DEBUG 0

#if DEBUG >= 1
#define D(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
#else
#define D(...) ((void)0)
#endif

#if DEBUG >= 2
#define DD(...) printf(__VA_ARGS__), printf("\n"), fflush(stdout)
#else
#define DD(...) ((void)0)
#endif

using ChannelBuffer = gfxstream::RenderChannel::Buffer;
using gfxstream::RenderChannel;
using gfxstream::RenderChannelPtr;
using ChannelState = gfxstream::RenderChannel::State;
using IoResult = gfxstream::RenderChannel::IoResult;
// using android::base::Stopwatch;
// using android::snapshot::Snapshotter;

#define OPENGL_SAVE_VERSION 1

namespace android {
namespace opengl {

// TODO (b/138549350): See if Android/Fuchsia pipe protocols can be unified
// to give the best performance in each guest OS.
enum class RecvMode {
    Android = 0,
    Fuchsia = 1,
    VirtioGpu = 2,
};

static RecvMode recvMode = RecvMode::Android;

namespace {

class EmuglPipe : public AndroidPipe {
public:

    //////////////////////////////////////////////////////////////////////////
    // The pipe service class for this implementation.
    class Service : public AndroidPipe::Service {
    public:
        Service() : AndroidPipe::Service("opengles") {}

        // Create a new EmuglPipe instance.
        AndroidPipe* create(void* hwPipe, const char* args, AndroidPipeFlags flags) override {
            return createPipe(hwPipe, this, args, flags);
        }

        bool canLoad() const override { return true; }

        virtual void preLoad(android::base::Stream* stream) override {
// #ifdef SNAPSHOT_PROFILE
//             mLoadMeter.restartUs();
// #endif
//             const bool hasRenderer = stream->getByte();
//             const auto& renderer = android_getOpenglesRenderer();
//             if (hasRenderer != (bool)renderer) {
//                 // die?
//                 return;
//             }
//             if (!hasRenderer) {
//                 return;
//             }
//             int version = stream->getBe32();
//             (void)version;
//             renderer->load(stream, Snapshotter::get().loader().textureLoader());
// #ifdef SNAPSHOT_PROFILE
//             printf("OpenglEs preload time: %lld ms\n",
//                    (long long)(mLoadMeter.elapsedUs() / 1000));
// #endif
        }

        void postLoad(android::base::Stream* stream) override {
            if (const auto& renderer = android_getOpenglesRenderer()) {
                renderer->resumeAll();
            }
#ifdef SNAPSHOT_PROFILE
            printf("OpenglEs total load time: %lld ms\n",
                   (long long)(mLoadMeter.elapsedUs() / 1000));
#endif
        }

        void preSave(android::base::Stream* stream) override {
            // #ifdef SNAPSHOT_PROFILE
            //             mSaveMeter.restartUs();
            // #endif
            //             if (const auto& renderer = android_getOpenglesRenderer()) {
            //                 renderer->pauseAllPreSave();
            //                 stream->putByte(1);
            //                 stream->putBe32(OPENGL_SAVE_VERSION);
            //                 renderer->save(stream,
            //                                Snapshotter::get().saver().textureSaver());
            //
            //                 writeScreenshot(*renderer);
            //             } else {
            //                 stream->putByte(0);
            //             }
        }

        void postSave(android::base::Stream* stream) override {
            if (const auto& renderer = android_getOpenglesRenderer()) {
                renderer->resumeAll();
            }
#ifdef SNAPSHOT_PROFILE
            printf("OpenglEs total save time: %lld ms\n",
                   (long long)(mSaveMeter.elapsedUs() / 1000));
#endif
        }

        virtual AndroidPipe* load(void* hwPipe,
                                  const char* args,
                                  android::base::Stream* stream) override {
            return createPipe(hwPipe, this, args, ANDROID_PIPE_DEFAULT, stream);
        }

    private:
        static AndroidPipe* createPipe(
                void* hwPipe,
                Service* service,
                const char* args,
                AndroidPipeFlags flags,
                android::base::Stream* loadStream = nullptr) {
            const auto& renderer = android_getOpenglesRenderer();
            if (!renderer) {
                // This should never happen, unless there is a bug in the
                // emulator's initialization, or the system image, or we're
                // loading from an incompatible snapshot.
                D("Trying to open the OpenGLES pipe without GPU emulation!");
                return nullptr;
            }

            auto pipe = new EmuglPipe(hwPipe, service, renderer, flags, loadStream);
            if (!pipe->mIsWorking) {
                delete pipe;
                pipe = nullptr;
            }
            return pipe;
        }

        void writeScreenshot(gfxstream::Renderer& renderer) {
            // #if SNAPSHOT_PROFILE > 1
            //             Stopwatch sw;
            // #endif
            //             if (!mSnapshotCallbackRegistered) {
            //                 // We have to wait for the screenshot saving thread, but
            //                 // there's no need to join it too soon: it is ok to only
            //                 // block when the rest of snapshot saving is complete.
            //                 // Snapshotter::get().addOperationCallback(
            //                 //         [this](Snapshotter::Operation op,
            //                 //                Snapshotter::Stage stage) {
            //                 //             if (op == Snapshotter::Operation::Save &&
            //                 //                 stage == Snapshotter::Stage::End) {
            //                 //                 if (mScreenshotSaver) {
            //                 //                     mScreenshotSaver->wait();
            //                 //                     mScreenshotSaver.clear();
            //                 //                 }
            //                 //             }
            //                 //         });
            //                 mSnapshotCallbackRegistered = true;
            //             }
            //             // always do 4 channel screenshot because swiftshader_indirect
            //             // has issues with 3 channels
            //             const unsigned int nChannels = 4;
            //             unsigned int width;
            //             unsigned int height;
            //             std::vector<unsigned char> pixels;
            //             renderer.getScreenshot(nChannels, &width, &height, pixels);
            // #if SNAPSHOT_PROFILE > 1
            //             printf("Screenshot load texture time %lld ms\n",
            //                    (long long)(sw.elapsedUs() / 1000));
            // #endif
            //             if (width > 0 && height > 0) {
            //                 std::string dataDir =
            //                         Snapshotter::get().saver().snapshot().dataDir();
            //                 mScreenshotSaver.emplace([nChannels, width, height,
            //                                           dataDir = std::move(dataDir),
            //                                           pixels = std::move(pixels)] {
            // #if SNAPSHOT_PROFILE > 1
            //                     Stopwatch sw;
            // #endif
            //                     std::string fileName = android::base::PathUtils::join(
            //                             dataDir, "screenshot.png");
            //                     // TODO: fix the screenshot rotation?
            //                     savepng(fileName.c_str(), nChannels, width, height,
            //                             SKIN_ROTATION_0,
            //                             const_cast<unsigned char*>(pixels.data()));
            // #if SNAPSHOT_PROFILE > 1
            //                     printf("Screenshot image write time %lld ms\n",
            //                            (long long)(sw.elapsedUs() / 1000));
            // #endif
            //                 });
            //                 mScreenshotSaver->start();
            //             }
        }

        base::Optional<base::FunctorThread> mScreenshotSaver;
#ifdef SNAPSHOT_PROFILE
        Stopwatch mSaveMeter;
        Stopwatch mLoadMeter;
#endif
    };

    /////////////////////////////////////////////////////////////////////////
    // Constructor, check that |mIsWorking| is true after this call to verify
    // that everything went well.
    EmuglPipe(void* hwPipe, Service* service, const gfxstream::RendererPtr& renderer,
              AndroidPipeFlags flags, android::base::Stream* loadStream = nullptr)
        : AndroidPipe(hwPipe, service) {
        bool isWorking = true;
        if (loadStream) {
            DD("%s: loading GLES pipe state for hwpipe=%p", __func__, mHwPipe);
            isWorking = (bool)loadStream->getBe32();
            android::base::loadBuffer(loadStream, &mDataForReading);
            mDataForReadingLeft = loadStream->getBe32();
        }

        uint32_t virtioGpuContextId = -1;
        if (flags & ANDROID_PIPE_VIRTIO_GPU_BIT) {
            virtioGpuContextId = (uint32_t)(uintptr_t)hwPipe;
        }

        mChannel = renderer->createRenderChannel(loadStream, virtioGpuContextId);
        if (!mChannel) {
            D("Failed to create an OpenGLES pipe channel!");
            return;
        }

        mIsWorking = isWorking;
        mChannel->setEventCallback([this](RenderChannel::State events) {
            onChannelHostEvent(events);
        });
    }

    //////////////////////////////////////////////////////////////////////////
    // Overriden AndroidPipe methods

    virtual void onSave(android::base::Stream* stream) override {
        DD("%s: saving GLES pipe state for hwpipe=%p", __FUNCTION__, mHwPipe);
        stream->putBe32(mIsWorking);
        android::base::saveBuffer(stream, mDataForReading);
        stream->putBe32(mDataForReadingLeft);

        mChannel->onSave(stream);
    }

    virtual void onGuestClose(PipeCloseReason reason) override {
        D("%s", __func__);
        mIsWorking = false;
        mChannel->stop();
        // Make sure there's no operation scheduled for this pipe instance to
        // run on the main thread.
        abortPendingOperation();
        delete this;
    }

    virtual unsigned onGuestPoll() const override {
        DD("%s", __func__);

        unsigned ret = 0;
        if (mDataForReadingLeft > 0) {
            ret |= PIPE_POLL_IN;
        }
        ChannelState state = mChannel->state();
        if ((state & ChannelState::CanRead) != 0) {
            ret |= PIPE_POLL_IN;
        }
        if ((state & ChannelState::CanWrite) != 0) {
            ret |= PIPE_POLL_OUT;
        }
        if ((state & ChannelState::Stopped) != 0) {
            ret |= PIPE_POLL_HUP;
        }
        DD("%s: returning %d", __func__, ret);
        return ret;
    }

    virtual int onGuestRecv(AndroidPipeBuffer* buffers,
                            int numBuffers) override {
        DD("%s", __func__);

        // Consume the pipe's dataForReading, then put the next received data
        // piece there. Repeat until the buffers are full or we're out of data
        // in the channel.
        int len = 0;
        size_t buffOffset = 0;

        auto buff = buffers;
        const auto buffEnd = buff + numBuffers;
        while (buff != buffEnd) {
            if (mDataForReadingLeft == 0) {
                if (android::opengl::recvMode == android::opengl::RecvMode::Android) {
                    // No data left, read a new chunk from the channel.
                    int spinCount = 20;
                    for (;;) {

                        auto result = mChannel->tryRead(&mDataForReading);
                        if (result == IoResult::Ok) {
                            mDataForReadingLeft = mDataForReading.size();
                            break;
                        }
                        DD("%s: tryRead() failed with %d", __func__, (int)result);
                        if (len > 0) {
                            DD("%s: returning %d bytes", __func__, (int)len);
                            return len;
                        }
                        // This failed either because the channel was stopped
                        // from the host, or if there was no data yet in the
                        if (result == IoResult::Error) {
                            return PIPE_ERROR_IO;
                        }
                        // Spin a little before declaring there is nothing
                        // to read. Many GL calls are much faster than the
                        // whole host-to-guest-to-host transition.
                        if (--spinCount > 0) {
                            continue;
                        }
                        DD("%s: returning PIPE_ERROR_AGAIN", __func__);
                        return PIPE_ERROR_AGAIN;
                    }
                } else if (android::opengl::recvMode == android::opengl::RecvMode::Fuchsia) {
                    // No data left, return if we already received some data,
                    // otherwise read a new chunk from the channel.
                    if (len > 0) {
                        DD("%s: returning %d bytes", __func__, (int)len);
                        return len;
                    }
                    // Block a little before declaring there is nothing
                    // to read. This gives the render thread a chance to
                    // process pending data before we return control to
                    // the guest. The amount of time we block here should
                    // be kept at a minimum. It's preferred to instead have
                    // the guest block on work that takes a significant
                    // amount of time.

                    const RenderChannel::Duration kBlockAtMostUs = 100;
                    auto currTime = android::base::getUnixTimeUs();
                    auto result = mChannel->readBefore(&mDataForReading, currTime + kBlockAtMostUs);

                    if (result != IoResult::Ok) {
                        DD("%s: tryRead() failed with %d", __func__, (int)result);
                        // This failed either because the channel was stopped
                        // from the host, or if there was no data yet in the
                        // channel.
                        if (len > 0) {
                            DD("%s: returning %d bytes", __func__, (int)len);
                            return len;
                        }
                        if (result == IoResult::Error) {
                            return PIPE_ERROR_IO;
                        }

                        DD("%s: returning PIPE_ERROR_AGAIN", __func__);
                        return PIPE_ERROR_AGAIN;
                    }
                    mDataForReadingLeft = mDataForReading.size();
                } else { // Virtio-gpu
                    // No data left, return if we already received some data,
                    // otherwise read a new chunk from the channel.
                    if (len > 0) {
                        DD("%s: returning %d bytes", __func__, (int)len);
                        return len;
                    }
                    // Block a little before declaring there is nothing
                    // to read. This gives the render thread a chance to
                    // process pending data before we return control to
                    // the guest. The amount of time we block here should
                    // be kept at a minimum. It's preferred to instead have
                    // the guest block on work that takes a significant
                    // amount of time.

                    const RenderChannel::Duration kBlockAtMostUs = 10000;
                    auto currTime = android::base::getUnixTimeUs();
                    auto result = mChannel->readBefore(&mDataForReading, currTime + kBlockAtMostUs);

                    if (result != IoResult::Ok) {
                        DD("%s: tryRead() failed with %d", __func__, (int)result);
                        // This failed either because the channel was stopped
                        // from the host, or if there was no data yet in the
                        // channel.
                        if (len > 0) {
                            DD("%s: returning %d bytes", __func__, (int)len);
                            return len;
                        }
                        if (result == IoResult::Error) {
                            return PIPE_ERROR_IO;
                        }

                        DD("%s: returning PIPE_ERROR_AGAIN", __func__);
                        return PIPE_ERROR_AGAIN;
                    }
                    mDataForReadingLeft = mDataForReading.size();
                }
            }

            const size_t curSize = std::min<size_t>(buff->size - buffOffset,
                                                    mDataForReadingLeft);
            memcpy(buff->data + buffOffset,
                   mDataForReading.data() +
                           (mDataForReading.size() - mDataForReadingLeft),
                   curSize);

            len += curSize;
            mDataForReadingLeft -= curSize;
            buffOffset += curSize;
            if (buffOffset == buff->size) {
                ++buff;
                buffOffset = 0;
            }
        }

        DD("%s: received %d bytes", __func__, (int)len);
        return len;
    }

    virtual int onGuestSend(const AndroidPipeBuffer* buffers,
                            int numBuffers,
                            void** newPipePtr) override {
        DD("%s", __func__);

        if (!mIsWorking) {
            DD("%s: pipe already closed!", __func__);
            return PIPE_ERROR_IO;
        }

        // Count the total bytes to send.
        int count = 0;
        for (int n = 0; n < numBuffers; ++n) {
            count += buffers[n].size;
        }

        // Copy everything into a single ChannelBuffer.
        ChannelBuffer outBuffer;
        outBuffer.resize_noinit(count);
        auto ptr = outBuffer.data();
        for (int n = 0; n < numBuffers; ++n) {
            memcpy(ptr, buffers[n].data, buffers[n].size);
            ptr += buffers[n].size;
        }

        D("%s: %p sending %d bytes to host", __func__, this, count);
        // Send it through the channel.
        auto result = mChannel->tryWrite(std::move(outBuffer));
        if (result != IoResult::Ok) {
            D("%s: tryWrite() failed with %d", __func__, (int)result);
            return result == IoResult::Error ? PIPE_ERROR_IO : PIPE_ERROR_AGAIN;
        }

        return count;
    }

    virtual void onGuestWantWakeOn(int flags) override {
        DD("%s: flags=%d", __func__, flags);

        // Translate |flags| into ChannelState flags.
        ChannelState wanted = ChannelState::Empty;
        if (flags & PIPE_WAKE_READ) {
            wanted |= ChannelState::CanRead;
        }
        if (flags & PIPE_WAKE_WRITE) {
            wanted |= ChannelState::CanWrite;
        }

        // Signal events that are already available now.
        ChannelState state = mChannel->state();
        ChannelState available = state & wanted;
        DD("%s: state=%d wanted=%d available=%d", __func__, (int)state,
           (int)wanted, (int)available);
        if (available != ChannelState::Empty) {
            DD("%s: signaling events %d", __func__, (int)available);
            signalState(available);
            wanted &= ~available;
        }

        // Ask the channel to be notified of remaining events.
        if (wanted != ChannelState::Empty) {
            DD("%s: waiting for events %d", __func__, (int)wanted);
            mChannel->setWantedEvents(wanted);
        }
    }

private:
    // Called to signal the guest that read/write wake events occured.
    // Note: this can be called from either the guest or host render
    // thread.
    void signalState(ChannelState state) {
        int wakeFlags = 0;
        if ((state & ChannelState::CanRead) != 0) {
            wakeFlags |= PIPE_WAKE_READ;
        }
        if ((state & ChannelState::CanWrite) != 0) {
            wakeFlags |= PIPE_WAKE_WRITE;
        }
        if (wakeFlags != 0) {
            this->signalWake(wakeFlags);
        }
    }

    // Called when an i/o event occurs on the render channel
    void onChannelHostEvent(ChannelState state) {
        D("%s: events %d (working %d)", __func__, (int)state, (int)mIsWorking);
        // NOTE: This is called from the host-side render thread.
        // but closeFromHost() and signalWake() can be called from
        // any thread.
        if (!mIsWorking) {
            return;
        }
        if ((state & ChannelState::Stopped) != 0) {
            this->closeFromHost();
            return;
        }
        signalState(state);
    }

    // A RenderChannel pointer used for communication.
    RenderChannelPtr mChannel;

    // Set to |true| if the pipe is in working state, |false| means we're not
    // initialized or the pipe is closed.
    bool mIsWorking = false;

    // These two variables serve as a reading buffer for the guest.
    // Each time we get a read request, first we extract a single chunk from
    // the |mChannel| into here, and then copy its content into the
    // guest-supplied memory.
    // If guest didn't have enough room for the whole buffer, we track the
    // number of remaining bytes in |mDataForReadingLeft| for the next read().
    uint32_t mDataForReadingLeft = 0;
    ChannelBuffer mDataForReading;

    DISALLOW_COPY_ASSIGN_AND_MOVE(EmuglPipe);
};

}  // namespace

void registerPipeService() {
    android::AndroidPipe::Service::add(std::make_unique<EmuglPipe::Service>());
    registerGLProcessPipeService();
}

void pipeSetRecvMode(int mode) {
    recvMode = (RecvMode)mode;
}

}  // namespace opengl
}  // namespace android

// Declared in android/opengles-pipe.h
void android_init_opengles_pipe() {
    android::opengl::registerPipeService();
}

void android_opengles_pipe_set_recv_mode(int mode) {
    android::opengl::pipeSetRecvMode(mode);
}

