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

#pragma once

#include "BinderGenericUtils.h"

#include <android-base/thread_annotations.h>
#include <audio_utils/mutex.h>
#include <chrono>
#include <map>
#include <mutex>
#include <utils/Log.h>
#include <utils/Timers.h>

/**
 * ServiceSingleton provides a non-blocking NDK/CPP compatible service cache.
 *
 * This is a specialized cache that allows per-service configuration.
 *
 * Features:
 *
 * 1) Seamless compatibility with NDK and CPP based interfaces.
 * 2) Time-out based service acquisition.
 *    Set the maximum time to wait for any service.
 * 3) Service prefetch:
 *    Reduce start-up by prefetching service in advance (not on demand).
 *    Prefetch is automatically installed by getService().
 * 4) Manual interface setting for test and non-service manager acquisition support.
 *
 * If both NDK and CPP interfaces are available, we prefer the CPP version
 * for the following reasons:
 * 1) Established sp<> reference counting avoids mistakes. NDK tends to be error-prone.
 * 2) Possible reduced binder object clutter by a singleton notification binder object.
 *    Fewer binder objects are more efficient for the binder driver and ServiceManager.
 *    For example, fewer binder deaths means less ServiceManager (linear time) cleanup.
 *    A single binder object also offers binder access serialization.
 * 3) CPP offers slightly better efficiency as it is closer to the
 *    actual implementation, a minor detail and effect.
 *
 * We use a per-service ServiceHandler object to collect methods and implementation details.
 * Currently this is separate for NDK and CPP interfaces to the same service;
 * unification is possible by using ibinder_internals.h.
 */
namespace android::mediautils {

enum ServiceOptions {
    kNone = 0,
    kNonNull = (1 << 0),  // don't return a null interface unless disabled.
                          // partially implemented and experimental.
};

// Traits may come through a constexpr static function collection.
// This participates in small buffer optimization SBO in std::function impl.
template <typename Service>
struct DefaultServiceTraits {
    // getServiceName() returns the name associated with Service.
    //
    // If name is empty, it returns the name from the Service descriptor.
    // If name starts with '/', it appends the name as a version to the Service descriptor,
    // e.g. "/default".
    // Otherwise the name is assumed to be the Service name.
    static constexpr const char* getServiceName() { return "/default"; }

    // This callback is called when a new service is received.
    // The callback requires at least one thread in the Binder threadpool.
    static constexpr void onNewService(const InterfaceType<Service>&) {}

    // This callback is called if the service has died.
    // The callback requires at least one thread in the Binder threadpool.
    static constexpr void onServiceDied(const InterfaceType<Service>&) {}

    // ServiceOptions configured for the Service.
    static constexpr ServiceOptions options() { return ServiceOptions::kNone; }
};

// We store the traits as functors.
template <typename Service>
struct FunctionalServiceTraits {
    template <typename ServiceTraits>
    explicit FunctionalServiceTraits(const ServiceTraits& serviceTraits)
        : getServiceName{serviceTraits.getServiceName}
        , onNewService{serviceTraits.onNewService}
        , onServiceDied{serviceTraits.onServiceDied}
        , options{serviceTraits.options} {
    }
    std::function<const char*()> getServiceName;
    std::function<void(const InterfaceType<Service>& service)> onNewService;
    std::function<void(const InterfaceType<Service>& service)> onServiceDied;
    std::function<ServiceOptions()> options;
};

namespace details {

class ServiceHandler
{
public:
    /**
     * Returns a ServiceHandler, templated type T is String16 for the native type
     * of the CPP service descriptors and const char* for the native type of the NDK
     * service descriptors.
     */
    template<typename T>
    requires (std::is_same_v<T, const char*> || std::is_same_v<T, String16>)
    static std::shared_ptr<ServiceHandler> getInstance(const T& name);

