/*
 * Copyright (C) 2017 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 LOG_TAG "health_hidl_hal_test"

#include <mutex>

#include <VtsHalHidlTargetTestBase.h>
#include <android-base/logging.h>
#include <android/hardware/health/2.0/IHealth.h>
#include <android/hardware/health/2.0/types.h>

using ::testing::AssertionFailure;
using ::testing::AssertionResult;
using ::testing::AssertionSuccess;
using ::testing::VtsHalHidlTargetTestBase;
using ::testing::VtsHalHidlTargetTestEnvBase;

namespace android {
namespace hardware {
namespace health {
namespace V2_0 {

using V1_0::BatteryStatus;

// Test environment for graphics.composer
class HealthHidlEnvironment : public VtsHalHidlTargetTestEnvBase {
   public:
    // get the test environment singleton
    static HealthHidlEnvironment* Instance() {
        static HealthHidlEnvironment* instance = new HealthHidlEnvironment;
        return instance;
    }

    virtual void registerTestServices() override { registerTestService<IHealth>(); }

   private:
    HealthHidlEnvironment() {}

    GTEST_DISALLOW_COPY_AND_ASSIGN_(HealthHidlEnvironment);
};

class HealthHidlTest : public ::testing::VtsHalHidlTargetTestBase {
   public:
    virtual void SetUp() override {
        std::string serviceName = HealthHidlEnvironment::Instance()->getServiceName<IHealth>();
        LOG(INFO) << "get service with name:" << serviceName;
        ASSERT_FALSE(serviceName.empty());
        mHealth = ::testing::VtsHalHidlTargetTestBase::getService<IHealth>(serviceName);
        ASSERT_NE(mHealth, nullptr);
    }

    sp<IHealth> mHealth;
};

class Callback : public IHealthInfoCallback {
   public:
    Return<void> healthInfoChanged(const HealthInfo&) override {
        std::lock_guard<std::mutex> lock(mMutex);
        mInvoked = true;
        mInvokedNotify.notify_all();
        return Void();
    }
    template <typename R, typename P>
    bool waitInvoke(std::chrono::duration<R, P> duration) {
        std::unique_lock<std::mutex> lock(mMutex);
        bool r = mInvokedNotify.wait_for(lock, duration, [this] { return this->mInvoked; });
        mInvoked = false;
        return r;
    }
   private:
    std::mutex mMutex;
    std::condition_variable mInvokedNotify;
    bool mInvoked = false;
};

#define ASSERT_OK(r) ASSERT_TRUE(isOk(r))
#define EXPECT_OK(r) EXPECT_TRUE(isOk(r))
template <typename T>
AssertionResult isOk(const Return<T>& r) {
    return r.isOk() ? AssertionSuccess() : (AssertionFailure() << r.description());
}

#define ASSERT_ALL_OK(r) ASSERT_TRUE(isAllOk(r))
// Both isOk() and Result::SUCCESS
AssertionResult isAllOk(const Return<Result>& r) {
    if (!r.isOk()) {
        return AssertionFailure() << r.description();
    }
    if (static_cast<Result>(r) != Result::SUCCESS) {
        return AssertionFailure() << toString(static_cast<Result>(r));
    }
    return AssertionSuccess();
}

/**
 * Test whether callbacks work. Tested functions are IHealth::registerCallback,
 * unregisterCallback, and update.
 */
TEST_F(HealthHidlTest, Callbacks) {
    using namespace std::chrono_literals;
    sp<Callback> firstCallback = new Callback();
    sp<Callback> secondCallback = new Callback();

    ASSERT_ALL_OK(mHealth->registerCallback(firstCallback));
    ASSERT_ALL_OK(mHealth->registerCallback(secondCallback));

    // registerCallback may or may not invoke the callback immediately, so the test needs
    // to wait for the invocation. If the implementation chooses not to invoke the callback
    // immediately, just wait for some time.
    firstCallback->waitInvoke(200ms);
    secondCallback->waitInvoke(200ms);

    // assert that the first callback is invoked when update is called.
    ASSERT_ALL_OK(mHealth->update());

    ASSERT_TRUE(firstCallback->waitInvoke(1s));
    ASSERT_TRUE(secondCallback->waitInvoke(1s));

    ASSERT_ALL_OK(mHealth->unregisterCallback(firstCallback));

    // clear any potentially pending callbacks result from wakealarm / kernel events
    // If there is none, just wait for some time.
    firstCallback->waitInvoke(200ms);
    secondCallback->waitInvoke(200ms);

    // assert that the second callback is still invoked even though the first is unregistered.
    ASSERT_ALL_OK(mHealth->update());

    ASSERT_FALSE(firstCallback->waitInvoke(200ms));
    ASSERT_TRUE(secondCallback->waitInvoke(1s));

    ASSERT_ALL_OK(mHealth->unregisterCallback(secondCallback));
}

