#include <jni.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "secp256k1.h"
#include "secp256k1_ecdh.h"
#include "secp256k1_recovery.h"

JNIEXPORT jlong JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ctx_1clone
        (JNIEnv *env, jclass classObject, jlong ctx_l) {
    const secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;

    jlong ctx_clone_l = (uintptr_t) secp256k1_context_clone(ctx);

    (void) classObject;
    (void) env;

    return ctx_clone_l;

}

JNIEXPORT jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1ContextRandomize
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;

    const unsigned char *seed = (unsigned char *) (*env)->GetDirectBufferAddress(env,
                                                                                 byteBufferObject);

    (void) classObject;

    return secp256k1_context_randomize(ctx, seed);

}

JNIEXPORT void JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1destroy_1context
        (JNIEnv *env, jclass classObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;

    secp256k1_context_destroy(ctx);

    (void) classObject;
    (void) env;
}

JNIEXPORT jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1verify
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint siglen,
         jint publen) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;

    unsigned char *data = (unsigned char *) (*env)->GetDirectBufferAddress(env, byteBufferObject);
    const unsigned char *sigdata = {(unsigned char *) (data + 32)};
    const unsigned char *pubdata = {(unsigned char *) (data + siglen + 32)};

    secp256k1_ecdsa_signature sig;
    secp256k1_pubkey pubkey;

    if (!secp256k1_ecdsa_signature_parse_compact(ctx, &sig, sigdata)) {
        (void) classObject;
        return 1;
    }

    if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen)) {
        (void) classObject;
        return 2;
    }

    if (!secp256k1_ecdsa_verify(ctx, &sig, data, &pubkey)) {
        (void) classObject;
        return 3;
    }

    (void) classObject;
    return 0;
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdsa_1sign
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    unsigned char *data = (unsigned char *) (*env)->GetDirectBufferAddress(env, byteBufferObject);
    unsigned char *secKey = (unsigned char *) (data + 32);

    jobjectArray retArray;
    jbyteArray sigArray, intsByteArray;
    unsigned char intsarray[2];

    secp256k1_ecdsa_signature sig[72];

    int ret = secp256k1_ecdsa_sign(ctx, sig, data, secKey, NULL, NULL);

    unsigned char outputSer[64];
    size_t outputLen = 64;

    int ret2 = ret == 0 ? 1 : 0;

    if (ret) {
        int ret3 = secp256k1_ecdsa_signature_serialize_compact(ctx, outputSer, sig);
        ret2 = ret3 == 0 ? 2 : 0;
        (void) ret3;
    }
    (void) ret;

    intsarray[0] = outputLen;
    intsarray[1] = ret2;
    (void) ret2;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    sigArray = (*env)->NewByteArray(env, (int) outputLen);
    (*env)->SetByteArrayRegion(env, sigArray, 0, (int) outputLen, (jbyte *) outputSer);
    (*env)->SetObjectArrayElement(env, retArray, 0, sigArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}

