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

#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include "Display/DisplayModeController.h"
#include "Display/DisplaySnapshot.h"
#include "DisplayHardware/HWComposer.h"

#include <android-base/properties.h>
#include <common/FlagManager.h>
#include <common/trace.h>
#include <ftl/concat.h>
#include <ftl/expected.h>
#include <ftl/fake_guard.h>
#include <log/log.h>
#include <utils/Errors.h>

namespace android::display {

template <size_t N>
inline std::string DisplayModeController::Display::concatId(const char (&str)[N]) const {
    return std::string(ftl::Concat(str, ' ', snapshot.get().displayId().value).str());
}

DisplayModeController::Display::Display(DisplaySnapshotRef snapshot,
                                        RefreshRateSelectorPtr selectorPtr)
      : snapshot(snapshot),
        selectorPtr(std::move(selectorPtr)),
        pendingModeFpsTrace(concatId("PendingModeFps")),
        activeModeFpsTrace(concatId("ActiveModeFps")),
        renderRateFpsTrace(concatId("RenderRateFps")),
        hasDesiredModeTrace(concatId("HasDesiredMode"), false) {}

DisplayModeController::DisplayModeController() {
    using namespace std::string_literals;
    mSupportsHdcp = base::GetBoolProperty("debug.sf.hdcp_support"s, false);
}

void DisplayModeController::registerDisplay(PhysicalDisplayId displayId,
                                            DisplaySnapshotRef snapshotRef,
                                            RefreshRateSelectorPtr selectorPtr) {
    DisplayPtr displayPtr = std::make_unique<Display>(snapshotRef, selectorPtr);
    // TODO: b/349703362 - Remove first condition when HDCP aidl APIs are enforced
    displayPtr->setSecure(!supportsHdcp() ||
                          snapshotRef.get().connectionType() ==
                                  ui::DisplayConnectionType::Internal);
    std::lock_guard lock(mDisplayLock);
    mDisplays.emplace_or_replace(displayId, std::move(displayPtr));
}

void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef,
                                            DisplayModeId activeModeId,
                                            scheduler::RefreshRateSelector::Config config) {
    const auto& snapshot = snapshotRef.get();
    const auto displayId = snapshot.displayId();
    DisplayPtr displayPtr =
            std::make_unique<Display>(snapshotRef, snapshot.displayModes(), activeModeId, config);
    // TODO: b/349703362 - Remove first condition when HDCP aidl APIs are enforced
    displayPtr->setSecure(!supportsHdcp() ||
                          snapshotRef.get().connectionType() ==
                                  ui::DisplayConnectionType::Internal);
    std::lock_guard lock(mDisplayLock);
    mDisplays.emplace_or_replace(displayId, std::move(displayPtr));
}

void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) {
    std::lock_guard lock(mDisplayLock);
    const bool ok = mDisplays.erase(displayId);
    ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str());
}

auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) const
        -> RefreshRateSelectorPtr {
    std::lock_guard lock(mDisplayLock);
    return mDisplays.get(displayId)
            .transform([](const DisplayPtr& displayPtr) { return displayPtr->selectorPtr; })
            .value_or(nullptr);
}

auto DisplayModeController::setDesiredMode(PhysicalDisplayId displayId,
                                           DisplayModeRequest&& desiredMode) -> DesiredModeAction {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get();

    {
        SFTRACE_NAME(displayPtr->concatId(__func__).c_str());
        ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str());

        std::scoped_lock lock(displayPtr->desiredModeLock);

        if (auto& desiredModeOpt = displayPtr->desiredModeOpt) {
            if (!FlagManager::getInstance().modeset_state_machine() ||
                desiredMode.mode.matchesResolution(desiredModeOpt->mode)) {
                // A mode transition was already scheduled, so just override the desired mode.
                const bool emitEvent = desiredModeOpt->emitEvent;
                const bool force = desiredModeOpt->force;
                desiredModeOpt = std::move(desiredMode);
                desiredModeOpt->emitEvent |= emitEvent;
                desiredModeOpt->force |= force;
                return DesiredModeAction::None;
            }
        }

        // If the desired mode is already active...
        const auto activeMode = displayPtr->selectorPtr->getActiveMode();
        if (const auto& desiredModePtr = desiredMode.mode.modePtr;
            !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
            ftl::FakeGuard guard(kMainThreadContext);

            // ...and there is no pending resolution change...
            if (!FlagManager::getInstance().modeset_state_machine() ||
                (!displayPtr->pendingModeOpt && desiredMode.mode.matchesResolution(activeMode))) {
                if (activeMode == desiredMode.mode) {
                    return DesiredModeAction::None;
                }

                // ...but the render rate changed:
                setActiveModeLocked(displayId, desiredModePtr->getId(),
                                    desiredModePtr->getVsyncRate(), desiredMode.mode.fps);
                return DesiredModeAction::InitiateRenderRateSwitch;
            }
        }

        // Restore peak render rate to schedule the next frame as soon as possible.
        setActiveModeLocked(displayId, activeMode.modePtr->getId(),
                            activeMode.modePtr->getVsyncRate(), activeMode.modePtr->getPeakFps());

        // Initiate a mode change.
        displayPtr->desiredModeOpt = std::move(desiredMode);
        displayPtr->hasDesiredModeTrace = true;
    }

    return DesiredModeAction::InitiateDisplayModeSwitch;
}

