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

#ifndef ANDROID_OS_VIBRATORHALCONTROLLER_H
#define ANDROID_OS_VIBRATORHALCONTROLLER_H

#include <android-base/thread_annotations.h>
#include <android/hardware/vibrator/IVibrator.h>

#include <vibratorservice/VibratorCallbackScheduler.h>
#include <vibratorservice/VibratorHalWrapper.h>

namespace android {

namespace vibrator {

std::shared_ptr<HalWrapper> connectHal(std::shared_ptr<CallbackScheduler> scheduler);

template <typename T>
using HalFunction = std::function<T(HalWrapper*)>;

// Controller for Vibrator HAL handle.
// This relies on a given Connector to connect to the underlying Vibrator HAL service and reconnects
// after each failed api call. This also ensures connecting to the service is thread-safe.
class HalController {
public:
    using Connector =
            std::function<std::shared_ptr<HalWrapper>(std::shared_ptr<CallbackScheduler>)>;

    HalController() : HalController(std::make_shared<CallbackScheduler>(), &connectHal) {}
    HalController(std::shared_ptr<CallbackScheduler> callbackScheduler, Connector connector)
          : mConnector(connector),
            mConnectedHal(nullptr),
            mCallbackScheduler(std::move(callbackScheduler)) {}
    virtual ~HalController() = default;

    /* Connects to the newest HAL version available, possibly waiting for the registered service to
     * become available. This will automatically be called at the first API usage if it was not
     * manually called beforehand. Calling this manually during the setup phase can avoid slowing
     * the first API call later on. Returns true if any HAL version is available, false otherwise.
     */
    virtual bool init();

    /* Reloads HAL service instance without waiting. This relies on the HAL version found by init()
     * to rapidly reconnect to the specific HAL service, or defers to init() if it was never called.
     */
    virtual void tryReconnect();

    /* Returns info loaded from the connected HAL. This allows partial results to be returned if any
     * of the Info fields has failed, but also retried on any failure.
     */
    Info getInfo() {
        static Info sDefaultInfo = InfoCache().get();
        return apply<Info>([](HalWrapper* hal) { return hal->getInfo(); }, sDefaultInfo, "getInfo");
    }

    /* Calls given HAL function, applying automatic retries to reconnect with the HAL when the
     * result has failed. Parameter functionName is for logging purposes.
     */
    template <typename T>
    HalResult<T> doWithRetry(const HalFunction<HalResult<T>>& halFn, const char* functionName) {
        return apply(halFn, HalResult<T>::unsupported(), functionName);
    }

private:
    static constexpr int MAX_RETRIES = 1;

    Connector mConnector;
    std::mutex mConnectedHalMutex;
    // Shared pointer to allow local copies to be used by different threads.
    std::shared_ptr<HalWrapper> mConnectedHal GUARDED_BY(mConnectedHalMutex);
    // Shared pointer to allow copies to be passed to possible recreated mConnectedHal instances.
    std::shared_ptr<CallbackScheduler> mCallbackScheduler;

    /* Calls given HAL function, applying automatic retries to reconnect with the HAL when the
     * result has failed. Given default value is returned when no HAL is available, and given
     * function name is for logging purposes.
     */
    template <typename T>
    T apply(const HalFunction<T>& halFn, T defaultValue, const char* functionName) {
        if (!init()) {
            ALOGV("Skipped %s because Vibrator HAL is not available", functionName);
            return defaultValue;
        }
        std::shared_ptr<HalWrapper> hal;
        {
            std::lock_guard<std::mutex> lock(mConnectedHalMutex);
            hal = mConnectedHal;
        }

        for (int i = 0; i < MAX_RETRIES; i++) {
            T result = halFn(hal.get());
            if (result.isFailedLogged(functionName)) {
                tryReconnect();
            } else {
                return result;
            }
        }

        return halFn(hal.get());
    }
};

}; // namespace vibrator

}; // namespace android

#endif // ANDROID_OS_VIBRATORHALCONTROLLER_H
