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

#include <libradiocompat/CallbackManager.h>

#include <android-base/logging.h>

using namespace std::literals::chrono_literals;

namespace android::hardware::radio::compat {

/**
 * How much setter thread will wait with setting response functions after the last
 * setResponseFunctions call from the framework. Subsequent calls from the framework reset the
 * clock, so this number should be larger than the longest time between setResponseFunctions calls
 * from the framework.
 *
 * Real world measurements with Cuttlefish give <10ms delay between Modem and Data and <2ms delays
 * between all others.
 */
static constexpr auto kDelayedSetterDelay = 100ms;

CallbackManager::CallbackManager(std::shared_ptr<DriverContext> context, sp<V1_5::IRadio> hidlHal)
    : mHidlHal(hidlHal),
      mRadioResponse(sp<compat::RadioResponse>::make(context)),
      mRadioIndication(sp<compat::RadioIndication>::make(context)),
      mDelayedSetterThread(&CallbackManager::delayedSetterThread, this) {}

CallbackManager::~CallbackManager() {
    {
        std::unique_lock<std::mutex> lock(mDelayedSetterGuard);
        mDelayedSetterDeadline = std::nullopt;
        mDestroy = true;
        mDelayedSetterCv.notify_all();
    }
    mDelayedSetterThread.join();
}

RadioResponse& CallbackManager::response() const {
    return *mRadioResponse;
}

RadioIndication& CallbackManager::indication() const {
    return *mRadioIndication;
}

void CallbackManager::setResponseFunctionsDelayed() {
    LOG(INFO) << "CallbackManager: defer setResponseFunctions";
    std::unique_lock<std::mutex> lock(mDelayedSetterGuard);
    mDelayedSetterDeadline = std::chrono::steady_clock::now() + kDelayedSetterDelay;
    mDelayedSetterCv.notify_all();
}

void CallbackManager::delayedSetterThread() {
    // TODO(b/405830123): remove or degrade logs from this file once the race condition is solved
    LOG(INFO) << "CallbackManager: setting up delay thread";
    while (!mDestroy) {
        std::unique_lock<std::mutex> lock(mDelayedSetterGuard);
        auto deadline = mDelayedSetterDeadline;

        // not waiting to set response functions
        if (!deadline) {
            mDelayedSetterCv.wait(lock);
            continue;
        }

        // waiting to set response functions, but not yet
        if (*deadline > std::chrono::steady_clock::now()) {
            LOG(INFO) << "CallbackManager: wait until " << deadline->time_since_epoch().count();
            mDelayedSetterCv.wait_until(lock, *deadline);
            continue;
        }

        LOG(INFO) << "CallbackManager: calling deferred setResponseFunctions";
        mHidlHal->setResponseFunctions(mRadioResponse, mRadioIndication).assertOk();
        mDelayedSetterDeadline = std::nullopt;
    }
    LOG(INFO) << "CallbackManager: delay thread destroyed";
}

}  // namespace android::hardware::radio::compat