auto DisplayModeController::getDesiredMode(PhysicalDisplayId displayId) const
        -> DisplayModeRequestOpt {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();

    {
        std::scoped_lock lock(displayPtr->desiredModeLock);
        return displayPtr->desiredModeOpt;
    }
}

auto DisplayModeController::getPendingMode(PhysicalDisplayId displayId) const
        -> DisplayModeRequestOpt {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();

    if (FlagManager::getInstance().modeset_state_machine()) {
        return displayPtr->pendingModeOpt;
    }

    {
        std::scoped_lock lock(displayPtr->desiredModeLock);
        return displayPtr->pendingModeOpt;
    }
}

bool DisplayModeController::isModeSetPending(PhysicalDisplayId displayId) const {
    if (FlagManager::getInstance().modeset_state_machine()) {
        return getPendingMode(displayId).has_value();
    }

    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();

    {
        std::scoped_lock lock(displayPtr->desiredModeLock);
        return displayPtr->isModeSetPending;
    }
}

scheduler::FrameRateMode DisplayModeController::getActiveMode(PhysicalDisplayId displayId) const {
    return selectorPtrFor(displayId)->getActiveMode();
}

auto DisplayModeController::takeDesiredModeIfMatches(PhysicalDisplayId displayId,
                                                     ui::Size expectedResolution)
        -> DisplayModeRequestOpt {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();

    DisplayModeRequestOpt desiredModeOpt;
    {
        std::scoped_lock lock(displayPtr->desiredModeLock);

        if (FlagManager::getInstance().synced_resolution_switch()) {
            if (const auto modeOpt = displayPtr->desiredModeOpt.transform(
                        [](const auto& request) { return request.mode; })) {
                const bool resolutionMatch =
                        displayPtr->selectorPtr->getActiveMode().matchesResolution(*modeOpt);

                if (!resolutionMatch) {
                    if (expectedResolution == modeOpt->modePtr->getResolution()) {
                        displayPtr->desiredModeOpt->force = true;
                    } else {
                        // When initiating a resolution change, wait until the commit that resizes
                        // the display.
                        return std::nullopt;
                    }
                }
            }
        }

        std::swap(displayPtr->desiredModeOpt, desiredModeOpt);
        displayPtr->hasDesiredModeTrace = false;
    }
    return desiredModeOpt;
}

void DisplayModeController::clearDesiredMode(PhysicalDisplayId displayId) {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();

    {
        std::scoped_lock lock(displayPtr->desiredModeLock);
        displayPtr->desiredModeOpt.reset();
        displayPtr->hasDesiredModeTrace = false;
    }
}

auto DisplayModeController::initiateModeChange(
        PhysicalDisplayId displayId, DisplayModeRequest&& desiredMode,
        const hal::VsyncPeriodChangeConstraints& constraints,
        hal::VsyncPeriodChangeTimeline& outTimeline) -> ModeChangeResult {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(ModeChangeResult::Aborted)).get();

    if (!FlagManager::getInstance().modeset_state_machine()) {
        displayPtr->isModeSetPending = true;

        // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not
        // cleared until the next `SF::initiateDisplayModeChanges`. However, the desired mode has
        // been consumed at this point, so clear the `force` flag to prevent an endless loop of
        // `initiateModeChange`.
        std::scoped_lock lock(displayPtr->desiredModeLock);
        if (displayPtr->desiredModeOpt) {
            displayPtr->desiredModeOpt->force = false;
        }
    }

    ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str());
    displayPtr->pendingModeOpt = std::move(desiredMode);

    const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr;

    const auto error = mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(),
                                                                  constraints, &outTimeline);
    switch (error) {
        case FAILED_TRANSACTION:
            return ModeChangeResult::Rejected;
        case OK:
            SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
            return ModeChangeResult::Changed;
        default:
            return ModeChangeResult::Aborted;
    }
}

void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId,
                                               Fps vsyncRate, Fps renderFps) {
    std::lock_guard lock(mDisplayLock);
    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);

    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
    displayPtr->isModeSetPending = false;
}

