/*
 * Copyright 2023 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 <array>
#include <atomic>
#include <memory>
#include <optional>

#include <ui/DisplayId.h>
#include <ui/Fence.h>
#include <ui/FenceTime.h>

#include <scheduler/Features.h>
#include <scheduler/Time.h>
#include <scheduler/VsyncId.h>
#include <scheduler/interface/CompositeResult.h>

// TODO(b/185536303): Pull to FTL.
#include "../../../TracedOrdinal.h"
#include "../../../Utils/Dumper.h"
#include "../../../Utils/RingBuffer.h"

namespace android::scheduler {

struct IVsyncSource;

// Read-only interface to the metrics computed by FrameTargeter for the latest frame.
class FrameTarget {
public:
    VsyncId vsyncId() const { return mVsyncId; }

    // The time when the frame actually began, as opposed to when it had been scheduled to begin.
    TimePoint frameBeginTime() const { return mFrameBeginTime; }

    // Relative to when the frame actually began, as opposed to when it had been scheduled to begin.
    Duration expectedFrameDuration() const { return mExpectedPresentTime - mFrameBeginTime; }

    TimePoint expectedPresentTime() const { return mExpectedPresentTime; }

    std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }

    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
    TimePoint pastVsyncTime(Period minFramePeriod) const;

    // The present fence for the frame that had targeted the most recent VSYNC before this frame.
    // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
    // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
    // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
    // signaled by now (unless that frame missed).
    FenceTimePtr presentFenceForPastVsync(Period minFramePeriod) const;

    // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
    const FenceTimePtr& presentFenceForPreviousFrame() const {
        return mPresentFences.front().fenceTime;
    }

    bool isFramePending() const { return mFramePending; }
    bool didMissFrame() const { return mFrameMissed; }
    bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
    TimePoint lastSignaledFrameTime() const { return mLastSignaledFrameTime; };

protected:
    explicit FrameTarget(const std::string& displayLabel);
    ~FrameTarget() = default;

    bool wouldPresentEarly(Period minFramePeriod) const;

    // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
    TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
        return mExpectedPresentTime - minFramePeriod;
    }

    void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
                  TimePoint expectedPresentTime) {
        mFenceWithFenceTimes.next() = {std::move(presentFence), presentFenceTime,
                                       expectedPresentTime};
    }

    VsyncId mVsyncId;
    TimePoint mFrameBeginTime;
    TimePoint mExpectedPresentTime;
    std::optional<TimePoint> mEarliestPresentTime;

    TracedOrdinal<bool> mFramePending;
    TracedOrdinal<bool> mFrameMissed;
    TracedOrdinal<bool> mHwcFrameMissed;
    TracedOrdinal<bool> mGpuFrameMissed;

    struct FenceWithFenceTime {
        sp<Fence> fence = Fence::NO_FENCE;
        FenceTimePtr fenceTime = FenceTime::NO_FENCE;
        TimePoint expectedPresentTime = TimePoint();
    };
    // size should be longest sf-duration / shortest vsync period and round up
    std::array<FenceWithFenceTime, 5> mPresentFences; // currently consider 166hz.
    utils::RingBuffer<FenceWithFenceTime, 5> mFenceWithFenceTimes;

    TimePoint mLastSignaledFrameTime;

private:
    friend class FrameTargeterTestBase;

    template <int N>
    inline bool targetsVsyncsAhead(Period minFramePeriod) const {
        static_assert(N > 1);
        return expectedFrameDuration() > (N - 1) * minFramePeriod;
    }

    const FenceTimePtr pastVsyncTimePtr() const {
        auto pastFenceTimePtr = FenceTime::NO_FENCE;
        for (size_t i = 0; i < mFenceWithFenceTimes.size(); i++) {
            const auto& [_, fenceTimePtr, expectedPresentTime] = mFenceWithFenceTimes[i];
            if (expectedPresentTime > mFrameBeginTime) {
                return pastFenceTimePtr;
            }
            pastFenceTimePtr = fenceTimePtr;
        }
        return pastFenceTimePtr;
    }

    size_t getPresentFenceShift(Period minFramePeriod) const {
        size_t shift = 0;
        if (minFramePeriod.ns() == 0) {
            return shift;
        }
        const bool isTwoVsyncsAhead = targetsVsyncsAhead<2>(minFramePeriod);
        if (isTwoVsyncsAhead) {
            shift = static_cast<size_t>(expectedFrameDuration().ns() / minFramePeriod.ns());
            if (shift >= mPresentFences.size()) {
                shift = mPresentFences.size() - 1;
            }
        }
        return shift;
    }
};

// Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
class FrameTargeter final : private FrameTarget {
public:
    FrameTargeter(PhysicalDisplayId displayId, FeatureFlags flags)
          : FrameTarget(to_string(displayId)),
            mBackpressureGpuComposition(flags.test(Feature::kBackpressureGpuComposition)),
            mSupportsExpectedPresentTime(flags.test(Feature::kExpectedPresentTime)) {}

    const FrameTarget& target() const { return *this; }

    struct BeginFrameArgs {
        TimePoint frameBeginTime;
        VsyncId vsyncId;
        TimePoint expectedVsyncTime;
        Duration sfWorkDuration;
        Duration hwcMinWorkDuration;
    };

    void beginFrame(const BeginFrameArgs&, const IVsyncSource&);

    std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod,
                                                        Duration hwcMinWorkDuration);

    // TODO(b/241285191): Merge with FrameTargeter::endFrame.
    FenceTimePtr setPresentFence(sp<Fence>);

    void endFrame(const CompositeResult&);

    void dump(utils::Dumper&) const;

private:
    friend class FrameTargeterTestBase;

    // For tests.
    using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs);
    void beginFrame(const BeginFrameArgs&, const IVsyncSource&, IsFencePendingFuncPtr);
    FenceTimePtr setPresentFence(sp<Fence>, FenceTimePtr);

    static bool isFencePending(const FenceTimePtr&, int graceTimeMs);

    const bool mBackpressureGpuComposition;
    const bool mSupportsExpectedPresentTime;

    TimePoint mScheduledPresentTime;
    CompositionCoverageFlags mCompositionCoverage;

    std::atomic_uint mFrameMissedCount = 0;
    std::atomic_uint mHwcFrameMissedCount = 0;
    std::atomic_uint mGpuFrameMissedCount = 0;
};

} // namespace android::scheduler