JNIEXPORT jint JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1seckey_1verify
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    unsigned char *secKey = (unsigned char *) (*env)->GetDirectBufferAddress(env, byteBufferObject);

    (void) classObject;

    return secp256k1_ec_seckey_verify(ctx, secKey);
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ec_1pubkey_1create
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jboolean compressed,
         jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    const unsigned char *secKey = (unsigned char *) (*env)->GetDirectBufferAddress(env,
                                                                                   byteBufferObject);

    secp256k1_pubkey pubkey;

    jobjectArray retArray;
    jbyteArray pubkeyArray, intsByteArray;
    unsigned char intsarray[2];

    int ret = secp256k1_ec_pubkey_create(ctx, &pubkey, secKey);
    unsigned int flags = compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED;

    unsigned char outputSer[65];
    size_t outputLen = 65;

    if (ret) {
        int ret2 = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &pubkey, flags);
        (void) ret2;
    }

    intsarray[0] = outputLen;
    intsarray[1] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    pubkeyArray = (*env)->NewByteArray(env, (int) outputLen);
    (*env)->SetByteArrayRegion(env, pubkeyArray, 0, (int) outputLen, (jbyte *) outputSer);
    (*env)->SetObjectArrayElement(env, retArray, 0, pubkeyArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;

}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1add
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    unsigned char *privkey = (unsigned char *) (*env)->GetDirectBufferAddress(env,
                                                                              byteBufferObject);
    const unsigned char *tweak = (unsigned char *) (privkey + 32);

    jobjectArray retArray;
    jbyteArray privArray, intsByteArray;
    unsigned char intsarray[2];

    int privkeylen = 32;

    int ret = secp256k1_ec_privkey_tweak_add(ctx, privkey, tweak);

    intsarray[0] = privkeylen;
    intsarray[1] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    privArray = (*env)->NewByteArray(env, privkeylen);
    (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte *) privkey);
    (*env)->SetObjectArrayElement(env, retArray, 0, privArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1privkey_1tweak_1mul
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    unsigned char *privkey = (unsigned char *) (*env)->GetDirectBufferAddress(env,
                                                                              byteBufferObject);
    const unsigned char *tweak = (unsigned char *) (privkey + 32);

    jobjectArray retArray;
    jbyteArray privArray, intsByteArray;
    unsigned char intsarray[2];

    int privkeylen = 32;

    int ret = secp256k1_ec_privkey_tweak_mul(ctx, privkey, tweak);

    intsarray[0] = privkeylen;
    intsarray[1] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    privArray = (*env)->NewByteArray(env, privkeylen);
    (*env)->SetByteArrayRegion(env, privArray, 0, privkeylen, (jbyte *) privkey);
    (*env)->SetObjectArrayElement(env, retArray, 0, privArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1add
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
/*  secp256k1_pubkey* pubkey = (secp256k1_pubkey*) (*env)->GetDirectBufferAddress(env, byteBufferObject);*/
    unsigned char *pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject);
    const unsigned char *tweak = (unsigned char *) (pkey + publen);

    jobjectArray retArray;
    jbyteArray pubArray, intsByteArray;
    unsigned char intsarray[2];
    unsigned char outputSer[65];
    size_t outputLen = 65;

    secp256k1_pubkey pubkey;
    int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen);

    if (ret) {
        ret = secp256k1_ec_pubkey_tweak_add(ctx, &pubkey, tweak);
    }

    if (ret) {
        int ret2 = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &pubkey,
                                                 SECP256K1_EC_UNCOMPRESSED);
        (void) ret2;
    }

    intsarray[0] = outputLen;
    intsarray[1] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    pubArray = (*env)->NewByteArray(env, (int) outputLen);
    (*env)->SetByteArrayRegion(env, pubArray, 0, (int) outputLen, (jbyte *) outputSer);
    (*env)->SetObjectArrayElement(env, retArray, 0, pubArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1pubkey_1tweak_1mul
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    unsigned char *pkey = (*env)->GetDirectBufferAddress(env, byteBufferObject);
    const unsigned char *tweak = (unsigned char *) (pkey + publen);

    jobjectArray retArray;
    jbyteArray pubArray, intsByteArray;
    unsigned char intsarray[2];
    unsigned char outputSer[65];
    size_t outputLen = 65;

    secp256k1_pubkey pubkey;
    int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pkey, publen);

    if (ret) {
        ret = secp256k1_ec_pubkey_tweak_mul(ctx, &pubkey, tweak);
    }

    if (ret) {
        int ret2 = secp256k1_ec_pubkey_serialize(ctx, outputSer, &outputLen, &pubkey,
                                                 SECP256K1_EC_UNCOMPRESSED);
        (void) ret2;
    }

    intsarray[0] = outputLen;
    intsarray[1] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    pubArray = (*env)->NewByteArray(env, (int) outputLen);
    (*env)->SetByteArrayRegion(env, pubArray, 0, (int) outputLen, (jbyte *) outputSer);
    (*env)->SetObjectArrayElement(env, retArray, 0, pubArray);

    intsByteArray = (*env)->NewByteArray(env, 2);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 2, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}

JNIEXPORT jobjectArray JNICALL Java_org_bitcoin_NativeSecp256k1_secp256k1_1ecdh
        (JNIEnv *env, jclass classObject, jobject byteBufferObject, jlong ctx_l, jint publen) {
    secp256k1_context *ctx = (secp256k1_context *) (uintptr_t) ctx_l;
    const unsigned char *secdata = (*env)->GetDirectBufferAddress(env, byteBufferObject);
    const unsigned char *pubdata = (const unsigned char *) (secdata + 32);

    jobjectArray retArray;
    jbyteArray outArray, intsByteArray;
    unsigned char intsarray[1];
    secp256k1_pubkey pubkey;
    unsigned char nonce_res[32];
    size_t outputLen = 32;

    int ret = secp256k1_ec_pubkey_parse(ctx, &pubkey, pubdata, publen);

    if (ret) {
        ret = secp256k1_ecdh(
                ctx,
                nonce_res,
                &pubkey,
                secdata,
                NULL,
                NULL
        );
    }

    intsarray[0] = ret;

    retArray = (*env)->NewObjectArray(env, 2,
                                      (*env)->FindClass(env, "[B"),
                                      (*env)->NewByteArray(env, 1));

    outArray = (*env)->NewByteArray(env, (int) outputLen);
    (*env)->SetByteArrayRegion(env, outArray, 0, 32, (jbyte *) nonce_res);
    (*env)->SetObjectArrayElement(env, retArray, 0, outArray);

    intsByteArray = (*env)->NewByteArray(env, 1);
    (*env)->SetByteArrayRegion(env, intsByteArray, 0, 1, (jbyte *) intsarray);
    (*env)->SetObjectArrayElement(env, retArray, 1, intsByteArray);

    (void) classObject;

    return retArray;
}
