/**
 * Note:
 * This procedure directly writes the internal representation of BIGNUMs.
 * We do so as there is no direct interface based on Little Endian Integer Arrays.
 * Also note that the same representation is used in the Cordoba Java Implementation of BigIntegers,
 *        whereof certain functionality is still being used.
 */
extern "C" void Java_java_math_NativeBN_litEndInts2bn(JNIEnv* env, jclass, jintArray arr, int len, jboolean neg, jlong ret0) {
  if (!oneValidHandle(env, ret0)) return;
  BIGNUM* ret = toBigNum(ret0);
  bn_check_top(ret);
  if (len > 0) {
    ScopedIntArrayRO scopedArray(env, arr);
    if (scopedArray.get() == NULL) {
      return;
    }

    STATIC_ASSERT(sizeof(BN_ULONG) == sizeof(jint), BN_ULONG_not_32_bit);
    const BN_ULONG* tmpInts = reinterpret_cast<const BN_ULONG*>(scopedArray.get());
    if ((tmpInts != NULL) && (bn_wexpand(ret, len) != NULL)) {
      int i = len; do { i--; ret->d[i] = tmpInts[i]; } while (i > 0);
      ret->top = len;
      ret->neg = neg;
      // need to call this due to clear byte at top if avoiding
      // having the top bit set (-ve number)
      // Basically get rid of top zero ints:
      bn_correct_top(ret);
    } else {
      throwExceptionIfNecessary(env);
    }
  } else { // (len = 0) means value = 0 and sign will be 0, too.
    ret->top = 0;
  }
}
extern "C" void Java_java_math_NativeBN_BN_1generate_1prime_1ex(JNIEnv* env, jclass, jlong ret, int bits,
                                          jboolean safe, jlong add, jlong rem, jlong cb) {
  if (!oneValidHandle(env, ret)) return;
  BN_generate_prime_ex(toBigNum(ret), bits, safe, toBigNum(add), toBigNum(rem),
                       reinterpret_cast<BN_GENCB*>(cb));
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1shift(JNIEnv* env, jclass, jlong r, jlong a, int n) {
  if (!twoValidHandles(env, r, a)) return;
  if (n >= 0) {
    BN_lshift(toBigNum(r), toBigNum(a), n);
  } else {
    BN_rshift(toBigNum(r), toBigNum(a), -n);
  }
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1bin2bn(JNIEnv* env, jclass, jbyteArray arr, int len, jboolean neg, jlong ret) {
  if (!oneValidHandle(env, ret)) return;
  ScopedByteArrayRO bytes(env, arr);
  if (bytes.get() == NULL) {
    return;
  }
  BN_bin2bn(reinterpret_cast<const unsigned char*>(bytes.get()), len, toBigNum(ret));
  if (!throwExceptionIfNecessary(env) && neg) {
    BN_set_negative(toBigNum(ret), true);
  }
}
extern "C" int Java_java_math_NativeBN_BN_1hex2bn(JNIEnv* env, jclass, jlong a0, jstring str) {
  if (!oneValidHandle(env, a0)) return -1;
  ScopedUtfChars chars(env, str);
  if (chars.c_str() == NULL) {
    return -1;
  }
  BIGNUM* a = toBigNum(a0);
  int result = BN_hex2bn(&a, chars.c_str());
  throwExceptionIfNecessary(env);
  return result;
}
/*
 * public static native void EVP_VerifyUpdate(int, byte[], int, int)
 */
static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) {
    // LOGI("NativeCrypto_EVP_VerifyUpdate %x, %x, %d, %d", ctx, buffer, offset, length);

    if (ctx == NULL || buffer == NULL) {
        throwNullPointerException(env);
        return;
    }

    jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
    EVP_VerifyUpdate(ctx, (unsigned char*) (bufferBytes + offset), length);
    env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);

    throwExceptionIfNecessary(env);
}
/*
 * public static native void EVP_DigestReset(int)
 */
static jint NativeCrypto_EVP_DigestBlockSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
    // LOGI("NativeCrypto_EVP_DigestBlockSize");

    if (ctx == NULL) {
        throwNullPointerException(env);
        return -1;
    }

    int result = EVP_MD_CTX_block_size(ctx);

    throwExceptionIfNecessary(env);

    return result;
}
/*
 * public static native void EVP_VerifyFinal(int, byte[], int, int, int)
 */
static int NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length, EVP_PKEY* pkey) {
    // LOGI("NativeCrypto_EVP_VerifyFinal %x, %x, %d, %d %x", ctx, buffer, offset, length, pkey);

    if (ctx == NULL || buffer == NULL || pkey == NULL) {
        throwNullPointerException(env);
        return -1;
    }

    jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
    int result = EVP_VerifyFinal(ctx, (unsigned char*) (bufferBytes + offset), length, pkey);
    env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);

    throwExceptionIfNecessary(env);

    return result;
}
/*
 * public static native int EVP_DigestFinal(int, byte[], int)
 */
