/*
 * Copyright (C) 2019 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 "AdbDebuggingManager-JNI"

#define LOG_NDEBUG 0

#include <condition_variable>
#include <mutex>
#include <optional>

#include <adb/pairing/pairing_server.h>
#include <android-base/properties.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/utils.h>
#include <utils/Log.h>

namespace android {

// ----------------------------------------------------------------------------
namespace {

struct ServerDeleter {
    void operator()(PairingServerCtx* p) { pairing_server_destroy(p); }
};
using PairingServerPtr = std::unique_ptr<PairingServerCtx, ServerDeleter>;
struct PairingResultWaiter {
    std::mutex mutex_;
    std::condition_variable cv_;
    std::optional<bool> is_valid_;
    PeerInfo peer_info_;

    static void ResultCallback(const PeerInfo* peer_info, void* opaque) {
        auto* p = reinterpret_cast<PairingResultWaiter*>(opaque);
        {
            std::unique_lock<std::mutex> lock(p->mutex_);
            if (peer_info) {
                memcpy(&(p->peer_info_), peer_info, sizeof(PeerInfo));
            }
            p->is_valid_ = (peer_info != nullptr);
        }
        p->cv_.notify_one();
    }
};

PairingServerPtr sServer;
std::unique_ptr<PairingResultWaiter> sWaiter;
} // namespace

static jint native_pairing_start(JNIEnv* env, jobject thiz, jstring javaGuid, jstring javaPassword) {
    // Server-side only sends its GUID on success.
    PeerInfo system_info = { .type = ADB_DEVICE_GUID };

    ScopedUtfChars guid = GET_UTF_OR_RETURN(env, javaGuid);
    memcpy(system_info.data, guid.c_str(), guid.size());

    ScopedUtfChars password = GET_UTF_OR_RETURN(env, javaPassword);

    // Create the pairing server
    sServer = PairingServerPtr(
            pairing_server_new_no_cert(reinterpret_cast<const uint8_t*>(password.c_str()),
                                       password.size(), &system_info, 0));

    sWaiter.reset(new PairingResultWaiter);
    uint16_t port = pairing_server_start(sServer.get(), sWaiter->ResultCallback, sWaiter.get());
    if (port == 0) {
        ALOGE("Failed to start pairing server");
        return -1;
    }

    return port;
}

static void native_pairing_cancel(JNIEnv* /* env */, jclass /* clazz */) {
    if (sServer != nullptr) {
        sServer.reset();
    }
}

static jstring native_pairing_wait(JNIEnv* env, jobject thiz) {
    ALOGI("Waiting for pairing server to complete");
    std::unique_lock<std::mutex> lock(sWaiter->mutex_);
    if (!sWaiter->is_valid_.has_value()) {
        sWaiter->cv_.wait(lock, [&]() { return sWaiter->is_valid_.has_value(); });
    }
    if (!*(sWaiter->is_valid_)) {
        return nullptr;
    }

    char* peer_public_key = reinterpret_cast<char*>(sWaiter->peer_info_.data);
    return env->NewStringUTF(peer_public_key);
}

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

static const JNINativeMethod gPairingThreadMethods[] = {
        /* name, signature, funcPtr */
        {"native_pairing_start", "(Ljava/lang/String;Ljava/lang/String;)I",
         (void*)native_pairing_start},
        {"native_pairing_cancel", "()V", (void*)native_pairing_cancel},
        {"native_pairing_wait", "()Ljava/lang/String;", (void*)native_pairing_wait},
};

int register_android_server_AdbDebuggingManager(JNIEnv* env) {
    return jniRegisterNativeMethods(env, "com/android/server/adb/AdbPairingThread",
                                    gPairingThreadMethods, NELEM(gPairingThreadMethods));
}

} /* namespace android */
