// This SWIG interface file is used by SWIG to generate all of the glue code
// (JNI code, SWIG proxy classes, SWIG wrapper classes, etc) that is needed by
// the Kotlin/Java SDK to interact with the Rust-built store.
//
// There is a `swigDittoFFI` task in dittoffi's `build.gradle.kts` that
// makes use of this file. When the Rust ffi crate is built there is a C
// header file, `dittoffi.h` that gets generated. This is the `dittoffi.h` file
// that is referenced in this file, and is the source of the vast majority of
// the type definitions that you see below.

//// Exception handling
%include <exception.i>
%exception {
    try {
        $action
    } catch (const std::runtime_error &e) {
        SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, e.what());
        return $null;
    } catch (const std::exception &e) {
        SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, e.what());
        return $null;
    } catch (...) {
        SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, "Unknown exception");
        return $null;
    }
}

%typemap(javabody) SWIGTYPE %{
    private long swigCPtr;
    protected boolean swigCMemOwn;

    public $javaclassname(long cPtr, boolean cMemoryOwn) {
        swigCMemOwn = cMemoryOwn;
        swigCPtr = cPtr;
    }

    public static long getCPtr($javaclassname obj) {
        return (obj == null) ? 0 : obj.swigCPtr;
    }

    protected static long swigRelease($javaclassname obj) {
        long ptr = 0;
        if (obj != null) {
            if (!obj.swigCMemOwn)
                throw new RuntimeException("Cannot release ownership as memory is not owned");
            ptr = obj.swigCPtr;
            obj.swigCMemOwn = false;
            obj.delete();
        }
        return ptr;
    }

    public boolean isAlive() {
        return swigCPtr != 0;
    }
%}

#ifdef DITTOFFI_EXPERIMENTAL_BUS
%{
#define DITTOFFI_EXPERIMENTAL_BUS 1
%}
#endif

// Everything in the `%{ ... %}` brackets gets copied straight into the
// generated `dittoffi_android.cpp` file
%{
#define DITTO_LMDB_ENABLE 1
#include "attachment_observer_interfaces.h"
#include "bus.h"
#include "connection_request.h"
#include "crash_reporter.h"
#include "live_query_java_interfaces.h"
#include "presence.h"
#include "store_observer.h"
%}


#define DITTO_LMDB_ENABLE 1

//The default assert is ignored on release config. Assert and throw the exception to the JVM layer as a RuntimeException.
#define ASSERT_AND_THROW_RETURN_NULL(condition, jenv, message) \
    do { \
        if (!(condition)) { \
            SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, message); \
            return NULL; \
        } \
    } while (0)

#define ASSERT_AND_THROW_RETURN_VOID(condition, jenv, message) \
    do { \
        if (!(condition)) { \
            SWIG_JavaThrowException(jenv, SWIG_JavaRuntimeException, message); \
        } \
    } while (0)

/* == HOW TO GO FROM JAVA's `byte[]` (SRC) to C's `slice_ref_uint8_t` (DST) == */
//  1. Declare the Java SRC type <-> FFI DST type association
%typemap(jstype) slice_ref_uint8_t "byte[]"

//  2. Pick the (pair of) type(s) to use within the JNI interface (JNI.C / JNI.Java).
//     Here: `jbyteArray` (JNI.C) and its corresponding Java type `byte[]` (JNI.Java)
%typemap(jni) slice_ref_uint8_t "jbyteArray"
%typemap(jtype) slice_ref_uint8_t "byte[]"

//  3. Implement the SRC -> JNI.Java conversion (in this case it is trivial)
%typemap(javain) slice_ref_uint8_t "$javainput"

//  3. Implement the JNI.C -> DST conversion
%typemap(in) slice_ref_uint8_t {
  if ($input == NULL) {
    $1 = slice_ref_uint8_t {
      .ptr = (uint8_t const *) NULL,
      .len = (size_t) 0,
    };
  } else {
    uint8_t const *ptr = (uint8_t const *) JCALL2(GetByteArrayElements, jenv, $input, NULL);
    ASSERT_AND_THROW_RETURN_VOID(ptr != NULL, jenv, "Failed to get byte array elements") ;
    auto length = JCALL1(GetArrayLength, jenv, $input);
    ASSERT_AND_THROW_RETURN_VOID(length >= 0, jenv, "Failed to get byte array length") ;

    $1 = slice_ref_uint8_t {
      .ptr = ptr,
      .len = (size_t) length,
    };
  }
}

