/*
 * Copyright 2019 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 <type_traits>
#include <utility>
#include <variant>

#include <ftl/concat.h>
#include <ftl/optional.h>
#include <ftl/unit.h>
#include <gui/DisplayEventReceiver.h>

#include <scheduler/Fps.h>
#include <scheduler/FrameRateMode.h>
#include <scheduler/Seamlessness.h>

#include "DisplayHardware/DisplayMode.h"
#include "Scheduler/OneShotTimer.h"
#include "ThreadContext.h"
#include "Utils/Dumper.h"

namespace android::scheduler {

using namespace std::chrono_literals;

using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;

// Selects the refresh rate of a display by ranking its `DisplayModes` in accordance with
// the DisplayManager (or override) `Policy`, the `LayerRequirement` of each active layer,
// and `GlobalSignals`.
class RefreshRateSelector {
public:
    // Margin used when matching refresh rates to the content desired ones.
    static constexpr nsecs_t MARGIN_FOR_PERIOD_CALCULATION =
            std::chrono::nanoseconds(800us).count();

    // The lowest Render Frame Rate that will ever be selected
    static constexpr Fps kMinSupportedFrameRate = 20_Hz;

    class Policy {
        static constexpr int kAllowGroupSwitchingDefault = false;

    public:
        // The default mode, used to ensure we only initiate display mode switches within the
        // same mode group as defaultMode's group.
        DisplayModeId defaultMode;
        // Whether or not we switch mode groups to get the best frame rate.
        bool allowGroupSwitching = kAllowGroupSwitchingDefault;
        // The primary refresh rate ranges. @see DisplayModeSpecs.aidl for details.
        // TODO(b/257072060): use the render range when selecting SF render rate
        //  or the app override frame rate
        FpsRanges primaryRanges;
        // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
        FpsRanges appRequestRanges;
        // The idle timer configuration, if provided.
        std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig> idleScreenConfigOpt;

        Policy() = default;

        Policy(DisplayModeId defaultMode, FpsRange range,
               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
                       idleScreenConfigOpt = std::nullopt)
              : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
                       allowGroupSwitching, idleScreenConfigOpt) {}

        Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
                       idleScreenConfigOpt = std::nullopt)
              : defaultMode(defaultMode),
                allowGroupSwitching(allowGroupSwitching),
                primaryRanges(primaryRanges),
                appRequestRanges(appRequestRanges),
                idleScreenConfigOpt(idleScreenConfigOpt) {}

        bool operator==(const Policy& other) const {
            using namespace fps_approx_ops;
            return similarExceptIdleConfig(other) &&
                    idleScreenConfigOpt == other.idleScreenConfigOpt;
        }

        bool operator!=(const Policy& other) const { return !(*this == other); }

        bool primaryRangeIsSingleRate() const {
            return isApproxEqual(primaryRanges.physical.min, primaryRanges.physical.max);
        }

        bool similarExceptIdleConfig(const Policy& updated) const {
            using namespace fps_approx_ops;
            return defaultMode == updated.defaultMode && primaryRanges == updated.primaryRanges &&
                    appRequestRanges == updated.appRequestRanges &&
                    allowGroupSwitching == updated.allowGroupSwitching;
        }

        std::string toString() const;
    };

    enum class SetPolicyResult { Invalid, Unchanged, Changed };

    // We maintain the display manager policy and the override policy separately. The override
    // policy is used by CTS tests to get a consistent device state for testing. While the override
    // policy is set, it takes precedence over the display manager policy. Once the override policy
    // is cleared, we revert to using the display manager policy.
    struct DisplayManagerPolicy : Policy {
        using Policy::Policy;
    };

    struct OverridePolicy : Policy {
        using Policy::Policy;
    };

    struct NoOverridePolicy {};

    using PolicyVariant = std::variant<DisplayManagerPolicy, OverridePolicy, NoOverridePolicy>;

    SetPolicyResult setPolicy(const PolicyVariant&) EXCLUDES(mLock) REQUIRES(kMainThreadContext);

    void onModeChangeInitiated() REQUIRES(kMainThreadContext) { mNumModeSwitchesInPolicy++; }

    // Gets the current policy, which will be the override policy if active, and the display manager
    // policy otherwise.
    Policy getCurrentPolicy() const EXCLUDES(mLock);
    // Gets the display manager policy, regardless of whether an override policy is active.
    Policy getDisplayManagerPolicy() const EXCLUDES(mLock);

    // Returns true if mode is allowed by the current policy.
    bool isModeAllowed(const FrameRateMode&) const EXCLUDES(mLock);

    // Describes the different options the layer voted for refresh rate
    enum class LayerVoteType {
        NoVote,          // Doesn't care about the refresh rate
        Min,             // Minimal refresh rate available
        Max,             // Maximal refresh rate available
        Heuristic,       // Specific refresh rate that was calculated by platform using a heuristic
        ExplicitDefault, // Specific refresh rate that was provided by the app with Default
                         // compatibility
        ExplicitExactOrMultiple, // Specific refresh rate that was provided by the app with
                                 // ExactOrMultiple compatibility
        ExplicitExact,           // Specific refresh rate that was provided by the app with
                                 // Exact compatibility
        ExplicitGte,             // Greater than or equal to frame rate provided by the app
        ExplicitCategory,        // Specific frame rate category was provided by the app

        ftl_last = ExplicitCategory
    };

    // Captures the layer requirements for a refresh rate. This will be used to determine the
    // display refresh rate.
    struct LayerRequirement {
        // Layer's name. Used for debugging purposes.
        std::string name;
        // Layer's owner uid
        uid_t ownerUid = static_cast<uid_t>(-1);
        // Layer vote type.
        LayerVoteType vote = LayerVoteType::NoVote;
        // Layer's desired refresh rate, if applicable.
        Fps desiredRefreshRate;
        // If a seamless mode switch is required.
        Seamlessness seamlessness = Seamlessness::Default;
        // Layer frame rate category.
        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
        // Goes together with frame rate category vote. Allow refresh rate changes only
        // if there would be no jank.
        bool frameRateCategorySmoothSwitchOnly = false;
        // Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
        // would have on choosing the refresh rate.
        float weight = 0.0f;
        // Whether layer is in focus or not based on WindowManager's state
        bool focused = false;

        bool operator==(const LayerRequirement& other) const {
            return name == other.name && vote == other.vote &&
                    isApproxEqual(desiredRefreshRate, other.desiredRefreshRate) &&
                    seamlessness == other.seamlessness && weight == other.weight &&
                    focused == other.focused && frameRateCategory == other.frameRateCategory;
        }

        bool operator!=(const LayerRequirement& other) const { return !(*this == other); }

        bool isNoVote() const { return RefreshRateSelector::isNoVote(vote); }
    };

    // Returns true if the layer explicitly instructs to not contribute to refresh rate selection.
    // In other words, true if the layer should be ignored.
    static bool isNoVote(LayerVoteType vote) { return vote == LayerVoteType::NoVote; }

    // Global state describing signals that affect refresh rate choice.
    struct GlobalSignals {
        // Whether the user touched the screen recently. Used to apply touch boost.
        bool touch = false;
        // True if the system hasn't seen any buffers posted to layers recently.
        bool idle = false;
        // Whether the display is about to be powered on, or has been in PowerMode::ON
        // within the timeout of DisplayPowerTimer.
        bool powerOnImminent = false;

        bool operator==(GlobalSignals other) const {
            return touch == other.touch && idle == other.idle &&
                    powerOnImminent == other.powerOnImminent;
        }

        auto toString() const {
            return ftl::Concat("{touch=", touch, ", idle=", idle,
                               ", powerOnImminent=", powerOnImminent, '}');
        }
    };

    struct ScoredFrameRate {
        FrameRateMode frameRateMode;
        float score = 0.0f;

        bool operator==(const ScoredFrameRate& other) const {
            return frameRateMode == other.frameRateMode && score == other.score;
        }

        static bool scoresEqual(float lhs, float rhs) {
            constexpr float kEpsilon = 0.0001f;
            return std::abs(lhs - rhs) <= kEpsilon;
        }

        struct DescendingScore {
            bool operator()(const ScoredFrameRate& lhs, const ScoredFrameRate& rhs) const {
                return lhs.score > rhs.score && !scoresEqual(lhs.score, rhs.score);
            }
        };
    };

    using FrameRateRanking = std::vector<ScoredFrameRate>;

    struct RankedFrameRates {
        FrameRateRanking ranking; // Ordered by descending score.
        GlobalSignals consideredSignals;
        Fps pacesetterFps;

        bool operator==(const RankedFrameRates& other) const {
            return ranking == other.ranking && consideredSignals == other.consideredSignals &&
                    isApproxEqual(pacesetterFps, other.pacesetterFps);
        }
    };

    // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
    // that refresh rate.
    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
                                         Fps pacesetterFps = {}) const EXCLUDES(mLock);

    FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
        std::lock_guard lock(mLock);
        return {mMinRefreshRateModeIt->second->getPeakFps(),
                mMaxRefreshRateModeIt->second->getPeakFps()};
    }

    ftl::Optional<FrameRateMode> onKernelTimerChanged(ftl::Optional<DisplayModeId> desiredModeIdOpt,
                                                      bool timerExpired) const EXCLUDES(mLock);

    void setActiveMode(DisplayModeId, Fps renderFrameRate) EXCLUDES(mLock);

    // See mActiveModeOpt for thread safety.
    FrameRateMode getActiveMode() const EXCLUDES(mLock);

    // Returns a known frame rate that is the closest to frameRate
    Fps findClosestKnownFrameRate(Fps frameRate) const;

    enum class KernelIdleTimerController { Sysprop, HwcApi, ftl_last = HwcApi };

    // Configuration flags.
    struct Config {
        enum class FrameRateOverride {
            // Do not override the frame rate for an app
            Disabled,

            // Override the frame rate for an app to a value which is also
            // a display refresh rate
            AppOverrideNativeRefreshRates,

            // Override the frame rate for an app to any value
            AppOverride,

            // Override the frame rate for all apps and all values.
            Enabled,

            ftl_last = Enabled
        };
        FrameRateOverride enableFrameRateOverride = FrameRateOverride::Disabled;

        // Specifies the upper refresh rate threshold (inclusive) for layer vote types of multiple
        // or heuristic, such that refresh rates higher than this value will not be voted for. 0 if
        // no threshold is set.
        int frameRateMultipleThreshold = 0;

        // The Idle Timer timeout. 0 timeout means no idle timer.
        std::chrono::milliseconds legacyIdleTimerTimeout = 0ms;

        // The controller representing how the kernel idle timer will be configured
        // either on the HWC api or sysprop.
        ftl::Optional<KernelIdleTimerController> kernelIdleTimerController;
    };

    RefreshRateSelector(
            DisplayModes, DisplayModeId activeModeId,
            Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
                             .frameRateMultipleThreshold = 0,
                             .legacyIdleTimerTimeout = 0ms,
                             .kernelIdleTimerController = {}});

    RefreshRateSelector(const RefreshRateSelector&) = delete;
    RefreshRateSelector& operator=(const RefreshRateSelector&) = delete;

    DisplayModes displayModes() const {
        std::lock_guard lock(mLock);
        return mDisplayModes;
    }

    // Returns whether switching modes (refresh rate or resolution) is possible.
    // TODO(b/158780872): Consider HAL support, and skip frame rate detection if the modes only
    //  differ in resolution. Once Config::FrameRateOverride::Enabled becomes the default,
    //  we can probably remove canSwitch altogether since all devices will be able
    //  to switch to a frame rate divisor.
    bool canSwitch() const EXCLUDES(mLock) {
        std::lock_guard lock(mLock);
        return mDisplayModes.size() > 1 ||
                mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
    }

    // Class to enumerate options around toggling the kernel timer on and off.
    enum class KernelIdleTimerAction {
        TurnOff, // Turn off the idle timer.
        TurnOn   // Turn on the idle timer.
    };

    // Checks whether kernel idle timer should be active depending the policy decisions around
    // refresh rates.
    KernelIdleTimerAction getIdleTimerAction() const;

    bool supportsAppFrameRateOverrideByContent() const {
        return mFrameRateOverrideConfig != Config::FrameRateOverride::Disabled;
    }

    bool supportsFrameRateOverride() const {
        return mFrameRateOverrideConfig == Config::FrameRateOverride::Enabled;
    }

    // Return the display refresh rate divisor to match the layer
    // frame rate, or 0 if the display refresh rate is not a multiple of the
    // layer refresh rate.
    static int getFrameRateDivisor(Fps displayRefreshRate, Fps layerFrameRate);

    // Returns if the provided frame rates have a ratio t*1000/1001 or t*1001/1000
    // for an integer t.
    static bool isFractionalPairOrMultiple(Fps, Fps);

    using UidToFrameRateOverride = std::map<uid_t, Fps>;

    // Returns the frame rate override for each uid.
    UidToFrameRateOverride getFrameRateOverrides(const std::vector<LayerRequirement>&,
                                                 Fps displayFrameRate, GlobalSignals) const
            EXCLUDES(mLock);

    // Gets the FpsRange that the FrameRateCategory represents.
    static FpsRange getFrameRateCategoryRange(FrameRateCategory category);

    std::optional<KernelIdleTimerController> kernelIdleTimerController() {
        return mConfig.kernelIdleTimerController;
    }

    struct IdleTimerCallbacks {
        struct Callbacks {
            std::function<void()> onReset;
            std::function<void()> onExpired;
        };

        Callbacks platform;
        Callbacks kernel;
        Callbacks vrr;
    };

    void setIdleTimerCallbacks(IdleTimerCallbacks callbacks) EXCLUDES(mIdleTimerCallbacksMutex) {
        std::scoped_lock lock(mIdleTimerCallbacksMutex);
        mIdleTimerCallbacks = std::move(callbacks);
    }

    void clearIdleTimerCallbacks() EXCLUDES(mIdleTimerCallbacksMutex) {
        std::scoped_lock lock(mIdleTimerCallbacksMutex);
        mIdleTimerCallbacks.reset();
    }

    void startIdleTimer() {
        mIdleTimerStarted = true;
        if (mIdleTimer) {
            mIdleTimer->start();
        }
    }

    void stopIdleTimer() {
        mIdleTimerStarted = false;
        if (mIdleTimer) {
            mIdleTimer->stop();
        }
    }

    void resetKernelIdleTimer() {
        if (mIdleTimer && mConfig.kernelIdleTimerController) {
            mIdleTimer->reset();
        }
    }

    void resetIdleTimer() {
        if (mIdleTimer) {
            mIdleTimer->reset();
        }
    }

    void dump(utils::Dumper&) const EXCLUDES(mLock);

    std::chrono::milliseconds getIdleTimerTimeout();

    bool isVrrDevice() const;

private:
    friend struct TestableRefreshRateSelector;

    void constructAvailableRefreshRates() REQUIRES(mLock);

    // See mActiveModeOpt for thread safety.
    const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);

    RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
                                               GlobalSignals signals, Fps pacesetterFps) const
            REQUIRES(mLock);

    // Returns number of display frames and remainder when dividing the layer refresh period by
    // display refresh period.
    std::pair<nsecs_t, nsecs_t> getDisplayFrames(nsecs_t layerPeriod, nsecs_t displayPeriod) const;

    // Returns the lowest refresh rate according to the current policy. May change at runtime. Only
    // uses the primary range, not the app request range.
    const DisplayModePtr& getMinRefreshRateByPolicyLocked() const REQUIRES(mLock);

    // Returns the highest refresh rate according to the current policy. May change at runtime. Only
    // uses the primary range, not the app request range.
    const DisplayModePtr& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock);

    struct RefreshRateScoreComparator;

    enum class RefreshRateOrder {
        Ascending,
        Descending,

        ftl_last = Descending
    };

    typedef std::function<bool(const FrameRateMode)> RankFrameRatesPredicate;

    // Rank the frame rates.
    // Only modes in the primary range for which `predicate` is `true` will be scored.
    // Does not use the app requested range.
    FrameRateRanking rankFrameRates(
            std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
            std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt,
            const RankFrameRatesPredicate& predicate = [](FrameRateMode) { return true; }) const
            REQUIRES(mLock);

    const Policy* getCurrentPolicyLocked() const REQUIRES(mLock);
    bool isPolicyValidLocked(const Policy& policy) const REQUIRES(mLock);

    // Returns the refresh rate score as a ratio to max refresh rate, which has a score of 1.
    float calculateDistanceScoreFromMaxLocked(Fps refreshRate) const REQUIRES(mLock);

    // Returns the refresh rate score based on its distance from the reference rate.
    float calculateDistanceScoreLocked(Fps referenceRate, Fps refreshRate) const REQUIRES(mLock);

    // calculates a score for a layer. Used to determine the display refresh rate
    // and the frame rate override for certains applications.
    float calculateLayerScoreLocked(const LayerRequirement&, Fps refreshRate,
                                    bool isSeamlessSwitch) const REQUIRES(mLock);

    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const
            REQUIRES(mLock);

    // Calculates the score for non-exact matching layer that has LayerVoteType::ExplicitDefault.
    float calculateNonExactMatchingDefaultLayerScoreLocked(nsecs_t displayPeriod,
                                                           nsecs_t layerPeriod) const
            REQUIRES(mLock);

    void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
            REQUIRES(kMainThreadContext);

    void initializeIdleTimer(std::chrono::milliseconds timeout);

    std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
            REQUIRES(mIdleTimerCallbacksMutex) {
        if (!mIdleTimerCallbacks) return {};

        if (mIsVrrDevice) return mIdleTimerCallbacks->vrr;

        return mConfig.kernelIdleTimerController.has_value() ? mIdleTimerCallbacks->kernel
                                                             : mIdleTimerCallbacks->platform;
    }

    bool isNativeRefreshRate(Fps fps) const REQUIRES(mLock) {
        LOG_ALWAYS_FATAL_IF(mConfig.enableFrameRateOverride !=
                                    Config::FrameRateOverride::AppOverrideNativeRefreshRates,
                            "should only be called when "
                            "Config::FrameRateOverride::AppOverrideNativeRefreshRates is used");
        return mAppOverrideNativeRefreshRates.contains(fps);
    }

    std::vector<FrameRateMode> createFrameRateModes(
            const Policy&, std::function<bool(const DisplayMode&)>&& filterModes,
            const FpsRange&) const REQUIRES(mLock);

    // The display modes of the active display. The DisplayModeIterators below are pointers into
    // this container, so must be invalidated whenever the DisplayModes change. The Policy below
    // is also dependent, so must be reset as well.
    DisplayModes mDisplayModes GUARDED_BY(mLock);

    // Set of supported display refresh rates for easy lookup
    // when FrameRateOverride::AppOverrideNativeRefreshRates is in use.
    ftl::SmallMap<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;

    ftl::Optional<FrameRateMode> mActiveModeOpt GUARDED_BY(mLock);

    DisplayModeIterator mMinRefreshRateModeIt GUARDED_BY(mLock);
    DisplayModeIterator mMaxRefreshRateModeIt GUARDED_BY(mLock);

    // Display modes that satisfy the Policy's ranges, filtered and sorted by refresh rate.
    std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
    std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);

    // Caches whether the device is VRR-compatible based on the active display mode.
    std::atomic_bool mIsVrrDevice = false;

    Policy mDisplayManagerPolicy GUARDED_BY(mLock);
    std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);

    unsigned mNumModeSwitchesInPolicy GUARDED_BY(kMainThreadContext) = 0;

    mutable std::mutex mLock;

    // A sorted list of known frame rates that a Heuristic layer will choose
    // from based on the closest value.
    const std::vector<Fps> mKnownFrameRates;

    const Config mConfig;

    // A list of known frame rates that favors at least 60Hz if there is no exact match display
    // refresh rate
    const std::vector<Fps> mFrameRatesThatFavorsAtLeast60 = {23.976_Hz, 25_Hz, 29.97_Hz, 50_Hz,
                                                             59.94_Hz};

    Config::FrameRateOverride mFrameRateOverrideConfig;

    struct GetRankedFrameRatesCache {
        std::vector<LayerRequirement> layers;
        GlobalSignals signals;
        Fps pacesetterFps;

        RankedFrameRates result;

        bool matches(const GetRankedFrameRatesCache& other) const {
            return layers == other.layers && signals == other.signals &&
                    isApproxEqual(pacesetterFps, other.pacesetterFps);
        }
    };
    mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);

    // Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
    std::mutex mIdleTimerCallbacksMutex;
    std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
    // Used to detect (lack of) frame activity.
    ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
    std::atomic<bool> mIdleTimerStarted = false;
};

} // namespace android::scheduler
