%module wallycore
%{
#include "../include/wally_core.h"
#include "../include/wally_address.h"
#include "../include/wally_bip32.h"
#include "bip32_int.h"
#include "../include/wally_bip38.h"
#include "../include/wally_bip39.h"
#include "../include/wally_crypto.h"
#include "../include/wally_script.h"
#include "../include/wally_transaction.h"
#include "transaction_int.h"
#include "../include/wally_elements.h"
#include "../internal.h"
#include <limits.h>


static int check_result(JNIEnv *jenv, int result)
{
    switch (result) {
    case WALLY_OK:
        break;
    case WALLY_EINVAL:
        SWIG_JavaThrowException(jenv, SWIG_JavaIllegalArgumentException, "Invalid argument");
        break;
    case WALLY_ENOMEM:
        SWIG_JavaThrowException(jenv, SWIG_JavaOutOfMemoryError, "Out of memory");
        break;
    default: /* WALLY_ERROR */
        SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, "Failed");
        break;
    }
    return result;
}

static int int_cast(JNIEnv *jenv, size_t value) {
    if (value > INT_MAX)
        SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Invalid length");
    return (int)value;
}

static uint32_t uint32_cast(JNIEnv *jenv, jlong value) {
    if (value < 0 || value > UINT_MAX)
        SWIG_JavaThrowException(jenv, SWIG_JavaIndexOutOfBoundsException, "Invalid uint32_t");
    return (uint32_t)value;
}

/* Use a static class to hold our opaque pointers */
#define OBJ_CLASS "com/blockstream/libwally/Wally$Obj"

/* Create and return a java object to hold an opaque pointer */
static jobject create_obj(JNIEnv *jenv, void *p, int id) {
    jclass clazz;
    jmethodID ctor;

    if (!(clazz = (*jenv)->FindClass(jenv, OBJ_CLASS)))
        return NULL;
    if (!(ctor = (*jenv)->GetMethodID(jenv, clazz, "<init>", "(JI)V")))
        return NULL;
    return (*jenv)->NewObject(jenv, clazz, ctor, (jlong)(uintptr_t)p, id);
}

/* Fetch an opaque pointer from a java object */
static void *get_obj(JNIEnv *jenv, jobject obj, int id) {
    jclass clazz;
    jmethodID getter;
    void *ret;

    if (!obj || !(clazz = (*jenv)->GetObjectClass(jenv, obj)))
        return NULL;
    getter = (*jenv)->GetMethodID(jenv, clazz, "get_id", "()I");
    if (!getter || (*jenv)->CallIntMethod(jenv, obj, getter) != id ||
        (*jenv)->ExceptionOccurred(jenv))
        return NULL;
    getter = (*jenv)->GetMethodID(jenv, clazz, "get", "()J");
    if (!getter)
        return NULL;
    ret = (void *)(uintptr_t)((*jenv)->CallLongMethod(jenv, obj, getter));
    return (*jenv)->ExceptionOccurred(jenv) ? NULL : ret;
}

static void* get_obj_or_throw(JNIEnv *jenv, jobject obj, int id, const char *name) {
    void *ret = get_obj(jenv, obj, id);
    if (!ret)
        SWIG_JavaThrowException(jenv, SWIG_JavaIllegalArgumentException, name);
    return ret;
}

static unsigned char* malloc_or_throw(JNIEnv *jenv, size_t len) {
    unsigned char *p = (unsigned char *)wally_malloc(len);
    if (!p)
        SWIG_JavaThrowException(jenv, SWIG_JavaOutOfMemoryError, "Out of memory");
    return p;
}

static void clear_and_free(void *p, size_t len)
{
    if (p) {
        wally_bzero(p, len);
        wally_free(p);
    }
}

static jbyteArray create_array(JNIEnv *jenv, const unsigned char* p, size_t len) {
    jbyteArray ret = (*jenv)->NewByteArray(jenv, len);
    if (ret)
        (*jenv)->SetByteArrayRegion(jenv, ret, 0, len, (const jbyte*)p);
    return ret;
}

#define member_size(struct_, member) sizeof(((struct struct_ *)0)->member)
%}

%javaconst(1);
%ignore wally_free_string;
%ignore wally_bzero;

