// SPDX-License-Identifier: GPL-3.0-only
#include <jni.h>
#include <condition_variable>
#include <cstdint>
#include <mutex>
#include <vector>
#include "htp_n3n.h"

struct AndroidEdge {
    htp_n3n_edge *edge;
    JavaVM *vm;
    jobject protector;
    std::mutex mutex;
    std::condition_variable idle;
    size_t activeCalls = 0;
    bool closing = false;
};

class EdgeCall {
public:
    explicit EdgeCall(AndroidEdge *wrapper) : wrapper_(wrapper) {
        if (!wrapper_) return;
        std::lock_guard<std::mutex> lock(wrapper_->mutex);
        if (wrapper_->closing || !wrapper_->edge) return;
        wrapper_->activeCalls += 1;
        edge_ = wrapper_->edge;
    }

    ~EdgeCall() {
        if (!edge_) return;
        std::lock_guard<std::mutex> lock(wrapper_->mutex);
        wrapper_->activeCalls -= 1;
        if (wrapper_->activeCalls == 0) wrapper_->idle.notify_all();
    }

    htp_n3n_edge *get() const { return edge_; }

private:
    AndroidEdge *wrapper_ = nullptr;
    htp_n3n_edge *edge_ = nullptr;
};

static int protect_socket(void *context, int socketFd) {
    auto *wrapper = static_cast<AndroidEdge *>(context);
    if (!wrapper || !wrapper->vm || !wrapper->protector) return 0;
    JNIEnv *env = nullptr;
    bool attached = false;
    if (wrapper->vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
        if (wrapper->vm->AttachCurrentThread(&env, nullptr) != JNI_OK) return 0;
        attached = true;
    }
    jclass type = env->GetObjectClass(wrapper->protector);
    jmethodID method = env->GetMethodID(type, "protect", "(I)Z");
    const int protectedSocket = method && env->CallBooleanMethod(wrapper->protector, method, socketFd) ? 1 : 0;
    env->DeleteLocalRef(type);
    if (attached) wrapper->vm->DetachCurrentThread();
    return protectedSocket;
}

static AndroidEdge *wrapper(jlong handle) {
    return reinterpret_cast<AndroidEdge *>(handle);
}

static const char *chars(JNIEnv *env, jstring value) {
    return value ? env->GetStringUTFChars(value, nullptr) : nullptr;
}

static void release(JNIEnv *env, jstring value, const char *text) {
    if (value && text) env->ReleaseStringUTFChars(value, text);
}

extern "C" JNIEXPORT jlong JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_create(
    JNIEnv *env, jobject, jobject protector, jstring supernodeValue, jstring communityValue, jstring keyValue,
    jstring addressValue, jstring peerAddressValue, jint prefixLength, jint mtu, jint transport) {
    const char *supernode = chars(env, supernodeValue);
    const char *community = chars(env, communityValue);
    const char *key = chars(env, keyValue);
    const char *address = chars(env, addressValue);
    const char *peerAddress = chars(env, peerAddressValue);
    htp_n3n_config config{supernode, community, key, address, peerAddress,
        static_cast<uint8_t>(prefixLength), static_cast<uint16_t>(mtu),
        static_cast<htp_n3n_transport>(transport)};
    auto *androidEdge = new AndroidEdge{};
    env->GetJavaVM(&androidEdge->vm);
    androidEdge->protector = env->NewGlobalRef(protector);
    htp_n3n_callbacks callbacks{androidEdge, nullptr, protect_socket};
    androidEdge->edge = htp_n3n_create(&config, &callbacks);
    release(env, supernodeValue, supernode);
    release(env, communityValue, community);
    release(env, keyValue, key);
    release(env, addressValue, address);
    release(env, peerAddressValue, peerAddress);
    if (!androidEdge->edge) {
        env->DeleteGlobalRef(androidEdge->protector);
        delete androidEdge;
        return 0;
    }
    return reinterpret_cast<jlong>(androidEdge);
}