    /**
     * Initializes the service handler with new service traits
     * (methods that are triggered on service events).
     *
     * This is optional.  Default construction of traits is allowed for
     * services that do not require special handling.
     *
     * @param serviceTraits
     * @return true if the service handler had been previously initialized.
     */
    template<typename Service, typename ServiceTraits>
    bool init(const ServiceTraits& serviceTraits) {
        auto traits = std::make_shared<FunctionalServiceTraits<Service>>(serviceTraits);
        std::shared_ptr<void> oldTraits;
        std::lock_guard l(mMutex);
        std::swap(oldTraits, mTraits);
        const bool existing = oldTraits != nullptr;
        mTraits = std::move(traits);
        mSkip = false;
        return existing;
    }

    /**
     * Returns the service based on a timeout.
     *
     * @param waitNs the time to wait, internally clamped to (0, INT64_MAX / 2) to
     *       avoid numeric overflow.
     * @param useCallback installs a callback instead of polling.
     *       the Callback persists if the call timeouts.  A Callback requires
     *       at least one thread in the threadpool.
     * @return Service interface.
     */
    template <typename Service>
    auto get(std::chrono::nanoseconds waitNs, bool useCallback) {
        audio_utils::unique_lock ul(mMutex);
        auto& service = std::get<BaseInterfaceType<Service>>(mService);

        if (mSkip || (service && mValid)) return service;  // early check.

        // clamp to avoid numeric overflow.  INT64_MAX / 2 is effectively forever for a device.
        std::chrono::nanoseconds kWaitLimitNs(
                std::numeric_limits<decltype(waitNs.count())>::max() / 2);
        waitNs = std::clamp(waitNs, decltype(waitNs)(0), kWaitLimitNs);
        const auto end = std::chrono::steady_clock::now() + waitNs;

        for (bool first = true; true; first = false) {
            // we may have released mMutex, so see if service has been obtained.
            if (mSkip || (service && mValid))  return service;

            const auto traits = getTraits_l<Service>();

            // first time or not using callback, check the service.
            if (first || !useCallback) {
                auto service_new = checkServicePassThrough<Service>(
                        traits->getServiceName());
                if (service_new) {
                    mValid = true;
                    service = std::move(service_new);
                    setDeathNotifier_l<Service>();
                    auto service_fixed = service;  // we're releasing the mutex.
                    ul.unlock();
                    traits->onNewService(interfaceFromBase<Service>(service_fixed));
                    mCv.notify_all();
                    return service_fixed;
                }
            }

            // install service callback if needed.
            if (useCallback && !mServiceNotificationHandle) {
                setServiceNotifier_l<Service>();
            }

            // check time expiration.
            const auto now = std::chrono::steady_clock::now();
            if (now >= end
                && (service || !(traits->options() & ServiceOptions::kNonNull))) {
                return service;
            }

            // compute time to wait, then wait.
            if (mServiceNotificationHandle) {
                mCv.wait_until(ul, end);
            } else {
                const auto target = now + kPollTime;
                mCv.wait_until(ul, std::min(target, end));
            }
            // loop back to see if we have any state change.
        }
    }

    /**
     * Sets an externally provided service override.
     *
     * @param Service
     * @param service_new
     */
    template<typename Service>
    void set(const InterfaceType<Service>& service_new) {
        audio_utils::unique_lock ul(mMutex);
        auto& service = std::get<BaseInterfaceType<Service>>(mService);
        const auto traits = getTraits_l<Service>();
        if (service) {
            auto orig_service = service;
            invalidateService_l<Service>();
            ul.unlock();
            traits->onServiceDied(interfaceFromBase<Service>(orig_service));
        }
        service = service_new;
        ul.unlock();
        // should we set the death notifier?  It could be a local service.
        if (service_new) traits->onNewService(service_new);
        mCv.notify_all();
    }