%pragma(java) jniclasscode=%{
    private static boolean loadLibrary() {
        try {
            System.loadLibrary("wallycore");
            return true;
        } catch (final UnsatisfiedLinkError e) {
            System.err.println("Native code library failed to load.\n" + e);
            return false;
        }
    }

    private static final boolean enabled = loadLibrary();
    public static boolean isEnabled() {
        return enabled;
    }

    static final class Obj {
        private final transient long ptr;
        private final int id;
        private Obj(final long ptr, final int id) { this.ptr = ptr; this.id = id; }
        private long get() { return ptr; }
        private int get_id() { return id; }
    }
%}

/* Raise an exception whenever a function fails */
%exception {
    if (!(*jenv)->ExceptionOccurred(jenv)) {
        $action
        check_result(jenv, result);
    }
}

/* Don't use our int return value except for exception checking */
%typemap(out) int %{
%}

/* Output parameters indicating how many bytes were written/sizes are
 * converted into return values. */
%typemap(in,noblock=1,numinputs=0) size_t *written(size_t sz) {
    sz = 0; $1 = ($1_ltype)&sz;
}
%typemap(in,noblock=1,numinputs=0) size_t *output(size_t sz) {
    sz = 0; $1 = ($1_ltype)&sz;
}
%typemap(argout,noblock=1) size_t* {
    $result = int_cast(jenv, *$1);
}

/* Output strings are converted to native Java strings and returned */
%typemap(in,noblock=1,numinputs=0) char **output(char *temp = 0) {
    $1 = &temp;
}
%typemap(argout,noblock=1) (char **output) {
    $result = NULL;
    if ($1 && *$1) {
        if (!(*jenv)->ExceptionOccurred(jenv))
            $result = (*jenv)->NewStringUTF(jenv, *$1);
        wally_free_string(*$1);
    }
}

/* uint32_t input arguments are taken as longs and cast with range checking */
%typemap(in) uint32_t {
    $1 = uint32_cast(jenv, $input);
}

/* uint64_t input arguments are taken as longs and cast unchecked. This means
 * callers need to take care with treating negative values correctly */
%typemap(in) uint64_t {
    $1 = (uint64_t)($input);
}

