/*
 * Copyright (C) 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.
 */

#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <aidl/android/frameworks/vibrator/BnVibratorControlService.h>
#include <aidl/android/frameworks/vibrator/BnVibratorController.h>
#include <aidl/android/frameworks/vibrator/IVibratorControlService.h>
#include <aidl/android/frameworks/vibrator/IVibratorController.h>
#include <aidl/android/frameworks/vibrator/ScaleParam.h>
#include <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <utils/Condition.h>
#include <utils/Mutex.h>

namespace android {

using ::aidl::android::frameworks::vibrator::BnVibratorController;
using ::aidl::android::frameworks::vibrator::BnVibratorControlService;
using ::aidl::android::frameworks::vibrator::IVibratorController;
using ::aidl::android::frameworks::vibrator::IVibratorControlService;
using ::aidl::android::frameworks::vibrator::ScaleParam;
using ::aidl::android::frameworks::vibrator::VibrationParam;
using ::android::getAidlHalInstanceNames;
using ::android::PrintInstanceNameToString;
using ndk::SpAIBinder;
using ::testing::Eq;
using ::testing::InitGoogleTest;
using ::testing::TestWithParam;
using ::testing::ValuesIn;

VibrationParam generateVibrationParam(int in_typesMask, float scale) {
    ScaleParam scaleParam = ScaleParam();
    scaleParam.typesMask = in_typesMask;
    scaleParam.scale = scale;
    return VibrationParam(scaleParam);
}

std::vector<VibrationParam> generateVibrationParams(int in_typesMask, float scale) {
    return {generateVibrationParam(in_typesMask, scale)};
}

class VibratorController : public BnVibratorController {
   public:
    ~VibratorController() override = default;

    ndk::ScopedAStatus requestVibrationParams(int in_typesMask,
                                              int64_t in_deadlineElapsedRealtimeMillis,
                                              const ::ndk::SpAIBinder& in_requestToken) override {
        if (in_requestToken == nullptr) {
            LOG(INFO) << "Vibrator controller failed to process a request for vibration params";
            return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT));
        }

        LOG(INFO) << "Vibrator controller received a request for vibration params for type: "
                  << in_typesMask << ", with a timeout of: " << in_deadlineElapsedRealtimeMillis;

        std::shared_ptr<IVibratorControlService> service =
            IVibratorControlService::fromBinder(in_requestToken);

        EXPECT_TRUE(service
                        ->onRequestVibrationParamsComplete(
                            in_requestToken, generateVibrationParams(in_typesMask, /* scale= */ 1))
                        .isOk());

        return ndk::ScopedAStatus::ok();
    }
};

class VibratorControlServiceTest : public ::testing::TestWithParam<std::string> {
   public:
    void SetUp() override {
        SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
        service = IVibratorControlService::fromBinder(binder);
        ASSERT_NE(service, nullptr);
    }

    std::shared_ptr<IVibratorControlService> service;
};

TEST_P(VibratorControlServiceTest, RegisterAndUnregisterVibratorControllerTest) {
    std::shared_ptr<IVibratorController> vibratorController =
        ::ndk::SharedRefBase::make<VibratorController>();

    EXPECT_TRUE(service->registerVibratorController(vibratorController).isOk());

    EXPECT_TRUE(service->unregisterVibratorController(vibratorController).isOk());
}

TEST_P(VibratorControlServiceTest, RegisterAndUnregisterMultipleVibratorControllersTest) {
    std::shared_ptr<IVibratorController> firstController =
        ::ndk::SharedRefBase::make<VibratorController>();
    std::shared_ptr<IVibratorController> secondController =
        ::ndk::SharedRefBase::make<VibratorController>();

    EXPECT_TRUE(service->registerVibratorController(firstController).isOk());
    EXPECT_TRUE(service->registerVibratorController(secondController).isOk());

    EXPECT_TRUE(service->unregisterVibratorController(firstController).isOk());
    EXPECT_TRUE(service->unregisterVibratorController(secondController).isOk());
}

TEST_P(VibratorControlServiceTest, SetAndClearVibrationParamsTest) {
    std::shared_ptr<IVibratorController> vibratorController =
        ::ndk::SharedRefBase::make<VibratorController>();

    EXPECT_TRUE(service->registerVibratorController(vibratorController).isOk());

    // Set empty params
    EXPECT_TRUE(service->setVibrationParams({}, vibratorController->getDefaultImpl()).isOk());

    // Set single param
    EXPECT_TRUE(
        service
            ->setVibrationParams({generateVibrationParam(ScaleParam::TYPE_ALARM, /* scale= */ 1)},
                                 vibratorController->getDefaultImpl())
            .isOk());

    // Set multiple params
    EXPECT_TRUE(service
                    ->setVibrationParams(
                        {generateVibrationParam(ScaleParam::TYPE_ALARM, /* scale= */ 1),
                         generateVibrationParam(ScaleParam::TYPE_RINGTONE, /* scale= */ 1)},
                        vibratorController->getDefaultImpl())
                    .isOk());

    // Clear params
    EXPECT_TRUE(
        service->clearVibrationParams(ScaleParam::TYPE_ALARM, vibratorController->getDefaultImpl())
            .isOk());

    EXPECT_TRUE(service->unregisterVibratorController(vibratorController).isOk());
}

TEST_P(VibratorControlServiceTest, UnregisteredControllerSetAndClearVibrationParamsTest) {
    std::shared_ptr<IVibratorController> vibratorController =
        ::ndk::SharedRefBase::make<VibratorController>();

    EXPECT_TRUE(
        service
            ->setVibrationParams(generateVibrationParams(ScaleParam::TYPE_MEDIA, /* scale= */ 1),
                                 vibratorController->getDefaultImpl())
            .isOk());

    EXPECT_TRUE(
        service->clearVibrationParams(ScaleParam::TYPE_MEDIA, vibratorController->getDefaultImpl())
            .isOk());
}

TEST_P(VibratorControlServiceTest, UnexpectedRequestTokenOnRequestVibrationParamsCompleteTest) {
    std::shared_ptr<IVibratorController> vibratorController =
        ::ndk::SharedRefBase::make<VibratorController>();

    auto unexpectedRequestToken = service->asBinder();

    // Empty params
    EXPECT_TRUE(service->onRequestVibrationParamsComplete(unexpectedRequestToken, {}).isOk());

    // Single param
    EXPECT_TRUE(service
                    ->onRequestVibrationParamsComplete(
                        unexpectedRequestToken,
                        generateVibrationParams(ScaleParam::TYPE_ALARM, /* scale= */ 1))
                    .isOk());

    // Multiple params
    EXPECT_TRUE(service
                    ->onRequestVibrationParamsComplete(
                        unexpectedRequestToken,
                        {generateVibrationParam(ScaleParam::TYPE_ALARM, /* scale= */ 1),
                         generateVibrationParam(ScaleParam::TYPE_RINGTONE, /* scale= */ 1)})
                    .isOk());

    EXPECT_TRUE(service->unregisterVibratorController(vibratorController).isOk());
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VibratorControlServiceTest);

INSTANTIATE_TEST_SUITE_P(
    PerInstance, VibratorControlServiceTest,
    testing::ValuesIn(getAidlHalInstanceNames(IVibratorControlService::descriptor)),
    PrintInstanceNameToString);

int main(int argc, char** argv) {
    InitGoogleTest(&argc, argv);
    ABinderProcess_setThreadPoolMaxThreadCount(/* numThreads= */ 1);
    ABinderProcess_startThreadPool();
    return RUN_ALL_TESTS();
}
}  // namespace android