/*
 * Copyright (C) 2020 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 "VibratorManagerHalController"

#include <utils/Log.h>

#include <vibratorservice/VibratorManagerHalController.h>

namespace Aidl = android::hardware::vibrator;

namespace android {

namespace vibrator {

std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler) {
    static bool gHalExists = true;
    if (gHalExists) {
        sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
        if (hal) {
            ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
            return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), hal);
        }
    }

    gHalExists = false;
    return std::make_shared<LegacyManagerHalWrapper>();
}

static constexpr int MAX_RETRIES = 1;

template <typename T>
HalResult<T> ManagerHalController::processHalResult(HalResult<T> result, const char* functionName) {
    if (result.isFailed()) {
        ALOGE("%s failed: %s", functionName, result.errorMessage());
        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
        mConnectedHal->tryReconnect();
    }
    return result;
}

template <typename T>
HalResult<T> ManagerHalController::apply(ManagerHalController::hal_fn<T>& halFn,
                                         const char* functionName) {
    std::shared_ptr<ManagerHalWrapper> hal = nullptr;
    {
        std::lock_guard<std::mutex> lock(mConnectedHalMutex);
        if (mConnectedHal == nullptr) {
            // Init was never called, so connect to HAL for the first time during this call.
            mConnectedHal = mConnector(mCallbackScheduler);

            if (mConnectedHal == nullptr) {
                ALOGV("Skipped %s because VibratorManager HAL is not available", functionName);
                return HalResult<T>::unsupported();
            }
        }
        hal = mConnectedHal;
    }

    HalResult<T> ret = processHalResult(halFn(hal), functionName);
    for (int i = 0; i < MAX_RETRIES && ret.isFailed(); i++) {
        ret = processHalResult(halFn(hal), functionName);
    }

    return ret;
}

// -------------------------------------------------------------------------------------------------

void ManagerHalController::init() {
    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
    if (mConnectedHal == nullptr) {
        mConnectedHal = mConnector(mCallbackScheduler);
    }
}

HalResult<void> ManagerHalController::ping() {
    hal_fn<void> pingFn = [](std::shared_ptr<ManagerHalWrapper> hal) { return hal->ping(); };
    return apply(pingFn, "ping");
}

void ManagerHalController::tryReconnect() {
    std::lock_guard<std::mutex> lock(mConnectedHalMutex);
    if (mConnectedHal == nullptr) {
        mConnectedHal = mConnector(mCallbackScheduler);
    } else {
        mConnectedHal->tryReconnect();
    }
}

HalResult<ManagerCapabilities> ManagerHalController::getCapabilities() {
    hal_fn<ManagerCapabilities> getCapabilitiesFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
        return hal->getCapabilities();
    };
    return apply(getCapabilitiesFn, "getCapabilities");
}

HalResult<std::vector<int32_t>> ManagerHalController::getVibratorIds() {
    hal_fn<std::vector<int32_t>> getVibratorIdsFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
        return hal->getVibratorIds();
    };
    return apply(getVibratorIdsFn, "getVibratorIds");
}

HalResult<std::shared_ptr<HalController>> ManagerHalController::getVibrator(int32_t id) {
    hal_fn<std::shared_ptr<HalController>> getVibratorFn =
            [&](std::shared_ptr<ManagerHalWrapper> hal) { return hal->getVibrator(id); };
    return apply(getVibratorFn, "getVibrator");
}

HalResult<void> ManagerHalController::prepareSynced(const std::vector<int32_t>& ids) {
    hal_fn<void> prepareSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
        return hal->prepareSynced(ids);
    };
    return apply(prepareSyncedFn, "prepareSynced");
}

HalResult<void> ManagerHalController::triggerSynced(
        const std::function<void()>& completionCallback) {
    hal_fn<void> triggerSyncedFn = [&](std::shared_ptr<ManagerHalWrapper> hal) {
        return hal->triggerSynced(completionCallback);
    };
    return apply(triggerSyncedFn, "triggerSynced");
}

HalResult<void> ManagerHalController::cancelSynced() {
    hal_fn<void> cancelSyncedFn = [](std::shared_ptr<ManagerHalWrapper> hal) {
        return hal->cancelSynced();
    };
    return apply(cancelSyncedFn, "cancelSynced");
}

}; // namespace vibrator

}; // namespace android