/* Treat uint32_t/uint64_t arrays like strings of ints */
%define %java_int_array(INTTYPE, JNITYPE, JTYPE, GETFN, RELEASEFN)
%typemap(jni)     (INTTYPE *STRING, size_t LENGTH) "JNITYPE"
%typemap(jtype)   (INTTYPE *STRING, size_t LENGTH) "JTYPE[]"
%typemap(jstype)  (INTTYPE *STRING, size_t LENGTH) "JTYPE[]"
%typemap(javain)  (INTTYPE *STRING, size_t LENGTH) "$javainput"
%typemap(freearg) (INTTYPE *STRING, size_t LENGTH) ""
%typemap(in)      (INTTYPE *STRING, size_t LENGTH) {
    if (!(*jenv)->ExceptionOccurred(jenv)) {
        $1 = $input ? (INTTYPE *) JCALL2(GETFN, jenv, $input, 0) : 0;
        $2 = $input ? (size_t) JCALL1(GetArrayLength, jenv, $input) : 0;
    } else {
        $1 = 0;
        $2 = 0;
    }
}
%typemap(argout)  (INTTYPE *STRING, size_t LENGTH) {
  if ($input) JCALL3(RELEASEFN, jenv, $input, (j##JTYPE *)$1, 0);
}
%enddef

%java_int_array(uint32_t, jintArray, int, GetIntArrayElements, ReleaseIntArrayElements)
%java_int_array(uint64_t, jlongArray, long, GetLongArrayElements, ReleaseLongArrayElements)

/* Input buffers with lengths are passed as arrays */
%apply(char *STRING, size_t LENGTH) { (const unsigned char *abf, size_t abf_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *asset, size_t asset_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *bytes, size_t bytes_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *chain_code, size_t chain_code_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *commitment, size_t commitment_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *extra, size_t extra_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *generator, size_t generator_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *hash160, size_t hash160_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *iv, size_t iv_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *key, size_t key_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *output_abf, size_t output_abf_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *output_asset, size_t output_asset_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *output_generator, size_t output_generator_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *pass, size_t pass_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *parent160, size_t parent160_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *priv_key, size_t priv_key_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *proof, size_t proof_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *pub_key, size_t pub_key_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *salt, size_t salt_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *script, size_t script_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *sig, size_t sig_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *sighash, size_t sighash_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *txhash, size_t txhash) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *vbf, size_t vbf_len) };
%apply(char *STRING, size_t LENGTH) { (const unsigned char *witness, size_t witness_len) };

/* Output buffers */
%apply(char *STRING, size_t LENGTH) { (unsigned char *asset_out, size_t asset_out_len) };
%apply(char *STRING, size_t LENGTH) { (unsigned char *abf_out, size_t abf_out_len) };
%apply(char *STRING, size_t LENGTH) { (unsigned char *bytes_out, size_t len) };
%apply(char *STRING, size_t LENGTH) { (unsigned char *vbf_out, size_t vbf_out_len) };

%apply(uint32_t *STRING, size_t LENGTH) { (const uint32_t *child_path, size_t child_path_len) }
%apply(uint32_t *STRING, size_t LENGTH) { (const uint32_t *sighash, size_t sighash_len) }
%apply(uint64_t *STRING, size_t LENGTH) { (const uint64_t *values, size_t values_len) }

%typemap(in, numinputs=0) uint64_t *value_out (uint64_t val) {
   val = 0; $1 = ($1_ltype)&val;
}
%typemap(argout) uint64_t* value_out{
   $result = (jlong)*$1;
}

%typemap(in, numinputs=0) uint64_t *value_out (uint64_t val) {
   val = 0; $1 = ($1_ltype)&val;
}
%typemap(argout) uint64_t* value_out{
   $result = (jlong)*$1;
}

/* Opaque types are converted to/from an internal object holder class */
%define %java_opaque_struct(NAME, ID)
%typemap(in, numinputs=0) struct NAME **output (struct NAME * w) {
    w = 0; $1 = ($1_ltype)&w;
}
%typemap(argout) struct NAME ** {
    if (*$1)
        $result = create_obj(jenv, *$1, ID);
}
%typemap (in) const struct NAME * {
    $1 = (struct NAME *)get_obj_or_throw(jenv, $input, ID, "NAME");
    if (!$1)
        return $null;
}
%typemap(jtype) const struct NAME * "Object"
%typemap(jni) const struct NAME * "jobject"
%enddef

/* Change a functions return type to match its output type mapping */
%define %return_decls(FUNC, JTYPE, JNITYPE)
%typemap(jstype) int FUNC "JTYPE"
%typemap(jtype) int FUNC "JTYPE"
%typemap(jni) int FUNC "JNITYPE"
%rename("%(strip:[wally_])s") FUNC;
%enddef

%define %returns_void__(FUNC)
%return_decls(FUNC, void, void)
%enddef
%define %returns_size_t(FUNC)
%return_decls(FUNC, int, jint)
%enddef
%define %returns_uint64(FUNC)
%return_decls(FUNC, long, jlong)
%enddef
%define %returns_string(FUNC)
%return_decls(FUNC, String, jstring)
%enddef
%define %returns_struct(FUNC, STRUCT)
%return_decls(FUNC, Object, jobject)
%enddef
%define %returns_array_(FUNC, ARRAYARG, LENARG, LEN)
%return_decls(FUNC, byte[], jbyteArray)
%exception FUNC {
    int skip = 0;
    jresult = NULL;
    if (!jarg ## ARRAYARG) {
        arg ## LENARG = LEN;
        arg ## ARRAYARG = malloc_or_throw(jenv, LEN);
        if (!arg ## ARRAYARG)
            skip = 1; /* Exception set by malloc_or_throw */
    }
    if (!skip && !(*jenv)->ExceptionOccurred(jenv)) {
        $action
        if (check_result(jenv, result) == WALLY_OK && !jarg ## ARRAYARG)
           jresult = create_array(jenv, arg ## ARRAYARG, LEN);
    }
    if (!jarg ## ARRAYARG)
        clear_and_free(arg ## ARRAYARG, LEN);
}
%enddef

/* Our wrapped opaque types */
%java_opaque_struct(words, 1)
%java_opaque_struct(ext_key, 2)
%java_opaque_struct(wally_tx_witness_stack, 3);
%java_opaque_struct(wally_tx_input, 4);
%java_opaque_struct(wally_tx_output, 5);
%java_opaque_struct(wally_tx, 6);

/* Our wrapped functions return types */
%returns_void__(bip32_key_free);
%returns_struct(bip32_key_from_base58_alloc, ext_key);
%rename("bip32_key_from_base58") bip32_key_from_base58_alloc;
%returns_struct(bip32_key_from_parent_alloc, ext_key);
%rename("bip32_key_from_parent") bip32_key_from_parent_alloc;
%returns_struct(bip32_key_from_parent_path_alloc, ext_key);
%rename("bip32_key_from_parent_path") bip32_key_from_parent_path_alloc;
%returns_struct(bip32_key_from_seed_alloc, ext_key);
%rename("bip32_key_from_seed") bip32_key_from_seed_alloc;
%returns_array_(bip32_key_get_chain_code, 2, 3, member_size(ext_key, chain_code));
%returns_size_t(bip32_key_get_child_num);
%returns_size_t(bip32_key_get_depth);
%returns_array_(bip32_key_get_hash160, 2, 3, member_size(ext_key, hash160));
%returns_array_(bip32_key_get_parent160, 2, 3, member_size(ext_key, parent160));
%returns_array_(bip32_key_get_priv_key, 2, 3, member_size(ext_key, priv_key) - 1);
%returns_array_(bip32_key_get_pub_key, 2, 3, member_size(ext_key, pub_key));
%returns_size_t(bip32_key_get_version);
%returns_struct(bip32_key_init_alloc, ext_key);
%rename("bip32_key_init") bip32_key_init_alloc;
%returns_array_(bip32_key_serialize, 3, 4, BIP32_SERIALIZED_LEN);
%returns_string(bip32_key_to_base58);
%returns_struct(bip32_key_unserialize_alloc, ext_key);
%rename("bip32_key_unserialize") bip32_key_unserialize_alloc;
%returns_array_(bip38_raw_from_private_key, 6, 7, BIP38_SERIALIZED_LEN);
%returns_string(bip38_from_private_key);
%returns_array_(bip38_raw_to_private_key, 6, 7, 32);
%returns_array_(bip38_to_private_key, 5, 6, 32);
%returns_size_t(bip38_raw_get_flags);
%returns_size_t(bip38_get_flags);
%returns_string(bip39_get_languages);
%returns_struct(bip39_get_wordlist, words);
%returns_string(bip39_get_word);
%returns_string(bip39_mnemonic_from_bytes);
%returns_size_t(bip39_mnemonic_to_bytes);
%returns_void__(bip39_mnemonic_validate);
%returns_size_t(bip39_mnemonic_to_seed);
%returns_string(wally_addr_segwit_from_bytes);
%returns_size_t(wally_addr_segwit_to_bytes);
%returns_array_(wally_aes, 6, 7, AES_BLOCK_LEN);
%returns_size_t(wally_aes_cbc);
%returns_array_(wally_asset_final_vbf, 8, 9, ASSET_TAG_LEN);
%returns_array_(wally_asset_generator_from_bytes, 5, 6, ASSET_GENERATOR_LEN);
%returns_size_t(wally_asset_rangeproof);
%returns_size_t(wally_asset_surjectionproof_size);
%returns_size_t(wally_asset_surjectionproof);
%returns_uint64(wally_asset_unblind);
%returns_array_(wally_asset_value_commitment, 6, 7, ASSET_COMMITMENT_LEN);
%returns_string(wally_base58_from_bytes);
%returns_size_t(wally_base58_to_bytes);
%returns_size_t(wally_base58_get_length);
%returns_void__(wally_ec_private_key_verify);
%returns_array_(wally_ec_public_key_decompress, 3, 4, EC_PUBLIC_KEY_UNCOMPRESSED_LEN);
%returns_array_(wally_ec_public_key_from_private_key, 3, 4, EC_PUBLIC_KEY_LEN);
%returns_array_(wally_ec_sig_from_bytes, 6, 7, EC_SIGNATURE_LEN);
%returns_array_(wally_ec_sig_normalize, 3, 4, EC_SIGNATURE_LEN);
%returns_array_(wally_ec_sig_from_der, 3, 4, EC_SIGNATURE_LEN);
%returns_size_t(wally_ec_sig_to_der);
%returns_void__(wally_ec_sig_verify);
%returns_size_t(wally_format_bitcoin_message);
%returns_array_(wally_hash160, 3, 4, HASH160_LEN);
%returns_string(wally_hex_from_bytes);
%returns_size_t(wally_hex_to_bytes);
%returns_array_(wally_hmac_sha256, 5, 6, HMAC_SHA256_LEN);
%returns_array_(wally_hmac_sha512, 5, 6, HMAC_SHA512_LEN);
%returns_void__(wally_init);
%returns_array_(wally_pbkdf2_hmac_sha256, 7, 8, PBKDF2_HMAC_SHA256_LEN);
%returns_array_(wally_pbkdf2_hmac_sha512, 7, 8, PBKDF2_HMAC_SHA512_LEN);
%returns_size_t(wally_script_push_from_bytes);
%returns_size_t(wally_scriptpubkey_csv_2of2_then_1_from_bytes);
%returns_size_t(wally_scriptpubkey_csv_2of3_then_2_from_bytes);
%returns_size_t(wally_scriptpubkey_get_type);
%returns_size_t(wally_scriptpubkey_op_return_from_bytes);
%returns_size_t(wally_scriptpubkey_p2pkh_from_bytes);
%returns_size_t(wally_scriptpubkey_p2sh_from_bytes);
%returns_size_t(wally_scriptpubkey_multisig_from_bytes);
%returns_size_t(wally_scriptsig_p2pkh_from_sig);
%returns_size_t(wally_scriptsig_p2pkh_from_der);
%returns_size_t(wally_scriptsig_multisig_from_bytes);
%returns_void__(wally_scrypt);
%returns_void__(wally_secp_randomize);
%returns_array_(wally_sha256, 3, 4, SHA256_LEN);
%returns_array_(wally_sha256d, 3, 4, SHA256_LEN);
%returns_array_(wally_sha512, 3, 4, SHA512_LEN);
%returns_void__(wally_tx_add_input);
%returns_void__(wally_tx_add_raw_input);
%returns_void__(wally_tx_add_output);
%returns_void__(wally_tx_add_raw_output);
%returns_void__(wally_tx_free);
%returns_struct(wally_tx_from_bytes, wally_tx);
%returns_struct(wally_tx_from_hex, wally_tx);
%returns_array_(wally_tx_get_btc_signature_hash, 8, 9, SHA256_LEN);
%returns_size_t(wally_tx_get_length);
%returns_array_(wally_tx_get_signature_hash, 12, 13, SHA256_LEN);
%returns_size_t(wally_tx_get_vsize);
%returns_size_t(wally_tx_get_weight);
%returns_size_t(wally_tx_get_witness_count);
%returns_struct(wally_tx_init_alloc, wally_tx);
%returns_void__(wally_tx_input_free);
%returns_struct(wally_tx_input_init_alloc, wally_tx_input);
%returns_void__(wally_tx_output_free);
%returns_struct(wally_tx_output_init_alloc, wally_tx_output);
%returns_void__(wally_tx_remove_input);
%returns_void__(wally_tx_remove_output);
%returns_void__(wally_tx_set_input_script);
%returns_void__(wally_tx_set_input_witness);
%returns_size_t(wally_tx_to_bytes);
%returns_string(wally_tx_to_hex);
%returns_size_t(wally_tx_vsize_from_weight);
%returns_void__(wally_tx_witness_stack_add);
%returns_void__(wally_tx_witness_stack_add_dummy);
%returns_void__(wally_tx_witness_stack_free);
%returns_struct(wally_tx_witness_stack_init_alloc, wally_tx_witness_stack);
%returns_void__(wally_tx_witness_stack_set);
%returns_void__(wally_tx_witness_stack_set_dummy);
%returns_string(wally_wif_from_bytes);
%returns_size_t(wally_wif_to_bytes);
%returns_size_t(wally_wif_is_uncompressed);
%returns_size_t(wally_wif_to_public_key);
%returns_string(wally_wif_to_address);
%returns_size_t(wally_witness_program_from_bytes);
%returns_size_t(wally_tx_is_elements);
%returns_size_t(wally_tx_is_coinbase);

%include "../include/wally_core.h"
%include "../include/wally_address.h"
%include "../include/wally_bip32.h"
%include "bip32_int.h"
%include "../include/wally_bip38.h"
%include "../include/wally_bip39.h"
%include "../include/wally_crypto.h"
%include "../include/wally_script.h"
%include "../include/wally_transaction.h"
%include "transaction_int.h"
%include "../include/wally_elements.h"