//  4. If the step above allocated any resources, specify here how to free them.
%typemap(freearg) slice_ref_uint8_t {
  // Free the allocation from `GetByteArrayElements`
  if ($input != NULL) {
    JCALL3(ReleaseByteArrayElements, jenv, $input, (jbyte *)$1.ptr, JNI_ABORT);
  }
}

/* This way, when an instance of (Java) type SRC is fed to a function whose FFI
   implementation expects DST, then:

 1. SRC is converted to JNI.Java using `javain`'s implementation
 2. JNI.Java is converted to JNI.C using JNI's builtin magic (marshalling)
 3. JNI.C is converted to DST using `in`'s implementation

 4. The FFI function is called.

 5. Once the FFI function returns, the helper allocated resources are freed
    using `freearg`.

 - Note: the applied mapping pattern matches the input of the FFI function
         (`slice_ref_uint8_t` param in the example).
         Extra filters may be added, such as a parameter name.

 - Thanks to `safer_ffi`, all the `slice` invariants ought to be expressed
   at the type level, hence the lack of need to specify functions or param names.

/* == END == */

/* Ditto for `String[] -> slice_ref_char_const_ptr_t`: */
// High-level Java type (corresponding to Kotlin's `Array<String>`):
%typemap(jstype) slice_ref_char_const_ptr_t "String[]"
// JNI-compatible Java type
%typemap(jtype) slice_ref_char_const_ptr_t "String[]"
// Conversion from the former to the latter (inwards)
%typemap(javain) slice_ref_char_const_ptr_t "$javainput"
// C JNI-equivalent to the `jtype`:
%typemap(jni) slice_ref_char_const_ptr_t "jobjectArray"
// Conversion from it to the C FFI type
%typemap(in) slice_ref_char_const_ptr_t {
  $1 = (slice_ref_char_const_ptr_t) {
    .len = (size_t) JCALL1(GetArrayLength, jenv, $input),
  };
  $1.ptr = (typeof($1.ptr)) calloc(sizeof(*$1.ptr), $1.len);
  for (size_t i = 0; i < $1.len; ++i) {
    jstring j_string = (jstring)JCALL2(GetObjectArrayElement, jenv, $input, i);
    char const * c_string = JCALL2(GetStringUTFChars, jenv, j_string, 0);
    ((char const * *) $1.ptr)[i] = strdup(c_string);
    // Free c_string
    JCALL2(ReleaseStringUTFChars, jenv, j_string, c_string);
    // Free j_string
    JCALL1(DeleteLocalRef, jenv, j_string);
  }
  // Freeing $1.ptr[i] is to be done within the `freearg` typemap
}
%typemap(freearg) slice_ref_char_const_ptr_t {
  while ($1.len > 0) {
    --$1.len;
    free((void *) $1.ptr[$1.len]); // frees strdup
  }
  free((void *) $1.ptr); // frees calloc
}

/* Ditto for `COrderByParam_t[] -> slice_ref_COrderByParam_t`: */
// High-level Java type (corresponding to Kotlin's `Array<COrderByParam_t>`):
%typemap(jstype) slice_ref_COrderByParam_t "COrderByParam_t[]"
// JNI-compatible Java type
%typemap(jtype) slice_ref_COrderByParam_t "COrderByParam_t[]"
// Conversion from the former to the latter (inwards)
%typemap(javain) slice_ref_COrderByParam_t "$javainput"
// C JNI-equivalent to the `jtype`:
%typemap(jni) slice_ref_COrderByParam_t "jobjectArray"
// Conversion from it to the C FFI type
%typemap(in) slice_ref_COrderByParam_t {
  $1 = (slice_ref_COrderByParam_t) {
    .len = (size_t) JCALL1(GetArrayLength, jenv, $input),
  };
  $1.ptr = (typeof($1.ptr)) calloc(sizeof(*$1.ptr), $1.len);
  // Prepare to call `COrderByParam_t.getCPtr()` as done above
  jclass jcls_COrderByParam_t = JCALL1(FindClass, jenv
    , "live/ditto/internal/swig/ffi/COrderByParam_t"
  );
  ASSERT_AND_THROW_RETURN_NULL(jcls_COrderByParam_t != NULL, jenv, "Failed to find COrderByParam_t class.");
  jmethodID getCPtr = JCALL3(GetStaticMethodID, jenv
    , jcls_COrderByParam_t
    , "getCPtr"
    , "(Llive/ditto/internal/swig/ffi/COrderByParam_t;)J"
  );
  ASSERT_AND_THROW_RETURN_NULL(getCPtr != 0, jenv, "Failed to get getCPtr method.");
  for (size_t i = 0; i < $1.len; ++i) {
    jobject jobj = JCALL2(GetObjectArrayElement, jenv, $input, i);
    ASSERT_AND_THROW_RETURN_NULL(jobj != NULL, jenv, "Null element in COrderByParam_t array");
    auto cptr = JCALL3(CallStaticLongMethod, jenv
            , jcls_COrderByParam_t
            , getCPtr
            , jobj
    );
    ASSERT_AND_THROW_RETURN_NULL(cptr != NULL, jenv, "Failed to get C pointer from COrderByParam_t object");

    memcpy(
      (void *) &$1.ptr[i],
      (void *) cptr,
      sizeof($1.ptr[i])
    );
    JCALL1(DeleteLocalRef, jenv, jobj);
  }
  // Freeing $1.ptr is to be done within the `freearg` typemap
}
%typemap(freearg) slice_ref_COrderByParam_t {
  free((void *) $1.ptr); // frees calloc
}

