/*
 * 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 <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gui/ISurfaceComposer.h>

#include <scheduler/interface/ICompositor.h>

#include "Scheduler/EventThread.h"
#include "Scheduler/LayerHistory.h"
#include "Scheduler/Scheduler.h"
#include "Scheduler/VSyncTracker.h"
#include "Scheduler/VsyncController.h"
#include "Scheduler/VsyncSchedule.h"
#include "fake/FakeClock.h"
#include "mock/MockVSyncDispatch.h"
#include "mock/MockVSyncTracker.h"
#include "mock/MockVsyncController.h"

namespace android {
class TestableSurfaceFlinger;
} // namespace android

namespace android::scheduler {

class TestableScheduler : public Scheduler, private ICompositor {
public:
    TestableScheduler(RefreshRateSelectorPtr selectorPtr,
                      TestableSurfaceFlinger& testableSurfaceFlinger, ISchedulerCallback& callback);

    TestableScheduler(std::unique_ptr<VsyncController> controller,
                      std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
                      surfaceflinger::Factory& factory, TimeStats& timeStats,
                      ISchedulerCallback& schedulerCallback)
          : Scheduler(*this, schedulerCallback,
                      (FeatureFlags)Feature::kContentDetection |
                              Feature::kSmallDirtyContentDetection,
                      factory, selectorPtr->getActiveMode().fps, timeStats) {
        const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
        registerDisplay(displayId, ui::DisplayConnectionType::Internal, std::move(selectorPtr),
                        std::move(controller), std::move(tracker), displayId);

        ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
            // Execute task to prevent broken promise exception on destruction.
            handler->handleMessage(Message());
        });
    }

    MOCK_METHOD(void, scheduleConfigure, (), (override));
    MOCK_METHOD(void, scheduleFrame, (Duration), (override));
    MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));

    void doFrameSignal(ICompositor& compositor, VsyncId vsyncId,
                       TimePoint expectedVsyncTime = TimePoint()) {
        ftl::FakeGuard guard1(kMainThreadContext);
        ftl::FakeGuard guard2(mDisplayLock);
        Scheduler::onFrameSignal(compositor, vsyncId, expectedVsyncTime);
    }

    void setEventThread(Cycle cycle, std::unique_ptr<EventThread> eventThreadPtr) {
        if (cycle == Cycle::Render) {
            mRenderEventThread = std::move(eventThreadPtr);
        } else {
            mLastCompositeEventThread = std::move(eventThreadPtr);
        }
    }

    auto refreshRateSelector() { return pacesetterSelectorPtr(); }

    void registerDisplay(
            PhysicalDisplayId displayId, ui::DisplayConnectionType connectionType,
            RefreshRateSelectorPtr selectorPtr,
            std::optional<PhysicalDisplayId> defaultPacesetterId = {},
            std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
        if (!FlagManager::getInstance().pacesetter_selection() && !defaultPacesetterId) {
            defaultPacesetterId = displayId;
        }
        registerDisplay(displayId, connectionType, std::move(selectorPtr),
                        std::make_unique<mock::VsyncController>(), vsyncTracker,
                        defaultPacesetterId);
    }

    void registerDisplay(PhysicalDisplayId displayId, ui::DisplayConnectionType connectionType,
                         RefreshRateSelectorPtr selectorPtr,
                         std::unique_ptr<VsyncController> controller,
                         std::shared_ptr<VSyncTracker> tracker,
                         std::optional<PhysicalDisplayId> defaultPacesetterId) {
        ftl::FakeGuard guard(kMainThreadContext);
        Scheduler::registerDisplayInternal(displayId, connectionType, std::move(selectorPtr),
                                           std::shared_ptr<VsyncSchedule>(
                                                   new VsyncSchedule(displayId, std::move(tracker),
                                                                     std::make_shared<
                                                                             mock::VSyncDispatch>(),
                                                                     std::move(controller),
                                                                     mockRequestHardwareVsync
                                                                             .AsStdFunction())),
                                           defaultPacesetterId);
    }

    testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;

    bool setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
        ftl::FakeGuard guard(kMainThreadContext);
        return Scheduler::setDisplayPowerMode(displayId, powerMode);
    }

    std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
        return mPacesetterDisplayId;
    }

    bool designatePacesetterDisplay(std::optional<PhysicalDisplayId> displayId = std::nullopt) {
        ftl::FakeGuard guard(kMainThreadContext);
        return Scheduler::designatePacesetterDisplay(displayId);
    }

    std::optional<hal::PowerMode> getDisplayPowerMode(PhysicalDisplayId id) {
        ftl::FakeGuard guard1(kMainThreadContext);
        ftl::FakeGuard guard2(mDisplayLock);
        return mDisplays.get(id).transform(
                [](const Display& display) { return display.powerMode; });
    }

    using Scheduler::resyncAllToHardwareVsync;

    auto& mutableLayerHistory() { return mLayerHistory; }
    auto& mutableAttachedChoreographers() NO_THREAD_SAFETY_ANALYSIS {
        return mAttachedChoreographers;
    }

    size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
        return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
    }

    size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
        return mLayerHistory.mActiveLayerInfos.size();
    }

    bool layerHistoryModeChangePending() const { return mLayerHistory.mModeChangePending; }

    void replaceTouchTimer(int64_t millis,
                           std::function<void(bool isReset)>&& testCallback = nullptr) {
        if (mTouchTimer) {
            mTouchTimer.reset();
        }
        mTouchTimer.emplace(
                "Testable Touch timer", std::chrono::milliseconds(millis),
                [this, testCallback] {
                    touchTimerCallback(TimerState::Reset);
                    if (testCallback != nullptr) {
                        testCallback(true);
                    }
                },
                [this, testCallback] {
                    touchTimerCallback(TimerState::Expired);
                    if (testCallback != nullptr) {
                        testCallback(false);
                    }
                });
        mTouchTimer->start();
    }

    bool isTouchActive() {
        std::lock_guard<std::mutex> lock(mPolicyLock);
        return mPolicy.touch == Scheduler::TouchState::Active;
    }

    void setTouchStateAndIdleTimerPolicy(PhysicalDisplayId displayId, GlobalSignals globalSignals) {
        std::lock_guard<std::mutex> lock(mPolicyLock);
        mPolicy.touch = globalSignals.touch ? TouchState::Active : TouchState::Inactive;
        mPolicy.idleTimers.emplace_or_replace(displayId,
                                              globalSignals.idle ? TimerState::Expired
                                                                 : TimerState::Reset);
    }

    void setPowerTimerPolicy(PhysicalDisplayId displayId, TimerState state) {
        std::lock_guard<std::mutex> lock(mPolicyLock);
        mPolicy.displayPowerTimers.emplace_or_replace(displayId, state);
        mDisplayPowerTimers.try_emplace(displayId,
                                        std::make_unique<OneShotTimer>(
                                                "FakeDisplayPowerTimer",
                                                std::chrono::milliseconds(0), [] {}, [] {}));
    }

    using Scheduler::TimerState;

    using Scheduler::idleTimerCallback;
    using Scheduler::touchTimerCallback;

    void setContentRequirements(std::vector<RefreshRateSelector::LayerRequirement> layers) {
        std::lock_guard<std::mutex> lock(mPolicyLock);
        mPolicy.contentRequirements = std::move(layers);
    }

    using Scheduler::DisplayModeChoice;
    using Scheduler::DisplayModeChoiceMap;

    DisplayModeChoiceMap chooseDisplayModes() NO_THREAD_SAFETY_ANALYSIS {
        return Scheduler::chooseDisplayModes();
    }

    using Scheduler::onDisplayModeChanged;

    void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) {
        auto schedule = getVsyncSchedule(id);
        std::lock_guard<std::mutex> lock(schedule->mHwVsyncLock);
        schedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
                                          : VsyncSchedule::HwVsyncState::Disabled;
    }

    void updateAttachedChoreographers(
            const surfaceflinger::frontend::LayerHierarchy& layerHierarchy,
            Fps displayRefreshRate) {
        Scheduler::updateAttachedChoreographers(layerHierarchy, displayRefreshRate);
    }

    fake::FakeClock* injectFakeClock() {
        std::unique_ptr<fake::FakeClock> fakeClock = std::make_unique<fake::FakeClock>();
        fake::FakeClock* fakeClockPtr = fakeClock.get();
        mClock = std::move(fakeClock);
        return fakeClockPtr;
    }

    static void setPresentFenceForFrameTargeter(FrameTargeter* target, sp<Fence> presentFence,
                                                FenceTimePtr presentFenceTime) {
        target->setPresentFence(presentFence, presentFenceTime);
    }

    using Scheduler::onHardwareVsyncRequest;

private:
    // ICompositor overrides:
    void configure() override {}
    bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override { return false; }
    CompositeResultsPerDisplay composite(PhysicalDisplayId,
                                         const scheduler::FrameTargeters&) override {
        return {};
    }
    void sample() override {}
    void sendNotifyExpectedPresentHint(PhysicalDisplayId) override {}
};

} // namespace android::scheduler
