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

#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "Scheduler/VSyncDispatch.h"
#include "mock/MockVSyncDispatch.h"

using namespace testing;

namespace android::scheduler {

class VSyncCallbackRegistrationTest : public Test {
protected:
    VSyncDispatch::Callback mCallback = [](nsecs_t, nsecs_t, nsecs_t) {};

    std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch = std::make_shared<mock::VSyncDispatch>();
    VSyncDispatch::CallbackToken mCallbackToken{7};
    std::string mCallbackName = "callback";

    std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch2 = std::make_shared<mock::VSyncDispatch>();
    VSyncDispatch::CallbackToken mCallbackToken2{42};
    std::string mCallbackName2 = "callback2";

    void assertDispatch(const VSyncCallbackRegistration& registration,
                        std::shared_ptr<VSyncDispatch> dispatch) {
        ASSERT_EQ(registration.mDispatch, dispatch);
    }

    void assertToken(const VSyncCallbackRegistration& registration,
                     const std::optional<VSyncDispatch::CallbackToken>& token) {
        ASSERT_EQ(registration.mToken, token);
    }
};

TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnDestruction) {
    // TODO (b/279581095): With ftl::Function, `_` can be replaced with
    // `mCallback`, here and in other calls to `registerCallback, since the
    // ftl version has an operator==, unlike std::function.
    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
            .WillOnce(Return(mCallbackToken));
    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);

    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
}

TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnPointerMove) {
    {
        InSequence seq;
        EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
                .WillOnce(Return(mCallbackToken));
        EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
                .WillOnce(Return(mCallbackToken2));
        EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
        EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
    }

    auto registration =
            std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch, mCallback, mCallbackName);

    auto registration2 =
            std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch2, mCallback, mCallbackName2);

    registration2 = std::move(registration);

    ASSERT_NO_FATAL_FAILURE(assertDispatch(*registration2.get(), mVsyncDispatch));
    ASSERT_NO_FATAL_FAILURE(assertToken(*registration2.get(), mCallbackToken));
}

TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnMoveOperator) {
    {
        InSequence seq;
        EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
                .WillOnce(Return(mCallbackToken));
        EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
                .WillOnce(Return(mCallbackToken2));
        EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
        EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
    }

    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);

    VSyncCallbackRegistration registration2(mVsyncDispatch2, mCallback, mCallbackName2);

    registration2 = std::move(registration);

    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));

    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
}

TEST_F(VSyncCallbackRegistrationTest, moveConstructor) {
    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
            .WillOnce(Return(mCallbackToken));
    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);

    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
    VSyncCallbackRegistration registration2(std::move(registration));

    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));

    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
}

TEST_F(VSyncCallbackRegistrationTest, moveOperatorEqualsSelf) {
    EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
            .WillOnce(Return(mCallbackToken));
    EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);

    VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);

    // Use a reference so the compiler doesn't realize that registration is
    // being moved to itself.
    VSyncCallbackRegistration& registrationRef = registration;
    registration = std::move(registrationRef);

    ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
    ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
}

} // namespace android::scheduler