/* Ditto for `SWIGTYPE_p_CDocument[] -> slice_ref_CDocument_ptr_t`: */
// High-level Java type (corresponding to Kotlin's `Array<SWIGTYPE_p_CDocument>`):
%typemap(jstype) slice_ref_CDocument_ptr_t "SWIGTYPE_p_CDocument[]"
// JNI-compatible Java type
%typemap(jtype) slice_ref_CDocument_ptr_t "SWIGTYPE_p_CDocument[]"
// Conversion from the former to the latter (inwards)
%typemap(javain) slice_ref_CDocument_ptr_t "$javainput"
// C JNI-equivalent to the `jtype`:
%typemap(jni) slice_ref_CDocument_ptr_t "jobjectArray"
// Conversion from it to the C FFI type
%typemap(in) slice_ref_CDocument_ptr_t {
  size_t arr_len = (size_t) JCALL1(GetArrayLength, jenv, $input);
  if (arr_len == 0) {
    $1 = (slice_ref_CDocument_ptr_t) { .ptr = (CDocument * const *)0xbad000, .len = 0 };
  } else {
    $1 = (slice_ref_CDocument_ptr_t) {
      .len = arr_len,
    };
    $1.ptr = (typeof($1.ptr)) calloc(sizeof(*$1.ptr), $1.len);
    // Prepare to call `SWIGTYPE_p_CDocument.getCPtr()` as done above
    jclass jcls_SWIGTYPE_p_CDocument = JCALL1(FindClass, jenv
      , "live/ditto/internal/swig/ffi/SWIGTYPE_p_CDocument"
    );
    ASSERT_AND_THROW_RETURN_NULL(jcls_SWIGTYPE_p_CDocument != NULL, jenv, "jcls_SWIGTYPE_p_CDocument is NULL");
    jmethodID getCPtr = JCALL3(GetStaticMethodID, jenv
      , jcls_SWIGTYPE_p_CDocument
      , "getCPtr"
      , "(Llive/ditto/internal/swig/ffi/SWIGTYPE_p_CDocument;)J"
    );
    ASSERT_AND_THROW_RETURN_NULL(getCPtr != 0, jenv, "Failed to get method ID for getCPtr");
    for (size_t i = 0; i < $1.len; ++i) {
      jobject jobj = JCALL2(GetObjectArrayElement, jenv, $input, i);
      ((CDocument_t const * *) $1.ptr)[i] = (CDocument *)JCALL3(CallStaticLongMethod, jenv
        , jcls_SWIGTYPE_p_CDocument
        , getCPtr
        , jobj
      );
      JCALL1(DeleteLocalRef, jenv, jobj);
    }
    // Freeing $1.ptr is to be done within the `freearg` typemap
  }
}
%typemap(freearg) slice_ref_CDocument_ptr_t {
  if ($1.len > 0) {
    free((void *) $1.ptr); // frees calloc
  }
}

