/*
 * Copyright 2022 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.
 */

#pragma once

#include <android/choreographer.h>
#include <gui/DisplayEventDispatcher.h>
#include <jni.h>
#include <utils/Looper.h>

#include <mutex>
#include <queue>
#include <thread>

namespace android {
using gui::VsyncEventData;

struct FrameCallback {
    AChoreographer_frameCallback callback;
    AChoreographer_frameCallback64 callback64;
    AChoreographer_vsyncCallback vsyncCallback;
    void* data;
    nsecs_t dueTime;

    inline bool operator<(const FrameCallback& rhs) const {
        // Note that this is intentionally flipped because we want callbacks due sooner to be at
        // the head of the queue
        return dueTime > rhs.dueTime;
    }
};

struct RefreshRateCallback {
    AChoreographer_refreshRateCallback callback;
    void* data;
    bool firstCallbackFired = false;
};

class Choreographer;

/**
 * Implementation of AChoreographerFrameCallbackData.
 */
struct ChoreographerFrameCallbackDataImpl {
    int64_t frameTimeNanos{0};

    VsyncEventData vsyncEventData;

    const Choreographer* choreographer;
};

class Choreographer : public DisplayEventDispatcher, public MessageHandler {
public:
    struct Context {
        std::mutex lock;
        std::vector<Choreographer*> ptrs GUARDED_BY(lock);
        std::map<AVsyncId, int64_t> startTimes GUARDED_BY(lock);
        bool registeredToDisplayManager GUARDED_BY(lock) = false;

        std::atomic<nsecs_t> mLastKnownVsync = -1;
    };
    static Context gChoreographers;

    explicit Choreographer(const sp<Looper>& looper, const sp<IBinder>& layerHandle = nullptr)
            EXCLUDES(gChoreographers.lock);
    void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                  AChoreographer_frameCallback64 cb64,
                                  AChoreographer_vsyncCallback vsyncCallback, void* data,
                                  nsecs_t delay);
    void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
            EXCLUDES(gChoreographers.lock);
    void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
    // Drains the queue of pending vsync periods and dispatches refresh rate
    // updates to callbacks.
    // The assumption is that this method is only called on a single
    // processing thread, either by looper or by AChoreographer_handleEvents
    void handleRefreshRateUpdates();
    void scheduleLatestConfigRequest();

    enum {
        MSG_SCHEDULE_CALLBACKS = 0,
        MSG_SCHEDULE_VSYNC = 1,
        MSG_HANDLE_REFRESH_RATE_UPDATES = 2,
    };
    virtual void handleMessage(const Message& message) override;

    static void initJVM(JNIEnv* env);
    static Choreographer* getForThread();
    static void signalRefreshRateCallbacks(nsecs_t vsyncPeriod) EXCLUDES(gChoreographers.lock);
    static int64_t getStartTimeNanosForVsyncId(AVsyncId vsyncId) EXCLUDES(gChoreographers.lock);
    virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
    int64_t getFrameInterval() const;
    bool inCallback() const;

private:
    Choreographer(const Choreographer&) = delete;

    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                       VsyncEventData vsyncEventData) override;
    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
    void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
    void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                             nsecs_t vsyncPeriod) override;
    void dispatchNullEvent(nsecs_t, PhysicalDisplayId) override;
    void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                    std::vector<FrameRateOverride> overrides) override;
    void dispatchHdcpLevelsChanged(PhysicalDisplayId displayId, int32_t connectedLevel,
                                   int32_t maxLevel) override;

    void scheduleCallbacks();

    ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
    void registerStartTime() const;

    std::mutex mLock;
    // Protected by mLock
    std::priority_queue<FrameCallback> mFrameCallbacks;
    std::vector<RefreshRateCallback> mRefreshRateCallbacks;

    nsecs_t mLatestVsyncPeriod = -1;
    VsyncEventData mLastVsyncEventData;
    bool mInCallback = false;

    const sp<Looper> mLooper;
    const std::thread::id mThreadId;

    // Approximation of num_threads_using_choreographer * num_frames_of_history with leeway.
    static constexpr size_t kMaxStartTimes = 250;
};

} // namespace android
