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

#include <android-base/threads.h>

#include <functional>
#include <memory>
#include <thread>

namespace android {

#ifdef BINDER_RPC_SINGLE_THREADED
class RpcMutex {
public:
    void lock() {}
    void unlock() {}
};

class RpcMutexUniqueLock {
public:
    RpcMutexUniqueLock(RpcMutex&) {}
    void unlock() {}
};

class RpcMutexLockGuard {
public:
    RpcMutexLockGuard(RpcMutex&) {}
};

class RpcConditionVariable {
public:
    void notify_one() {}
    void notify_all() {}

    void wait(RpcMutexUniqueLock&) {}

    template <typename Predicate>
    void wait(RpcMutexUniqueLock&, Predicate stop_waiting) {
        LOG_ALWAYS_FATAL_IF(!stop_waiting(), "RpcConditionVariable::wait condition not met");
    }

    template <typename Duration>
    std::cv_status wait_for(RpcMutexUniqueLock&, const Duration&) {
        return std::cv_status::no_timeout;
    }

    template <typename Duration, typename Predicate>
    bool wait_for(RpcMutexUniqueLock&, const Duration&, Predicate stop_waiting) {
        return stop_waiting();
    }
};

class RpcMaybeThread {
public:
    RpcMaybeThread() = default;

    template <typename Function, typename... Args>
    RpcMaybeThread(Function&& f, Args&&... args) {
        // std::function requires a copy-constructible closure,
        // so we need to wrap both the function and its arguments
        // in a shared pointer that std::function can copy internally
        struct Vars {
            std::decay_t<Function> f;
            std::tuple<std::decay_t<Args>...> args;

            explicit Vars(Function&& f, Args&&... args)
                  : f(std::move(f)), args(std::move(args)...) {}
        };
        auto vars = std::make_shared<Vars>(std::forward<Function>(f), std::forward<Args>(args)...);
        mFunc = [vars]() { std::apply(std::move(vars->f), std::move(vars->args)); };
    }

    void join() {
        if (mFunc) {
            // Move mFunc into a temporary so we can clear mFunc before
            // executing the callback. This avoids infinite recursion if
            // the callee then calls join() again directly or indirectly.
            decltype(mFunc) func = nullptr;
            mFunc.swap(func);
            func();
        }
    }
    void detach() { join(); }

    class id {
    public:
        bool operator==(const id&) const { return true; }
        bool operator!=(const id&) const { return false; }
        bool operator<(const id&) const { return false; }
        bool operator<=(const id&) const { return true; }
        bool operator>(const id&) const { return false; }
        bool operator>=(const id&) const { return true; }
    };

    id get_id() const { return id(); }

private:
    std::function<void(void)> mFunc;
};

namespace rpc_this_thread {
static inline RpcMaybeThread::id get_id() {
    return RpcMaybeThread::id();
}
} // namespace rpc_this_thread

static inline uint64_t rpcGetThreadId() {
    return 0;
}

static inline void rpcJoinIfSingleThreaded(RpcMaybeThread& t) {
    t.join();
}
#else  // BINDER_RPC_SINGLE_THREADED
using RpcMutex = std::mutex;
using RpcMutexUniqueLock = std::unique_lock<std::mutex>;
using RpcMutexLockGuard = std::lock_guard<std::mutex>;
using RpcConditionVariable = std::condition_variable;
using RpcMaybeThread = std::thread;
namespace rpc_this_thread = std::this_thread;

static inline uint64_t rpcGetThreadId() {
    return base::GetThreadId();
}

static inline void rpcJoinIfSingleThreaded(RpcMaybeThread&) {}
#endif // BINDER_RPC_SINGLE_THREADED

} // namespace android