/* Ditto for `SWIGTYPE_p_dittoffi_query_result_item[] -> slice_ref_dittoffi_query_result_item_ptr_t`: */
// High-level Java type (corresponding to Kotlin's `Array<SWIGTYPE_p_dittoffi_query_result_item>`):
%typemap(jstype) slice_ref_dittoffi_query_result_item_ptr_t "SWIGTYPE_p_dittoffi_query_result_item[]"
// JNI-compatible Java type
%typemap(jtype) slice_ref_dittoffi_query_result_item_ptr_t "SWIGTYPE_p_dittoffi_query_result_item[]"
// Conversion from the former to the latter (inwards)
%typemap(javain) slice_ref_dittoffi_query_result_item_ptr_t "$javainput"
// C JNI-equivalent to the `jtype`:
%typemap(jni) slice_ref_dittoffi_query_result_item_ptr_t "jobjectArray"
// Conversion from it to the C FFI type
%typemap(in) slice_ref_dittoffi_query_result_item_ptr_t {
  size_t arr_len = (size_t) JCALL1(GetArrayLength, jenv, $input);
  if (arr_len == 0) {
    $1 = (slice_ref_dittoffi_query_result_item_ptr_t) { .ptr = (dittoffi_query_result_item * const *)0xbad000, .len = 0 };
  } else {
    $1 = (slice_ref_dittoffi_query_result_item_ptr_t) {
      .len = arr_len,
    };
    $1.ptr = (typeof($1.ptr)) calloc(sizeof(*$1.ptr), $1.len);
    // Prepare to call `SWIGTYPE_p_dittoffi_query_result_item.getCPtr()` as done above
    jclass jcls_SWIGTYPE_p_dittoffi_query_result_item = JCALL1(FindClass, jenv
      , "live/ditto/internal/swig/ffi/SWIGTYPE_p_dittoffi_query_result_item"
    );
    ASSERT_AND_THROW_RETURN_NULL(jcls_SWIGTYPE_p_dittoffi_query_result_item != NULL, jenv, "jcls_SWIGTYPE_p_dittoffi_query_result_item is NULL");
    jmethodID getCPtr = JCALL3(GetStaticMethodID, jenv
      , jcls_SWIGTYPE_p_dittoffi_query_result_item
      , "getCPtr"
      , "(Llive/ditto/internal/swig/ffi/SWIGTYPE_p_dittoffi_query_result_item;)J"
    );
    ASSERT_AND_THROW_RETURN_NULL(getCPtr != 0, jenv, "Failed to get method ID for getCPtr");
    for (size_t i = 0; i < $1.len; ++i) {
      jobject jobj = JCALL2(GetObjectArrayElement, jenv, $input, i);
      ((dittoffi_query_result_item const * *) $1.ptr)[i] = (dittoffi_query_result_item *)JCALL3(CallStaticLongMethod, jenv
        , jcls_SWIGTYPE_p_dittoffi_query_result_item
        , getCPtr
        , jobj
      );
      JCALL1(DeleteLocalRef, jenv, jobj);
    }
    // Freeing $1.ptr is to be done within the `freearg` typemap
  }
}
%typemap(freearg) slice_ref_dittoffi_query_result_item_ptr_t {
  if ($1.len > 0) {
    free((void *) $1.ptr); // frees calloc
  }
}

/* These SWIG typemaps means that a Java `byte[]` is returned in place of a
 * `slice_boxed_uint8_t` ("outwards")
 */

// High-level Java type
%typemap(jstype) slice_boxed_uint8_t "byte[]"
// JNI-compatible Java type
%typemap(jtype) slice_boxed_uint8_t "byte[]"
// Conversion from the latter to the former ("outwards")
%typemap(javaout) slice_boxed_uint8_t {
  return $jnicall;
}
// C JNI-equivalent to the `jtype`
%typemap(jni) slice_boxed_uint8_t "jbyteArray"
// Conversion from the C FFI type to the `jni`
%typemap(out) slice_boxed_uint8_t {
  if ($1.ptr != NULL) {
    $result = JCALL1(NewByteArray, jenv, $1.len);
    JCALL4(SetByteArrayRegion, jenv, $result, 0, $1.len, (jbyte *)$1.ptr);
    // We do not call `ditto_c_bytes_free($1);` here since the `ret` typemap
    // takes care of that.
  } else {
    $result = JCALL1(NewByteArray, jenv, 0);
  }
}

// Here using `ret` instead of `new_free` ties this deallocating logic
// to ALL functions returning a `slice_boxed_uint8_t`. Which is exactly what
// we want.
%typemap(ret) slice_boxed_uint8_t {
  if ($1.ptr != NULL) {
    ditto_c_bytes_free($1);
  }
}

%inline %{ extern "C" {
  slice_boxed_uint8_t get_id (
    CAttachment_t const * it)
  {
    return it->id;
  }

  // This apparently no-op functions relies on the out typemap to perform the conversion
  slice_boxed_uint8_t JavaOf_slice_boxed_uint8_t (
    slice_boxed_uint8_t const * it)
  {
    return *it;
  }
} %}

