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

#include <android/gui/BnJankListener.h>
#include <binder/IInterface.h>
#include "BackgroundExecutor.h"
#include "Jank/JankTracker.h"

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

namespace android {

namespace {

using namespace testing;

class MockJankListener : public gui::BnJankListener {
public:
    MockJankListener() = default;
    ~MockJankListener() override = default;

    MOCK_METHOD(binder::Status, onJankData, (const std::vector<gui::JankData>& jankData),
                (override));
};

} // anonymous namespace

class JankTrackerTest : public Test {
public:
    JankTrackerTest() {}

    void SetUp() override { mListener = sp<StrictMock<MockJankListener>>::make(); }

    void addJankListener(int32_t layerId) {
        JankTracker::addJankListener(layerId, IInterface::asBinder(mListener));
    }

    void removeJankListener(int32_t layerId, int64_t after) {
        JankTracker::removeJankListener(layerId, IInterface::asBinder(mListener), after);
    }

    void addJankData(int32_t layerId, int jankType) {
        gui::JankData data;
        data.frameVsyncId = mVsyncId++;
        data.jankType = jankType;
        data.frameIntervalNs = 8333333;
        JankTracker::onJankData(layerId, data);
    }

    void flushBackgroundThread() { BackgroundExecutor::getLowPriorityInstance().flushQueue(); }

    size_t listenerCount() { return JankTracker::sListenerCount; }

    std::vector<gui::JankData> getCollectedJankData(int32_t layerId) {
        return JankTracker::getCollectedJankDataForTesting(layerId);
    }

    sp<StrictMock<MockJankListener>> mListener = nullptr;
    int64_t mVsyncId = 1000;
};

TEST_F(JankTrackerTest, jankDataIsTrackedAndPropagated) {
    ASSERT_EQ(listenerCount(), 0u);

    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
            .WillOnce([](const std::vector<gui::JankData>& jankData) {
                EXPECT_EQ(jankData[0].frameVsyncId, 1000);
                EXPECT_EQ(jankData[0].jankType, 1);
                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);

                EXPECT_EQ(jankData[1].frameVsyncId, 1001);
                EXPECT_EQ(jankData[1].jankType, 2);
                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);

                EXPECT_EQ(jankData[2].frameVsyncId, 1002);
                EXPECT_EQ(jankData[2].jankType, 3);
                EXPECT_EQ(jankData[2].frameIntervalNs, 8333333);
                return binder::Status::ok();
            });
    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(2)))
            .WillOnce([](const std::vector<gui::JankData>& jankData) {
                EXPECT_EQ(jankData[0].frameVsyncId, 1003);
                EXPECT_EQ(jankData[0].jankType, 4);
                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);

                EXPECT_EQ(jankData[1].frameVsyncId, 1004);
                EXPECT_EQ(jankData[1].jankType, 5);
                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);

                return binder::Status::ok();
            });

    addJankListener(123);
    addJankData(123, 1);
    addJankData(123, 2);
    addJankData(123, 3);
    JankTracker::flushJankData(123);
    addJankData(123, 4);
    removeJankListener(123, mVsyncId);
    addJankData(123, 5);
    JankTracker::flushJankData(123);
    addJankData(123, 6);
    JankTracker::flushJankData(123);
    removeJankListener(123, 0);

    flushBackgroundThread();
}

TEST_F(JankTrackerTest, jankDataIsAutomaticallyFlushedInBatches) {
    ASSERT_EQ(listenerCount(), 0u);

    // needs to be larger than kJankDataBatchSize in JankTracker.cpp.
    constexpr size_t kNumberOfJankDataToSend = 234;

    size_t jankDataReceived = 0;
    size_t numBatchesReceived = 0;

    EXPECT_CALL(*mListener.get(), onJankData(_))
            .WillRepeatedly([&](const std::vector<gui::JankData>& jankData) {
                jankDataReceived += jankData.size();
                numBatchesReceived++;
                return binder::Status::ok();
            });

    addJankListener(123);
    for (size_t i = 0; i < kNumberOfJankDataToSend; i++) {
        addJankData(123, 0);
    }

    flushBackgroundThread();
    // Check that we got some data, without explicitly flushing.
    EXPECT_GT(jankDataReceived, 0u);
    EXPECT_GT(numBatchesReceived, 0u);
    EXPECT_LT(numBatchesReceived, jankDataReceived); // batches should be > size 1.

    removeJankListener(123, 0);
    JankTracker::flushJankData(123);
    flushBackgroundThread();
    EXPECT_EQ(jankDataReceived, kNumberOfJankDataToSend);
}

TEST_F(JankTrackerTest, jankListenerIsRemovedWhenReturningNullError) {
    ASSERT_EQ(listenerCount(), 0u);

    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
            .WillOnce(Return(binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER)));

    addJankListener(123);
    addJankData(123, 1);
    addJankData(123, 2);
    addJankData(123, 3);
    JankTracker::flushJankData(123);
    addJankData(123, 4);
    addJankData(123, 5);
    JankTracker::flushJankData(123);
    flushBackgroundThread();

    EXPECT_EQ(listenerCount(), 0u);
}

TEST_F(JankTrackerTest, jankDataIsDroppedIfNobodyIsListening) {
    ASSERT_EQ(listenerCount(), 0u);

    addJankData(123, 1);
    addJankData(123, 2);
    addJankData(123, 3);
    flushBackgroundThread();

    EXPECT_EQ(getCollectedJankData(123).size(), 0u);
}

TEST_F(JankTrackerTest, listenerCountTracksRegistrations) {
    ASSERT_EQ(listenerCount(), 0u);

    addJankListener(123);
    addJankListener(456);
    flushBackgroundThread();
    EXPECT_EQ(listenerCount(), 2u);

    removeJankListener(123, 0);
    JankTracker::flushJankData(123);
    removeJankListener(456, 0);
    JankTracker::flushJankData(456);
    flushBackgroundThread();
    EXPECT_EQ(listenerCount(), 0u);
}

TEST_F(JankTrackerTest, listenerCountIsAccurateOnDuplicateRegistration) {
    ASSERT_EQ(listenerCount(), 0u);

    addJankListener(123);
    addJankListener(123);
    flushBackgroundThread();
    EXPECT_EQ(listenerCount(), 1u);

    removeJankListener(123, 0);
    JankTracker::flushJankData(123);
    flushBackgroundThread();
    EXPECT_EQ(listenerCount(), 0u);
}

TEST_F(JankTrackerTest, multipleLayersAreTrackedIndependently) {
    size_t jankDataReceived = 0;
    size_t numBatchesReceived = 0;

    EXPECT_CALL(*mListener.get(), onJankData(_))
            .WillRepeatedly([&](const std::vector<gui::JankData>& jankData) {
                jankDataReceived += jankData.size();
                numBatchesReceived++;
                return binder::Status::ok();
            });
    addJankListener(123);
    addJankListener(321);
    addJankData(123, 1);
    addJankData(123, 2);
    addJankData(123, 3);
    addJankData(321, 4);
    addJankData(321, 5);

    JankTracker::flushJankData(123);
    flushBackgroundThread();
    EXPECT_EQ(numBatchesReceived, 1u);
    EXPECT_EQ(jankDataReceived, 3u);

    JankTracker::flushJankData(321);
    flushBackgroundThread();
    EXPECT_EQ(numBatchesReceived, 2u);
    EXPECT_EQ(jankDataReceived, 5u);
}

} // namespace android