extern "C" JNIEXPORT jint JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_start(JNIEnv *, jobject, jlong handle) {
    EdgeCall call(wrapper(handle));
    return call.get() ? htp_n3n_start(call.get()) : -1;
}

extern "C" JNIEXPORT jint JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_state(JNIEnv *, jobject, jlong handle) {
    EdgeCall call(wrapper(handle));
    return static_cast<jint>(call.get() ? htp_n3n_get_state(call.get()) : HTP_N3N_STOPPED);
}

extern "C" JNIEXPORT jint JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_peerPath(JNIEnv *, jobject, jlong handle) {
    EdgeCall call(wrapper(handle));
    return static_cast<jint>(
        call.get() ? htp_n3n_get_peer_path(call.get()) : HTP_N3N_PEER_PATH_UNKNOWN);
}

extern "C" JNIEXPORT jint JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_resolvePeer(
    JNIEnv *env, jobject, jlong handle, jstring peerAddressValue, jint timeoutMs) {
    const char *peerAddress = chars(env, peerAddressValue);
    EdgeCall call(wrapper(handle));
    const int result = call.get() && peerAddress
        ? htp_n3n_resolve_peer(call.get(), peerAddress, timeoutMs)
        : -1;
    release(env, peerAddressValue, peerAddress);
    return result;
}

extern "C" JNIEXPORT jint JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_send(
    JNIEnv *env, jobject, jlong handle, jbyteArray value, jint length) {
    if (!value || length <= 0 || length > env->GetArrayLength(value)) return -1;
    EdgeCall call(wrapper(handle));
    if (!call.get()) return -1;
    jbyte *bytes = env->GetByteArrayElements(value, nullptr);
    if (!bytes) return -1;
    const int result = htp_n3n_send_ip_packet(call.get(),
        reinterpret_cast<uint8_t *>(bytes), static_cast<size_t>(length));
    env->ReleaseByteArrayElements(value, bytes, JNI_ABORT);
    return result;
}

extern "C" JNIEXPORT jbyteArray JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_receive(
    JNIEnv *env, jobject, jlong handle, jint capacity, jint timeoutMs) {
    if (capacity <= 0 || capacity > 65535) return nullptr;
    EdgeCall call(wrapper(handle));
    if (!call.get()) return nullptr;
    std::vector<uint8_t> packet(static_cast<size_t>(capacity));
    size_t length = 0;
    const int result = htp_n3n_receive_ip_packet(call.get(),
        packet.data(), packet.size(), &length, timeoutMs);
    if (result != 1 || length == 0) return nullptr;
    jbyteArray output = env->NewByteArray(static_cast<jsize>(length));
    env->SetByteArrayRegion(output, 0, static_cast<jsize>(length), reinterpret_cast<jbyte *>(packet.data()));
    return output;
}

extern "C" JNIEXPORT void JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_stop(JNIEnv *, jobject, jlong handle) {
    EdgeCall call(wrapper(handle));
    if (call.get()) htp_n3n_stop(call.get());
}

extern "C" JNIEXPORT void JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_destroy(JNIEnv *env, jobject, jlong handle) {
    AndroidEdge *androidEdge = wrapper(handle);
    if (!androidEdge) return;
    htp_n3n_edge *edge = nullptr;
    {
        std::unique_lock<std::mutex> lock(androidEdge->mutex);
        androidEdge->closing = true;
        androidEdge->idle.wait(lock, [androidEdge] { return androidEdge->activeCalls == 0; });
        edge = androidEdge->edge;
        androidEdge->edge = nullptr;
    }
    htp_n3n_destroy(edge);
    env->DeleteGlobalRef(androidEdge->protector);
    delete androidEdge;
}

extern "C" JNIEXPORT jstring JNICALL
Java_net_hivetechnology_o20010_106_vpn_NativeBridge_version(JNIEnv *env, jobject) {
    return env->NewStringUTF(htp_n3n_version());
}