// High-level Java type
%typemap(jstype) slice_ref_uint8_t "byte[]"
// JNI-compatible Java type
%typemap(jtype) slice_ref_uint8_t "byte[]"
// Conversion from the latter to the former ("outwards")
%typemap(javaout) slice_ref_uint8_t {
  return $jnicall;
}
// C JNI-equivalent to the `jtype`
%typemap(jni) slice_ref_uint8_t "jbyteArray"
// Conversion from the C FFI type to the `jni`
%typemap(out) slice_ref_uint8_t {
  if ($1.ptr != NULL) {
    $result = JCALL1(NewByteArray, jenv, $1.len);
    JCALL4(SetByteArrayRegion, jenv, $result, 0, $1.len, (jbyte *)$1.ptr);
    // We do not call `ditto_c_bytes_free($1);` here since the `ret` typemap
    // takes care of that.
  } else {
    $result = JCALL1(NewByteArray, jenv, 0);
  }
}

%inline %{ extern "C" {
  // This apparently no-op functions relies on the out typemap to perform the conversion
  slice_ref_uint8_t JavaOf_slice_ref_uint8_t (
    slice_ref_uint8_t const * it)
  {
    return *it;
  }
} %}

%typemap(javain) slice_boxed_uint8_t "$javainput"

%inline %{
  extern "C" {
    char const * get_collection_name_at_index(
      Vec_char_ptr_t const * coll_names,
      unsigned long int index
    ) {
      return coll_names->ptr[index];
    }

    slice_boxed_uint8_t take_buffer_at_index (
      Vec_slice_boxed_uint8_t const * ids,
      unsigned long int index)
    {
      slice_boxed_uint8_t ret = ids->ptr[index];
      ids->ptr[index] = {0};
      return ret;
    }

    // These two functions are used to make working with the live query FFI
    // easier from Java / Kotlin
    CDocument_t const * get_document_at_index (
      CDocument_t const * * docs,
      unsigned long int index)
    {
      if (docs == nullptr) {
        throw std::invalid_argument("docs pointer is null");
      }

      return docs[index];
    }

    size_t get_index_at (
      size_t *unsigned_long_ints,
      unsigned long index
    ) {
      return unsigned_long_ints[index];
    }
  }
%}

// Here we use a typemap to say that for functions that return `char *` and are
// marked with the `%newobject` directive we have custom behaviour for freeing
// the returned value. This ensures all strings get cleaned up.
%typemap(newfree) char * {
  if ($1 != NULL) {
    ditto_c_string_free($1);
  }
}
%newobject ditto_error_message;
%newobject ditto_get_complete_attachment_path;
%newobject ditto_get_sdk_version;
%newobject ditto_document_id_query_compatible;

// To make it possible (or at least a lot easier) to work with pointers to some
// of the Ditto_* types we use the `%pointer_functions` directive to provide a
// collection of pointer-related helper functions. These lead to the creation of
// the following functions for each type (Ditto_DocumentHandle is used as an
// example):
//
// 1. new_Ditto_DocumentHandle
// 2. copy_Ditto_DocumentHandle
// 3. delete_Ditto_DocumentHandle
// 4. Ditto_DocumentHandle_assign
// 5. Ditto_DocumentHandle_value
//
// See here for more information:
//   http://www.swig.org/Doc3.0/SWIGDocumentation.html#Library_nn4
//
%include "cpointer.i"
%include <stdint.i>
%pointer_functions(CDocument_t *, Ditto_DocumentHandle)
%pointer_functions(CReadTransaction_t *, Ditto_ReadTransactionHandle)
%pointer_functions(
  CWriteTransaction_t *,
  Ditto_WriteTransactionHandle
)
%pointer_functions(char *, Ditto_StringHandle)
%pointer_functions(long long, Ditto_LongLongHandle)
%pointer_functions(uint64_t, Ditto_U64Handle)
%pointer_functions(bool, Ditto_BoolHandle)
%inline %{ extern "C" {
  slice_boxed_uint8_t * new_Ditto_CBOR (void)
  {
    return (slice_boxed_uint8_t *) malloc(sizeof(slice_boxed_uint8_t));
  }
  void delete_Ditto_CBOR (
    slice_boxed_uint8_t * it)
  {
    free((void *) it);
  }
} %}

%include "carrays.i"
%array_class(COrderByParam_t, COrderByParamArray)

%apply unsigned long int { uintptr_t }

// `%include`s mean that SWIG processes the files and generates wrapper and
// proxy classes as appropriate for the types it encounters.
%include "attachment_observer_interfaces.h"
%include "bus.h"
%include "connection_request.h"
%include "crash_reporter.h"
%include "live_query_java_interfaces.h"
%include "presence.h"
%include "store_observer.h"