auto DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId) -> ModeChange {
    std::lock_guard lock(mDisplayLock);

    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(NoModeChange{"Unknown display"})).get();
    if (!displayPtr->pendingModeOpt) return NoModeChange{"No pending mode"};

    auto pendingMode = *std::exchange(displayPtr->pendingModeOpt, std::nullopt);
    auto& pendingModePtr = pendingMode.mode.modePtr;

    if (!displayPtr->selectorPtr->displayModes().contains(pendingModePtr->getId())) {
        return NoModeChange{"Unknown pending mode"};
    }

    const bool resolutionMatch =
            displayPtr->selectorPtr->getActiveMode().matchesResolution(pendingMode.mode);

    setActiveModeLocked(displayId, pendingModePtr->getId(), pendingModePtr->getVsyncRate(),
                        pendingMode.mode.fps);

    if (resolutionMatch) {
        return RefreshRateChange{std::move(pendingMode)};
    } else {
        return ResolutionChange{std::move(pendingMode)};
    }
}

void DisplayModeController::setActiveMode(PhysicalDisplayId displayId, DisplayModeId modeId,
                                          Fps vsyncRate, Fps renderFps) {
    std::lock_guard lock(mDisplayLock);
    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);
}

void DisplayModeController::setActiveModeLocked(PhysicalDisplayId displayId, DisplayModeId modeId,
                                                Fps vsyncRate, Fps renderFps) {
    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();

    SFTRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
    SFTRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());

    displayPtr->selectorPtr->setActiveMode(modeId, renderFps);

    if (mActiveModeListener) {
        mActiveModeListener(displayId, vsyncRate, renderFps);
    }
}

void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId) {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();

    const auto controllerOpt = displayPtr->selectorPtr->kernelIdleTimerController();
    if (!controllerOpt) return;

    using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction;

    switch (displayPtr->selectorPtr->getIdleTimerAction()) {
        case KernelIdleTimerAction::TurnOff:
            if (displayPtr->isKernelIdleTimerEnabled) {
                SFTRACE_INT("KernelIdleTimer", 0);
                updateKernelIdleTimer(displayId, std::chrono::milliseconds::zero(), *controllerOpt);
                displayPtr->isKernelIdleTimerEnabled = false;
            }
            break;
        case KernelIdleTimerAction::TurnOn:
            if (!displayPtr->isKernelIdleTimerEnabled) {
                SFTRACE_INT("KernelIdleTimer", 1);
                const auto timeout = displayPtr->selectorPtr->getIdleTimerTimeout();
                updateKernelIdleTimer(displayId, timeout, *controllerOpt);
                displayPtr->isKernelIdleTimerEnabled = true;
            }
            break;
    }
}

void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId,
                                                  std::chrono::milliseconds timeout,
                                                  KernelIdleTimerController controller) {
    switch (controller) {
        case KernelIdleTimerController::HwcApi:
            mComposerPtr->setIdleTimerEnabled(displayId, timeout);
            break;

        case KernelIdleTimerController::Sysprop:
            using namespace std::string_literals;
            base::SetProperty("graphics.display.kernel_idle_timer.enabled"s,
                              timeout > std::chrono::milliseconds::zero() ? "true"s : "false"s);
            break;
    }
}

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
auto DisplayModeController::getKernelIdleTimerState(PhysicalDisplayId displayId) const
        -> KernelIdleTimerState {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr =
            FTL_EXPECT(mDisplays.get(displayId).ok_or(KernelIdleTimerState())).get();

    const auto desiredModeIdOpt =
            (std::scoped_lock(displayPtr->desiredModeLock), displayPtr->desiredModeOpt)
                    .transform([](const display::DisplayModeRequest& request) {
                        return request.mode.modePtr->getId();
                    });

    return {desiredModeIdOpt, displayPtr->isKernelIdleTimerEnabled};
}

bool DisplayModeController::supportsHdcp() const {
    return mSupportsHdcp && FlagManager::getInstance().hdcp_level_hal() &&
            FlagManager::getInstance().hdcp_negotiation();
}

void DisplayModeController::startHdcpNegotiation(PhysicalDisplayId displayId) {
    using aidl::android::hardware::drm::HdcpLevel;
    using aidl::android::hardware::drm::HdcpLevels;
    constexpr HdcpLevels kLevels = {.connectedLevel = HdcpLevel::HDCP_V2_1,
                                    .maxLevel = HdcpLevel::HDCP_V2_3};

    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
    if (displayPtr->hdcpState == HdcpState::Desired) {
        const auto status = mComposerPtr->startHdcpNegotiation(displayId, kLevels);
        displayPtr->hdcpState = (status == NO_ERROR) ? HdcpState::Enabled : HdcpState::Undesired;
    }
}

void DisplayModeController::setSecure(PhysicalDisplayId displayId, bool secure) {
    std::lock_guard lock(mDisplayLock);
    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
    displayPtr->setSecure(secure);
}

#pragma clang diagnostic pop
} // namespace android::display