TEST_F(HealthHidlTest, UnregisterNonExistentCallback) {
    sp<Callback> callback = new Callback();
    auto ret = mHealth->unregisterCallback(callback);
    ASSERT_OK(ret);
    ASSERT_EQ(Result::NOT_FOUND, static_cast<Result>(ret)) << "Actual: " << toString(ret);
}

/**
 * Pass the test if:
 *  - Property is not supported (res == NOT_SUPPORTED)
 *  - Result is success, and predicate is true
 * @param res the Result value.
 * @param valueStr the string representation for actual value (for error message)
 * @param pred a predicate that test whether the value is valid
 */
#define EXPECT_VALID_OR_UNSUPPORTED_PROP(res, valueStr, pred) \
    EXPECT_TRUE(isPropertyOk(res, valueStr, pred, #pred))

AssertionResult isPropertyOk(Result res, const std::string& valueStr, bool pred,
                             const std::string& predStr) {
    if (res == Result::SUCCESS) {
        if (pred) {
            return AssertionSuccess();
        }
        return AssertionFailure() << "value doesn't match.\nActual: " << valueStr
                                  << "\nExpected: " << predStr;
    }
    if (res == Result::NOT_SUPPORTED) {
        return AssertionSuccess();
    }
    return AssertionFailure() << "Result is not SUCCESS or NOT_SUPPORTED: " << toString(res);
}

bool verifyStorageInfo(const hidl_vec<struct StorageInfo>& info) {
    for (size_t i = 0; i < info.size(); i++) {
        if (!(0 <= info[i].eol && info[i].eol <= 3 && 0 <= info[i].lifetimeA &&
              info[i].lifetimeA <= 0x0B && 0 <= info[i].lifetimeB && info[i].lifetimeB <= 0x0B)) {
            return false;
        }
    }

    return true;
}

template <typename T>
bool verifyEnum(T value) {
    for (auto it : hidl_enum_iterator<T>()) {
        if (it == value) {
            return true;
        }
    }

    return false;
}

bool verifyHealthInfo(const HealthInfo& health_info) {
    if (!verifyStorageInfo(health_info.storageInfos)) {
        return false;
    }

    using V1_0::BatteryStatus;
    using V1_0::BatteryHealth;

    if (!((health_info.legacy.batteryChargeCounter > 0) &&
          (health_info.legacy.batteryCurrent != INT32_MIN) &&
          (0 <= health_info.legacy.batteryLevel && health_info.legacy.batteryLevel <= 100) &&
          verifyEnum<BatteryHealth>(health_info.legacy.batteryHealth) &&
          (health_info.legacy.batteryStatus != BatteryStatus::UNKNOWN) &&
          verifyEnum<BatteryStatus>(health_info.legacy.batteryStatus))) {
        return false;
    }

    return true;
}

/*
 * Tests the values returned by getChargeCounter(),
 * getCurrentNow(), getCurrentAverage(), getCapacity(), getEnergyCounter(),
 * getChargeStatus(), getStorageInfo(), getDiskStats() and getHealthInfo() from
 * interface IHealth.
 */
TEST_F(HealthHidlTest, Properties) {
    EXPECT_OK(mHealth->getChargeCounter([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, std::to_string(value), value > 0);
    }));
    EXPECT_OK(mHealth->getCurrentNow([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, std::to_string(value), value != INT32_MIN);
    }));
    EXPECT_OK(mHealth->getCurrentAverage([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, std::to_string(value), value != INT32_MIN);
    }));
    EXPECT_OK(mHealth->getCapacity([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, std::to_string(value), 0 <= value && value <= 100);
    }));
    EXPECT_OK(mHealth->getEnergyCounter([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, std::to_string(value), value != INT64_MIN);
    }));
    EXPECT_OK(mHealth->getChargeStatus([](auto result, auto value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(
            result, toString(value),
            value != BatteryStatus::UNKNOWN && verifyEnum<BatteryStatus>(value));
    }));
    EXPECT_OK(mHealth->getStorageInfo([](auto result, auto& value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, toString(value), verifyStorageInfo(value));
    }));
    EXPECT_OK(mHealth->getDiskStats([](auto result, auto& value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, toString(value), true);
    }));
    EXPECT_OK(mHealth->getHealthInfo([](auto result, auto& value) {
        EXPECT_VALID_OR_UNSUPPORTED_PROP(result, toString(value), verifyHealthInfo(value));
    }));
}

}  // namespace V2_0
}  // namespace health
}  // namespace hardware
}  // namespace android

int main(int argc, char** argv) {
    using ::android::hardware::health::V2_0::HealthHidlEnvironment;
    ::testing::AddGlobalTestEnvironment(HealthHidlEnvironment::Instance());
    ::testing::InitGoogleTest(&argc, argv);
    HealthHidlEnvironment::Instance()->init(&argc, argv);
    int status = RUN_ALL_TESTS();
    LOG(INFO) << "Test result = " << status;
    return status;
}