static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray hash, jint offset) {
    // LOGI("NativeCrypto_EVP_DigestFinal%x, %x, %d, %d", ctx, hash, offset);

    if (ctx == NULL || hash == NULL) {
        throwNullPointerException(env);
        return -1;
    }

    int result = -1;

    jbyte* hashBytes = env->GetByteArrayElements(hash, NULL);
    EVP_DigestFinal(ctx, (unsigned char*) (hashBytes + offset), (unsigned int*)&result);
    env->ReleaseByteArrayElements(hash, hashBytes, 0);

    throwExceptionIfNecessary(env);

    return result;
}
extern "C" void Java_java_math_NativeBN_putULongInt(JNIEnv* env, jclass, jlong a0, unsigned long long dw, jboolean neg) {
    if (!oneValidHandle(env, a0)) return;
    unsigned int hi = dw >> 32; // This shifts without sign extension.
    int lo = (int)dw; // This truncates implicitly.

    // cf. litEndInts2bn:
    BIGNUM* a = toBigNum(a0);
    bn_check_top(a);
    if (bn_wexpand(a, 2) != NULL) {
      a->d[0] = lo;
      a->d[1] = hi;
      a->top = 2;
      a->neg = neg;
      bn_correct_top(a);
    } else {
      throwExceptionIfNecessary(env);
    }
}
/*
 * public static native void EVP_VerifyInit(int, java.lang.String)
 */
static void NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) {
    // LOGI("NativeCrypto_EVP_VerifyInit");

    if (ctx == NULL || algorithm == NULL) {
        throwNullPointerException(env);
        return;
    }

    const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL);

    const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars));
    env->ReleaseStringUTFChars(algorithm, algorithmChars);

    if (digest == NULL) {
        throwRuntimeException(env, "Hash algorithm not found");
        return;
    }

    EVP_VerifyInit(ctx, digest);

    throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_twosComp2bn(JNIEnv* env, jclass cls, jbyteArray arr, int bytesLen, jlong ret0) {
  if (!oneValidHandle(env, ret0)) return;
  BIGNUM* ret = toBigNum(ret0);

  ScopedByteArrayRO bytes(env, arr);
  if (bytes.get() == NULL) {
    return;
  }
  const unsigned char* s = reinterpret_cast<const unsigned char*>(bytes.get());
  if ((bytes[0] & 0X80) == 0) { // Positive value!
    //
    // We can use the existing BN implementation for unsigned big endian bytes:
    //
    BN_bin2bn(s, bytesLen, ret);
    BN_set_negative(ret, false);
  } else { // Negative value!
    //
    // We need to apply two's complement:
    //
    negBigEndianBytes2bn(env, cls, s, bytesLen, ret0);
    BN_set_negative(ret, true);
  }
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1sub(JNIEnv* env, jclass, jlong r, jlong a, jlong b) {
  if (!threeValidHandles(env, r, a, b)) return;
  BN_sub(toBigNum(r), toBigNum(a), toBigNum(b));
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1mul_1word(JNIEnv* env, jclass, jlong a, BN_ULONG w) {
  if (!oneValidHandle(env, a)) return;
  BN_mul_word(toBigNum(a), w);
  throwExceptionIfNecessary(env);
}
extern "C" BN_ULONG Java_java_math_NativeBN_BN_1mod_1word(JNIEnv* env, jclass, jlong a, BN_ULONG w) {
  if (!oneValidHandle(env, a)) return 0;
  int result = BN_mod_word(toBigNum(a), w);
  throwExceptionIfNecessary(env);
  return result;
}
extern "C" void Java_java_math_NativeBN_BN_1mod_1inverse(JNIEnv* env, jclass, jlong ret, jlong a, jlong n) {
  if (!threeValidHandles(env, ret, a, n)) return;
  Unique_BN_CTX ctx(BN_CTX_new());
  BN_mod_inverse(toBigNum(ret), toBigNum(a), toBigNum(n), ctx.get());
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1mod_1exp(JNIEnv* env, jclass, jlong r, jlong a, jlong p, jlong m) {
  if (!fourValidHandles(env, r, a, p, m)) return;
  Unique_BN_CTX ctx(BN_CTX_new());
  BN_mod_exp(toBigNum(r), toBigNum(a), toBigNum(p), toBigNum(m), ctx.get());
  throwExceptionIfNecessary(env);
}
static void NativeBN_BN_copy(JNIEnv* env, jclass, jlong to, jlong from) {
  if (!twoValidHandles(env, to, from)) return;
  BN_copy(toBigNum(to), toBigNum(from));
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1mul(JNIEnv* env, jclass, jlong r, jlong a, jlong b) {
  if (!threeValidHandles(env, r, a, b)) return;
  Unique_BN_CTX ctx(BN_CTX_new());
  BN_mul(toBigNum(r), toBigNum(a), toBigNum(b), ctx.get());
  throwExceptionIfNecessary(env);
}
extern "C" void Java_java_math_NativeBN_BN_1div(JNIEnv* env, jclass, jlong dv, jlong rem, jlong m, jlong d) {
  if (!fourValidHandles(env, (rem ? rem : dv), (dv ? dv : rem), m, d)) return;
  Unique_BN_CTX ctx(BN_CTX_new());
  BN_div(toBigNum(dv), toBigNum(rem), toBigNum(m), toBigNum(d), ctx.get());
  throwExceptionIfNecessary(env);
}
static jlong NativeBN_BN_new(JNIEnv* env, jclass) {
  jlong result = static_cast<jlong>(reinterpret_cast<uintptr_t>(BN_new()));
  throwExceptionIfNecessary(env);
  return result;
}
extern "C" void Java_java_math_NativeBN_BN_1copy(JNIEnv* env, jclass, jlong to, jlong from) {
  if (!twoValidHandles(env, to, from)) return;
  BN_copy(toBigNum(to), toBigNum(from));
  throwExceptionIfNecessary(env);
}