#include <jni.h>
#include <windows.h>  // For dynamic loading of DLL
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <regex>
#include "com_kyber_quantum_QuantumWrapper.h"
#include "KYBER_API.h" // Your provided KYBER API header

#define PROJECT_ROOT getenv("PROJECT_ROOT")
#define LIB_PATH "\\main\\resources\\libs\\lib_kyber.dll" // Update this to your actual DLL path relative to PROJECT_ROOT

// Function pointer typedefs
typedef void (*keygen_t)(uint8_t[], uint8_t[]);
typedef void (*encrypt_t)(uint8_t[], uint8_t[], uint16_t*);
typedef void (*decrypt_t)(uint8_t[], uint8_t[], uint16_t*);

// Shared library handle and function pointers
static HINSTANCE libHandle = nullptr;
static keygen_t keygenerationPtr = nullptr;
static encrypt_t encryptionPtr = nullptr;
static decrypt_t decryptionPtr = nullptr;

// Logging helper
void log(const std::string& msg) {
    std::cerr << "[JNI LOG]: " << msg << std::endl;
}

// Error handling helper: Directly throw a Java exception
void throwJavaException(JNIEnv* env, const char* message) {
    jclass exceptionClass = env->FindClass("java/lang/Exception");
    if (exceptionClass != nullptr) {
        env->ThrowNew(exceptionClass, message);
    }
}

// Function to check if a string is a valid hex
bool isValidHex(const std::string& hex) {
    return std::regex_match(hex, std::regex("^[0-9a-fA-F]*$"));
}

// Function to convert uint8_t array to hex string
std::string uint8ArrayToHex(uint8_t* array, size_t size) {
    std::stringstream ss;
    for (size_t i = 0; i < size; i++) {
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)array[i];
    }
    return ss.str();
}

// Function to convert hex string back to uint8_t array
void hexToUint8Array(const std::string& hex, uint8_t* array, size_t size) {
    for (size_t i = 0; i < size; i++) {
        array[i] = (uint8_t)std::stoi(hex.substr(2 * i, 2), nullptr, 16);
    }
}

// Function to convert uint16_t array to hex string
std::string uint16ArrayToHex(uint16_t* array, size_t size) {
    std::stringstream ss;
    for (size_t i = 0; i < size; i++) {
        ss << std::hex << std::setw(4) << std::setfill('0') << array[i]; // 4 characters per uint16_t
    }
    return ss.str();
}

// JNI_OnLoad: Load the shared library and resolve function pointers once
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    log("Loading the shared library...");

    std::string libFullPath = std::string(PROJECT_ROOT) + LIB_PATH;
    log("Library path: " + libFullPath);

    libHandle = LoadLibrary(libFullPath.c_str());

    if (!libHandle) {
        log("Failed to load library: " + std::to_string(GetLastError()));
        return JNI_ERR; // Return error if library cannot be loaded
    } else {
        printf("kyber lib loaded\n");
    }

    // Resolve function pointers
    keygenerationPtr = (keygen_t)GetProcAddress(libHandle, "keygeneration");
    encryptionPtr = (encrypt_t)GetProcAddress(libHandle, "encryption");
    decryptionPtr = (decrypt_t)GetProcAddress(libHandle, "decryption");

    if (!keygenerationPtr || !encryptionPtr || !decryptionPtr) {
        log("Failed to resolve functions: " + std::to_string(GetLastError()));
        FreeLibrary(libHandle);
        return JNI_ERR;
    }

    log("Library loaded and functions resolved successfully.");
    return JNI_VERSION_1_6; // Ensure to return the correct JNI version
}

// JNI_OnUnload: Unload the shared library and clean up
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
    log("Unloading the shared library...");

    if (libHandle) {
        FreeLibrary(libHandle);
        libHandle = nullptr;
        keygenerationPtr = nullptr;
        encryptionPtr = nullptr;
        decryptionPtr = nullptr;
    }

    log("Library unloaded successfully.");
}