    /**
     * Disables cache management in the ServiceHandler.  init() needs to be
     * called to restart.
     *
     * All notifiers removed.
     * Service pointer is released.
     */
    template<typename Service>
    void skip() {
        audio_utils::unique_lock ul(mMutex);
        mSkip = true;
        // remove notifiers.  OK to hold lock as presuming notifications one-way
        // or manually triggered outside of lock.
        mDeathNotificationHandle.reset();
        mServiceNotificationHandle.reset();
        auto& service = std::get<BaseInterfaceType<Service>>(mService);
        const auto traits = getTraits_l<Service>();
        std::shared_ptr<void> oldTraits;
        std::swap(oldTraits, mTraits);  // destroyed outside of lock.
        if (service) {
            auto orig_service = service;  // keep reference to service to manually notify death.
            invalidateService_l<Service>();  // sets service to nullptr
            ul.unlock();
            traits->onServiceDied(interfaceFromBase<Service>(orig_service));
        } else {
            ul.unlock();
        }
        mCv.notify_all();
    }

private:

    // invalidateService_l is called to remove the old death notifier,
    // invalidate the service, and optionally clear the service pointer.
    template <typename Service>
    void invalidateService_l() REQUIRES(mMutex) {
        mDeathNotificationHandle.reset();
        const auto traits = getTraits_l<Service>();
        mValid = false;
        if (!(traits->options() & ServiceOptions::kNonNull) || mSkip) {
            auto &service = std::get<BaseInterfaceType<Service>>(mService);
            service = nullptr;
        }
    }

    // gets the traits set by init(), initializes with default if init() not called.
    template <typename Service>
    std::shared_ptr<FunctionalServiceTraits<Service>> getTraits_l() REQUIRES(mMutex) {
        if (!mTraits) {
            mTraits = std::make_shared<FunctionalServiceTraits<Service>>(
                    DefaultServiceTraits<Service>{});
        }
        return std::static_pointer_cast<FunctionalServiceTraits<Service>>(mTraits);
    }

    // sets the service notification
    template <typename Service>
    void setServiceNotifier_l() REQUIRES(mMutex) {
        const auto traits = getTraits_l<Service>();
        mServiceNotificationHandle = requestServiceNotification<Service>(
                [traits, this](const InterfaceType<Service>& service) {
                    audio_utils::unique_lock ul(mMutex);
                    auto originalService = std::get<BaseInterfaceType<Service>>(mService);
                    if (originalService != service) {
                        mService = service;
                        mValid = true;
                        setDeathNotifier_l<Service>();
                        traits->onNewService(service);
                    }
                    ul.unlock();
                    mCv.notify_all();
                }, traits->getServiceName());
        ALOGW_IF(!mServiceNotificationHandle, "%s: cannot register service notification %s"
                                              " (do we have permission?)",
                __func__, toString(Service::descriptor).c_str());
    }

    // sets the death notifier for mService (mService must be non-null).
    template <typename Service>
    void setDeathNotifier_l() REQUIRES(mMutex) {
        auto base = std::get<BaseInterfaceType<Service>>(mService);
        auto service = interfaceFromBase<Service>(base);
        const auto binder = binderFromInterface(service);
        if (binder.get()) {
            auto traits = getTraits_l<Service>();
            mDeathNotificationHandle = requestDeathNotification(
                    base, [traits, service, this]() {
                        // as only one death notification is dispatched,
                        // we do not need to generation count.
                        {
                            std::lock_guard l(mMutex);
                            invalidateService_l<Service>();
                        }
                        traits->onServiceDied(service);
                    });
            ALOGW_IF(!mDeathNotificationHandle, "%s: cannot register death notification %s"
                                                " (already died?)",
                    __func__, toString(Service::descriptor).c_str());
        }
    }

    // initializes the variant for NDK use (called on first creation in the cache map).
    void init_ndk() EXCLUDES(mMutex) {
        std::lock_guard l(mMutex);
        mService = std::shared_ptr<::ndk::ICInterface>{};
    }

    // initializes the variant for CPP use (called on first creation in the cache map).
    void init_cpp() EXCLUDES(mMutex) {
        std::lock_guard l(mMutex);
        mService = sp<::android::IInterface>{};
    }

