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

#include <stdio.h>   // for printf
#include <stdlib.h>  // for NULL, free, malloc
#include <string.h>  // for memcpy

#include <atomic>  // for atomic_bool, memory_o...
#include <memory>  // for unique_ptr

#include "aemu/base/synchronization/Lock.h"  // for Lock, AutoLock
#include "aemu/base/synchronization/MessageChannel.h"
#include "host-common/opengles.h"  // for android_getFlushReadP...

#ifdef _WIN32
#undef ERROR
#endif

namespace android {
namespace opengl {

using android::base::AutoLock;
using android::base::Lock;
using android::base::MessageChannel;

namespace {

// A small structure to model a single frame of the GPU display,
// as passed between the EmuGL and main loop thread.
struct Frame {
    int width;
    int height;
    void* pixels;
    bool isValid;

    Frame(int w, int h, const void* pixels)
        : width(w), height(h), pixels(NULL), isValid(true) {
        this->pixels = ::malloc(w * 4 * h);
    }

    ~Frame() { ::free(pixels); }
};

// Real implementation of GpuFrameBridge interface.
class Bridge : public GpuFrameBridge {
public:
    // Constructor.
    Bridge()
        : GpuFrameBridge(),
          mRecFrame(NULL),
          mRecTmpFrame(NULL),
          mRecFrameUpdated(false),
          mReadPixelsFunc(android_getReadPixelsFunc()),
          mFlushPixelPipeline(android_getFlushReadPixelPipeline()) {}

    // The gpu bridge receives frames from a buffer that can contain multiple
    // frames. usually the bridge is one frame behind. This is usually not a
    // problem when we have a high enough framerate. However it is possible that
    // the framerate drops really low (even <1). This can result in the bridge
    // never delivering this "stuck frame".
    //
    // As a work around we will flush the reader pipeline if no frames are
    // delivered within at most 2x kFrameDelayms
    const long kMinFPS = 24;
    const long kFrameDelayMs = 1000 / kMinFPS;

    // Destructor
    virtual ~Bridge() {
        if (mRecFrame) {
            delete mRecFrame;
        }
        if (mRecTmpFrame) {
            delete mRecTmpFrame;
        }
    }

    // virtual void setLooper(android::base::Looper* aLooper) override {
    //     mTimer = std::unique_ptr<android::base::Looper::Timer>(
    //             aLooper->createTimer(_on_frame_notify, this));
    // }

    void notify() {
        AutoLock delay(mDelayLock);
        switch (mDelayCallback) {
            case FrameDelay::Reschedule:
                // mTimer->startRelative(kFrameDelayMs);
                mDelayCallback = FrameDelay::Scheduled;
                break;
            case FrameDelay::Scheduled:
                // mTimer->stop();
                mDelayCallback = FrameDelay::Firing;
                delay.unlock();
                mFlushPixelPipeline(mDisplayId);
                break;
            default:
                assert(false);
        }
    }

    // static void _on_frame_notify(void* opaque,
    //                              android::base::Looper::Timer* timer) {
    //     Bridge* worker = static_cast<Bridge*>(opaque);
    //     worker->notify();
    // }

    // Implementation of the GpuFrameBridge::postRecordFrame() method, must be
    // called from the EmuGL thread.
    virtual void postRecordFrame(int width,
                                 int height,
                                 const void* pixels) override {
        postFrame(width, height, pixels, true);
    }

    virtual void postRecordFrameAsync(int width,
                                      int height,
                                      const void* pixels) override {
        postFrame(width, height, pixels, false);
    }

    virtual void* getRecordFrame() override {
        if (mRecFrameUpdated.exchange(false)) {
            AutoLock lock(mRecLock);
            memcpy(mRecFrame->pixels, mRecTmpFrame->pixels,
                   mRecFrame->width * mRecFrame->height * 4);
            mRecFrame->isValid = true;
        }
        return mRecFrame && mRecFrame->isValid ? mRecFrame->pixels : nullptr;
    }

    virtual void* getRecordFrameAsync() override {
        if (mRecFrameUpdated.exchange(false)) {
            AutoLock lock(mRecLock);
            mReadPixelsFunc(mRecFrame->pixels,
                            mRecFrame->width * mRecFrame->height * 4,
                            mDisplayId);
            mRecFrame->isValid = true;
        }
        return mRecFrame && mRecFrame->isValid ? mRecFrame->pixels : nullptr;
    }

    virtual void invalidateRecordingBuffers() override {
        {
            AutoLock lock(mRecLock);
            // Release the buffers because new recording in the furture may have
            // different resolution if multi display changes its resolution.
            if (mRecFrame) {
                delete mRecFrame;
                mRecFrame = nullptr;
            }
            if (mRecTmpFrame) {
                delete mRecTmpFrame;
                mRecTmpFrame = nullptr;
            }
        }
        mRecFrameUpdated.store(false, std::memory_order_release);
    }

    void setFrameReceiver(FrameAvailableCallback receiver,
                          void* opaque) override {
        mReceiver = receiver;
        mReceiverOpaque = opaque;
    }

    void postFrame(int width, int height, const void* pixels, bool copy) {
        {
            AutoLock lock(mRecLock);
            if (!mRecFrame) {
                mRecFrame = new Frame(width, height, pixels);
            }
            if (!mRecTmpFrame) {
                mRecTmpFrame = new Frame(width, height, pixels);
            }
            if (copy) {
                memcpy(mRecTmpFrame->pixels, pixels, width * height * 4);
            }
        }
        mRecFrameUpdated.store(true, std::memory_order_release);
        if (mReceiver) {
            mReceiver(mReceiverOpaque);
            AutoLock delay(mDelayLock);
            switch (mDelayCallback) {
                case FrameDelay::NotScheduled:
                    // mTimer->startRelative(kFrameDelayMs);
                    mDelayCallback = FrameDelay::Scheduled;
                    break;
                case FrameDelay::Firing:
                    mDelayCallback = FrameDelay::NotScheduled;
                    break;
                default:
                    mDelayCallback = FrameDelay::Reschedule;
                    break;
            }
        }
    }

    virtual void setDisplayId(uint32_t displayId) override {
        mDisplayId = displayId;
    }
    enum class FrameDelay {
        NotScheduled = 0,  // No delay timer is scheduled
        Scheduled,   // A delay timer has been scheduled and will flush the
                     // pipeline on expiration
        Reschedule,  // Do not flush the pipeline, but reschedule
        Firing,      // A callback has been scheduled, nothing needs to happen
    };

private:
    FrameAvailableCallback mReceiver = nullptr;
    void* mReceiverOpaque = nullptr;
    Lock mRecLock;
    Frame* mRecFrame;
    Frame* mRecTmpFrame;
    std::atomic_bool mRecFrameUpdated;
    ReadPixelsFunc mReadPixelsFunc = 0;
    uint32_t mDisplayId = 0;
    FlushReadPixelPipeline mFlushPixelPipeline = 0;

    // std::unique_ptr<android::base::Looper::Timer> mTimer;
    Lock mDelayLock;
    FrameDelay mDelayCallback{FrameDelay::NotScheduled};
};
}  // namespace
// static
GpuFrameBridge* GpuFrameBridge::create() {
    return new Bridge();
}

}  // namespace opengl
}  // namespace android