extern "C" {

// Key generation
JNIEXPORT jobjectArray JNICALL Java_com_kyber_quantum_QuantumWrapper_keygeneration
(JNIEnv* env, jclass clazz) {
    if (!keygenerationPtr) {
        throwJavaException(env, "Key generation function not loaded.");
        return nullptr;
    }

    // Allocate arrays for public and secret keys
    uint8_t ekpke[1568];
    uint8_t dkpke[1536];

    try {
        // Call the keygeneration function
        keygenerationPtr(ekpke, dkpke);

        // Convert keys to hex strings
        std::string ekpkeHex = uint8ArrayToHex(ekpke, sizeof(ekpke));
        std::string dkpkeHex = uint8ArrayToHex(dkpke, sizeof(dkpke));

        // Create a Java string array to return
        jobjectArray keyPair = env->NewObjectArray(2, env->FindClass("java/lang/String"), env->NewStringUTF(""));
        env->SetObjectArrayElement(keyPair, 0, env->NewStringUTF(ekpkeHex.c_str()));
        env->SetObjectArrayElement(keyPair, 1, env->NewStringUTF(dkpkeHex.c_str()));

        log("Key generation completed successfully.");
        return keyPair;
    } catch (std::exception& e) {
        log("Exception during key generation: " + std::string(e.what()));
        throwJavaException(env, "Error during key generation.");
        return nullptr;
    }
}

JNIEXPORT jstring JNICALL Java_com_kyber_quantum_QuantumWrapper_encryption
(JNIEnv* env, jclass clazz, jstring ekpkeHex, jstring messageHex) {
    // Convert Java strings to C++ strings
    const char* ekpkeHexCStr = env->GetStringUTFChars(ekpkeHex, nullptr);
    const char* messageHexCStr = env->GetStringUTFChars(messageHex, nullptr);

    // Check if the keys are in valid hex format
    if (!isValidHex(ekpkeHexCStr)) {
        throwJavaException(env, ("Invalid hex format for keys: " + std::string(ekpkeHexCStr)).c_str());
        return nullptr;
    }

    // Check if the message is in valid hex format
    if (!isValidHex(messageHexCStr)) {
        throwJavaException(env, ("Invalid hex format for message: " + std::string(messageHexCStr)).c_str());
        return nullptr;
    }

    // Log the original message in hex format
    log("Original message (hex): " + std::string(messageHexCStr));

    // Prepare the encryption process
    try {
        uint8_t ekpke[1568];
        uint8_t c[1568]; // Ciphertext array

        // Convert hex to uint8_t array for keys
        hexToUint8Array(ekpkeHexCStr, ekpke, sizeof(ekpke));

        // Convert hex string to uint8_t array for the message
        size_t messageSize = strlen(messageHexCStr) / 2; // Each byte is represented by 2 hex characters
        std::vector<uint8_t> messageBytes(messageSize);
        hexToUint8Array(messageHexCStr, messageBytes.data(), messageSize); // Convert hex to uint8_t array

        // Convert uint8_t array to uint16_t array
        size_t messageSizeInUint16 = (messageSize + 1) / 2; // Each uint16_t is represented by 2 uint8_t
        std::vector<uint16_t> message(messageSizeInUint16);
        for (size_t i = 0; i < messageSizeInUint16; ++i) {
            message[i] = (static_cast<uint16_t>(messageBytes[2 * i]) << 8) | (i * 2 + 1 < messageSize ? messageBytes[2 * i + 1] : 0);
        }

        // Call the encryption function
        encryptionPtr(ekpke, c, message.data()); // Call the encryption function

        // Convert ciphertext to hex string
        std::string ciphertextHex = uint8ArrayToHex(c, sizeof(c));
        log("Generated ciphertext (hex): " + ciphertextHex); // Log the generated ciphertext

        // Release the Java strings
        env->ReleaseStringUTFChars(ekpkeHex, ekpkeHexCStr);
        env->ReleaseStringUTFChars(messageHex, messageHexCStr);

        log("Encryption completed successfully.");
        return env->NewStringUTF(ciphertextHex.c_str());
    } catch (std::exception& e) {
        log("Exception during encryption: " + std::string(e.what()));
        throwJavaException(env, "Error during encryption.");
        return nullptr;
    }
}
JNIEXPORT jstring JNICALL Java_com_kyber_quantum_QuantumWrapper_decryption
(JNIEnv* env, jclass clazz, jstring dkpkeHex, jstring ciphertextHex) {
    // Convert Java strings to C++ strings
    const char* dkpkeHexCStr = env->GetStringUTFChars(dkpkeHex, nullptr);
    const char* ciphertextHexCStr = env->GetStringUTFChars(ciphertextHex, nullptr);

    // Check if the private key is in valid hex format
    if (!isValidHex(dkpkeHexCStr)) {
        throwJavaException(env, ("Invalid hex format for private key: " + std::string(dkpkeHexCStr)).c_str());
        return nullptr;
    }

    // Check if the ciphertext is in valid hex format
    if (!isValidHex(ciphertextHexCStr)) {
        throwJavaException(env, ("Invalid hex format for ciphertext: " + std::string(ciphertextHexCStr)).c_str());
        return nullptr;
    }

    // Prepare the decryption process
    try {
        uint8_t dkpke[32]; // Private key array

        // Assume the decrypted message should be the same size as ciphertext for simplicity,
        // but adjust according to the API you are using
        size_t decryptedMessageSize = 32; // This size needs to be defined correctly
        std::vector<uint16_t> decryptedMessage(decryptedMessageSize / 2); // Use uint16_t array

        // Convert hex to uint8_t array for the private key
        hexToUint8Array(dkpkeHexCStr, dkpke, sizeof(dkpke));

        // Convert hex string to uint8_t array for the ciphertext
        size_t ciphertextSize = strlen(ciphertextHexCStr) / 2; // Each byte is represented by 2 hex characters
        std::vector<uint8_t> c(ciphertextSize);
        hexToUint8Array(ciphertextHexCStr, c.data(), ciphertextSize); // Convert hex to uint8_t array

        // Call the decryption function
        decryptionPtr(dkpke, c.data(), decryptedMessage.data()); // Call the decryption function

        // Convert decrypted message to hex string
        std::string decryptedMessageHex = uint16ArrayToHex(decryptedMessage.data(), decryptedMessage.size());
        log("Decrypted message (hex): " + decryptedMessageHex); // Log the decrypted message

        // Release the Java strings
        env->ReleaseStringUTFChars(dkpkeHex, dkpkeHexCStr);
        env->ReleaseStringUTFChars(ciphertextHex, ciphertextHexCStr);

        log("Decryption completed successfully.");
        return env->NewStringUTF(decryptedMessageHex.c_str());
    } catch (std::exception& e) {
        log("Exception during decryption: " + std::string(e.what()));
        throwJavaException(env, "Error during decryption.");
        return nullptr;
    }
}

} // extern "C"

print whether the original message given to encryption is same as the decrypted message or not
also, print all the conversions happening in the code