    static std::string toString(const std::string& s) { return s; }
    static std::string toString(const String16& s) { return String8(s).c_str(); }

    mutable std::mutex mMutex;
    std::condition_variable mCv;
    static constexpr auto kPollTime = std::chrono::seconds(1);

    std::variant<std::shared_ptr<::ndk::ICInterface>,
            sp<::android::IInterface>> mService GUARDED_BY(mMutex);
    // aesthetically we place these last, but a ServiceHandler is never deleted in
    // current operation, so there is no deadlock on destruction.
    std::shared_ptr<void> mDeathNotificationHandle GUARDED_BY(mMutex);
    std::shared_ptr<void> mServiceNotificationHandle GUARDED_BY(mMutex);
    std::shared_ptr<void> mTraits GUARDED_BY(mMutex);

    // mValid is true iff the service is non-null and alive.
    bool mValid GUARDED_BY(mMutex) = false;

    // mSkip indicates that the service is not cached.
    bool mSkip GUARDED_BY(mMutex) = false;
};

} // details

//----------------------------------
// ServiceSingleton API
//

/*
 * Implementation detail:
 *
 * Each CPP or NDK service interface has a unique ServiceHandler that
 * is stored in a singleton cache.  The cache key is based on the service descriptor string
 * so only one version can be chosen.  (The particular version may be changed using
 * ServiceTraits.getName()).
 */

/**
 * Sets the service trait parameters for acquiring the Service interface.
 *
 * If this is not set before the first service fetch, then default service traits are used.
 *
 * @return true if there is a preexisting (including prior default set) traits.
 */
template<typename Service, typename ServiceTraits>
bool initService(const ServiceTraits& serviceTraits = {}) {
    const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
    return serviceHandler->template init<Service>(serviceTraits);
}

/**
 * Returns either a std::shared_ptr<Interface> or sp<Interface>
 * for the AIDL service.  If the service is not available within waitNs,
 * the method will return nullptr
 * (or the previous invalidated service if Service.options() & kNonNull).
 *
 * This method installs a callback to obtain the service, so with waitNs == 0, it may be used to
 * prefetch the service before it is actually needed.
 *
 * @param waitNs wait time for the service to become available.
 * @return
 *    a sp<> for a CPP interface
 *    a std::shared_ptr<> for a NDK interface
 *
 */
template<typename Service>
auto getService(std::chrono::nanoseconds waitNs = {}) {
    const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
    return interfaceFromBase<Service>(serviceHandler->template get<Service>(
            waitNs, true /* useCallback */));
}

/**
 * Returns either a std::shared_ptr<Interface> or sp<Interface>
 * for the AIDL service.  If the service is not available within waitNs,
 * the method will return nullptr
 * (or the previous invalidated service if Service.options() & kNonNull).
 *
 * This method polls to obtain the service, which
 * is useful if the service is restricted due to permissions or
 * one is concerned about ThreadPool starvation.
 *
 * @param waitNs wait time for the service to become available.
 * @return
 *    a sp<> for a CPP interface
 *    a std::shared_ptr<> for a NDK interface
 */
template<typename Service>
auto checkService(std::chrono::nanoseconds waitNs = {}) {
    const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
    return interfaceFromBase<Service>(serviceHandler->template get<Service>(
            waitNs, false /* useCallback */));
}

/**
 * Sets a service implementation override, replacing any fetched service from ServiceManager.
 *
 * An empty service clears the cache.
 */
template<typename Service>
void setService(const InterfaceType<Service>& service) {
    const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
    serviceHandler->template set<Service>(service);
}

/**
 * Disables the service cache.
 *
 * This releases any service and notification callbacks.  After this,
 * another initService() can be called seamlessly.
 */
template<typename Service>
void skipService() {
    const auto serviceHandler = details::ServiceHandler::getInstance(Service::descriptor);
    serviceHandler->template skip<Service>();
}

} // namespace android::mediautils
