#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>

enum class ProofMessageType {
  Revealed = 1,
  HiddenProofSpecificBlinding = 2,
  HiddenExternalBlinding = 3,
};

/// ByteBuffer is a struct that represents an array of bytes to be sent over the FFI boundaries.
/// There are several cases when you might want to use this, but the primary one for us
/// is for returning protobuf-encoded data to Swift and Java. The type is currently rather
/// limited (implementing almost no functionality), however in the future it may be
/// more expanded.
///
/// ## Caveats
///
/// Note that the order of the fields is `len` (an i64) then `data` (a `*mut u8`), getting
/// this wrong on the other side of the FFI will cause memory corruption and crashes.
/// `i64` is used for the length instead of `u64` and `usize` because JNA has interop
/// issues with both these types.
///
/// ### `Drop` is not implemented
///
/// ByteBuffer does not implement Drop. This is intentional. Memory passed into it will
/// be leaked if it is not explicitly destroyed by calling [`ByteBuffer::destroy`], or
/// [`ByteBuffer::destroy_into_vec`]. This is for two reasons:
///
/// 1. In the future, we may allow it to be used for data that is not managed by
///    the Rust allocator\*, and `ByteBuffer` assuming it's okay to automatically
///    deallocate this data with the Rust allocator.
///
/// 2. Automatically running destructors in unsafe code is a
///    [frequent footgun](https://without.boats/blog/two-memory-bugs-from-ringbahn/)
///    (among many similar issues across many crates).
///
/// Note that calling `destroy` manually is often not needed, as usually you should
/// be passing these to the function defined by [`define_bytebuffer_destructor!`] from
/// the other side of the FFI.
///
/// Because this type is essentially *only* useful in unsafe or FFI code (and because
/// the most common usage pattern does not require manually managing the memory), it
/// does not implement `Drop`.
///
/// \* Note: in the case of multiple Rust shared libraries loaded at the same time,
/// there may be multiple instances of "the Rust allocator" (one per shared library),
/// in which case we're referring to whichever instance is active for the code using
/// the `ByteBuffer`. Note that this doesn't occur on all platforms or build
/// configurations, but treating allocators in different shared libraries as fully
/// independent is always safe.
///
/// ## Layout/fields
///
/// This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so
/// that we can verify rust users are constructing them appropriately), the fields, their types, and
/// their order are *very much* a part of the public API of this type. Consumers on the other side
/// of the FFI will need to know its layout.
///
/// If this were a C struct, it would look like
///
/// ```c,no_run
/// struct ByteBuffer {
///     // Note: This should never be negative, but values above
///     // INT64_MAX / i64::MAX are not allowed.
///     int64_t len;
///     // Note: nullable!
///     uint8_t *data;
/// };
/// ```
///
/// In rust, there are two fields, in this order: `len: i64`, and `data: *mut u8`.
///
/// For clarity, the fact that the data pointer is nullable means that `Option<ByteBuffer>` is not
/// the same size as ByteBuffer, and additionally is not FFI-safe (the latter point is not
/// currently guaranteed anyway as of the time of writing this comment).
///
/// ### Description of fields
///
/// `data` is a pointer to an array of `len` bytes. Note that data can be a null pointer and therefore
/// should be checked.
///
/// The bytes array is allocated on the heap and must be freed on it as well. Critically, if there
/// are multiple rust shared libraries using being used in the same application, it *must be freed
/// on the same heap that allocated it*, or you will corrupt both heaps.
///
/// Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which
/// means you must expose a function to release the resources of `data` which can be done easily
/// using the [`define_bytebuffer_destructor!`] macro provided by this crate.
struct ByteBuffer {
  int64_t len;
  uint8_t *data;
};

/// A wrapper around error codes, which is represented identically to an i32 on the other side of
/// the FFI. Essentially exists to check that we don't accidentally reuse success/panic codes for
/// other things.
using ErrorCode = int32_t;

/// Represents an error that occured within rust, storing both an error code, and additional data
/// that may be used by the caller.
///
/// Misuse of this type can cause numerous issues, so please read the entire documentation before
/// usage.
///
/// ## Rationale
///
/// This library encourages a pattern of taking a `&mut ExternError` as the final parameter for
/// functions exposed over the FFI. This is an "out parameter" which we use to write error/success
/// information that occurred during the function's execution.
///
/// To be clear, this means instances of `ExternError` will be created on the other side of the FFI,
/// and passed (by mutable reference) into Rust.
///
/// While this pattern is not particularly ergonomic in Rust (although hopefully this library
/// helps!), it offers two main benefits over something more ergonomic (which might be `Result`
/// shaped).
///
/// 1. It avoids defining a large number of `Result`-shaped types in the FFI consumer, as would
///    be required with something like an `struct ExternResult<T> { ok: *mut T, err:... }`
///
/// 2. It offers additional type safety over `struct ExternResult { ok: *mut c_void, err:... }`,
///    which helps avoid memory safety errors. It also can offer better performance for returning
///    primitives and repr(C) structs (no boxing required).
///
/// It also is less tricky to use properly than giving consumers a `get_last_error()` function, or
/// similar.
///
/// ## Caveats
///
/// Note that the order of the fields is `code` (an i32) then `message` (a `*mut c_char`), getting
/// this wrong on the other side of the FFI will cause memory corruption and crashes.
///
/// The fields are public largely for documentation purposes, but you should use
/// [`ExternError::new_error`] or [`ExternError::success`] to create these.
///
/// ## Layout/fields
///
/// This struct's field are not `pub` (mostly so that we can soundly implement `Send`, but also so
/// that we can verify rust users are constructing them appropriately), the fields, their types, and
/// their order are *very much* a part of the public API of this type. Consumers on the other side
/// of the FFI will need to know its layout.
///
/// If this were a C struct, it would look like
///
/// ```c,no_run
/// struct ExternError {
///     int32_t code;
///     char *message; // note: nullable
/// };
/// ```
///
/// In rust, there are two fields, in this order: `code: ErrorCode`, and `message: *mut c_char`.
/// Note that ErrorCode is a `#[repr(transparent)]` wrapper around an `i32`, so the first property
/// is equivalent to an `i32`.
///
/// #### The `code` field.
///
/// This is the error code, 0 represents success, all other values represent failure. If the `code`
/// field is nonzero, there should always be a message, and if it's zero, the message will always be
/// null.
///
/// #### The `message` field.
///
/// This is a null-terminated C string containing some amount of additional information about the
/// error. If the `code` property is nonzero, there should always be an error message. Otherwise,
/// this should will be null.
///
/// This string (when not null) is allocated on the rust heap (using this crate's
/// [`rust_string_to_c`]), and must be freed on it as well. Critically, if there are multiple rust
/// packages using being used in the same application, it *must be freed on the same heap that
/// allocated it*, or you will corrupt both heaps.
///
/// Typically, this object is managed on the other side of the FFI (on the "FFI consumer"), which
/// means you must expose a function to release the resources of `message` which can be done easily
/// using the [`define_string_destructor!`] macro provided by this crate.
///
/// If, for some reason, you need to release the resources directly, you may call
/// `ExternError::release()`. Note that you probably do not need to do this, and it's
/// intentional that this is not called automatically by implementing `drop`.
///
/// ## Example
///
/// ```rust,no_run
/// use ffi_support::{ExternError, ErrorCode};
///
/// #[derive(Debug)]
/// pub enum MyError {
///     IllegalFoo(String),
///     InvalidBar(i64),
///     // ...
/// }
///
/// // Putting these in a module is obviously optional, but it allows documentation, and helps
/// // avoid accidental reuse.
/// pub mod error_codes {
///     // note: -1 and 0 are reserved by ffi_support
///     pub const ILLEGAL_FOO: i32 = 1;
///     pub const INVALID_BAR: i32 = 2;
///     // ...
/// }
///
/// fn get_code(e: &MyError) -> ErrorCode {
///     match e {
///         MyError::IllegalFoo(_) => ErrorCode::new(error_codes::ILLEGAL_FOO),
///         MyError::InvalidBar(_) => ErrorCode::new(error_codes::INVALID_BAR),
///         // ...
///     }
/// }
///
/// impl From<MyError> for ExternError {
///     fn from(e: MyError) -> ExternError {
///         ExternError::new_error(get_code(&e), format!("{:?}", e))
///     }
/// }
/// ```
struct ExternError {
  ErrorCode code;
  char *message;
};

/// `FfiStr<'a>` is a safe (`#[repr(transparent)]`) wrapper around a
/// nul-terminated `*const c_char` (e.g. a C string). Conceptually, it is
/// similar to [`std::ffi::CStr`], except that it may be used in the signatures
/// of extern "C" functions.
///
/// Functions accepting strings should use this instead of accepting a C string
/// directly. This allows us to write those functions using safe code without
/// allowing safe Rust to cause memory unsafety.
///
/// A single function for constructing these from Rust ([`FfiStr::from_raw`])
/// has been provided. Most of the time, this should not be necessary, and users
/// should accept `FfiStr` in the parameter list directly.
///
/// ## Caveats
///
/// An effort has been made to make this struct hard to misuse, however it is
/// still possible, if the `'static` lifetime is manually specified in the
/// struct. E.g.
///
/// ```rust,no_run
/// # use ffi_support::FfiStr;
/// // NEVER DO THIS
/// #[no_mangle]
/// extern "C" fn never_do_this(s: FfiStr<'static>) {
///     // save `s` somewhere, and access it after this
///     // function returns.
/// }
/// ```
///
/// Instead, one of the following patterns should be used:
///
/// ```
/// # use ffi_support::FfiStr;
/// #[no_mangle]
/// extern "C" fn valid_use_1(s: FfiStr<'_>) {
///     // Use of `s` after this function returns is impossible
/// }
/// // Alternative:
/// #[no_mangle]
/// extern "C" fn valid_use_2(s: FfiStr) {
///     // Use of `s` after this function returns is impossible
/// }
/// ```
using FfiStr = const char*;

/// Used for receiving a ByteBuffer from C that was allocated by either C or Rust.
/// If Rust allocated, then the outgoing struct is `ffi_support::ByteBuffer`
/// Caller is responsible for calling free where applicable.
///
/// C will not notice a difference and can use the same struct
struct ByteArray {
  uintptr_t length;
  const uint8_t *data;
};

extern "C" {

/// Public destructor for strings managed by the other side of the FFI.
///
/// # Safety
///
/// This will free the string pointer it gets passed in as an argument,
/// and thus can be wildly unsafe if misused.
///
/// See the documentation of `ffi_support::destroy_c_string` and
/// `ffi_support::define_string_destructor!` for further info.
void bbs_string_free(char *s);

void bbs_byte_buffer_free(ByteBuffer v);

void free_bbs_blind_commitment(uint64_t v, ExternError *err);

int32_t bbs_blind_signature_size();

uint64_t bbs_blind_commitment_context_init(ExternError *err);

int32_t bbs_blind_commitment_context_add_message_string(uint64_t handle,
                                                        uint32_t index,
                                                        FfiStr message,
                                                        ExternError *err);

int32_t bbs_blind_commitment_context_add_message_bytes(uint64_t handle,
                                                       uint32_t index,
                                                       ByteArray message,
                                                       ExternError *err);

int32_t bbs_blind_commitment_context_add_message_prehashed(uint64_t handle,
                                                           uint32_t index,
                                                           ByteArray message,
                                                           ExternError *err);

int32_t bbs_blind_commitment_context_set_public_key(uint64_t handle,
                                                    ByteArray value,
                                                    ExternError *err);

int32_t bbs_blind_commitment_context_set_nonce_string(uint64_t handle,
                                                      FfiStr message,
                                                      ExternError *err);

int32_t bbs_blind_commitment_context_set_nonce_bytes(uint64_t handle,
                                                     ByteArray value,
                                                     ExternError *err);

int32_t bbs_blind_commitment_context_set_nonce_prehashed(uint64_t handle,
                                                         ByteArray value,
                                                         ExternError *err);

int32_t bbs_blind_commitment_context_finish(uint64_t handle,
                                            ByteBuffer *commitment,
                                            ByteBuffer *out_context,
                                            ByteBuffer *blinding_factor,
                                            ExternError *err);

void free_bbs_blind_sign(uint64_t v, ExternError *err);

int32_t bbs_blinding_factor_size();

uint64_t bbs_blind_sign_context_init(ExternError *err);

int32_t bbs_blind_sign_context_add_message_string(uint64_t handle,
                                                  uint32_t index,
                                                  FfiStr message,
                                                  ExternError *err);

int32_t bbs_blind_sign_context_add_message_bytes(uint64_t handle,
                                                 uint32_t index,
                                                 ByteArray message,
                                                 ExternError *err);

int32_t bbs_blind_sign_context_add_message_prehashed(uint64_t handle,
                                                     uint32_t index,
                                                     ByteArray message,
                                                     ExternError *err);

int32_t bbs_blind_sign_context_set_public_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_blind_sign_context_set_secret_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_blind_sign_context_set_commitment(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_blind_sign_context_finish(uint64_t handle,
                                      ByteBuffer *blinded_signature,
                                      ExternError *err);

int32_t bbs_unblind_signature(ByteArray blind_signature,
                              ByteArray blinding_factor,
                              ByteBuffer *unblind_signature,
                              ExternError *err);

void free_create_proof(uint64_t v, ExternError *err);

int32_t bbs_create_proof_context_size(uint64_t handle);

uint64_t bbs_create_proof_context_init(ExternError *err);

int32_t bbs_create_proof_context_add_proof_message_string(uint64_t handle,
                                                          FfiStr message,
                                                          ProofMessageType xtype,
                                                          ByteArray blinding_factor,
                                                          ExternError *err);

int32_t bbs_create_proof_context_add_proof_message_bytes(uint64_t handle,
                                                         ByteArray message,
                                                         ProofMessageType xtype,
                                                         ByteArray blinding_factor,
                                                         ExternError *err);

int32_t bbs_create_proof_context_add_proof_message_prehashed(uint64_t handle,
                                                             ByteArray message,
                                                             ProofMessageType xtype,
                                                             ByteArray blinding_factor,
                                                             ExternError *err);

int32_t bbs_create_proof_context_set_signature(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_create_proof_context_set_public_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_create_proof_context_set_nonce_string(uint64_t handle,
                                                  FfiStr message,
                                                  ExternError *err);

int32_t bbs_create_proof_context_set_nonce_bytes(uint64_t handle,
                                                 ByteArray value,
                                                 ExternError *err);

int32_t bbs_create_proof_context_set_nonce_prehashed(uint64_t handle,
                                                     ByteArray value,
                                                     ExternError *err);

int32_t bbs_create_proof_context_finish(uint64_t handle, ByteBuffer *proof, ExternError *err);

void free_bbs_sign(uint64_t v, ExternError *err);

int32_t bbs_signature_size();

uint64_t bbs_sign_context_init(ExternError *err);

int32_t bbs_sign_context_add_message_string(uint64_t handle, FfiStr message, ExternError *err);

int32_t bbs_sign_context_add_message_bytes(uint64_t handle, ByteArray message, ExternError *err);

int32_t bbs_sign_context_add_message_prehashed(uint64_t handle,
                                               ByteArray message,
                                               ExternError *err);

int32_t bbs_sign_context_set_secret_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_sign_context_set_public_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_sign_context_finish(uint64_t handle, ByteBuffer *signature, ExternError *err);

uint64_t bbs_verify_context_init(ExternError *err);

int32_t bbs_verify_context_add_message_string(uint64_t handle, FfiStr message, ExternError *err);

int32_t bbs_verify_context_add_message_bytes(uint64_t handle, ByteArray message, ExternError *err);

int32_t bbs_verify_context_add_message_prehashed(uint64_t handle,
                                                 ByteArray message,
                                                 ExternError *err);

int32_t bbs_verify_context_set_public_key(uint64_t handle, ByteArray public_key, ExternError *err);

int32_t bbs_verify_context_set_signature(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_verify_context_finish(uint64_t handle, ExternError *err);

void free_verify_proof(uint64_t v, ExternError *err);

int32_t bbs_get_total_messages_count_for_proof(ByteArray proof);

uint64_t bbs_verify_proof_context_init(ExternError *err);

int32_t bbs_verify_proof_context_add_message_string(uint64_t handle,
                                                    FfiStr message,
                                                    ExternError *err);

int32_t bbs_verify_proof_context_add_message_bytes(uint64_t handle,
                                                   ByteArray message,
                                                   ExternError *err);

int32_t bbs_verify_proof_context_add_message_prehashed(uint64_t handle,
                                                       ByteArray message,
                                                       ExternError *err);

int32_t bbs_verify_proof_context_set_proof(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_verify_proof_context_set_public_key(uint64_t handle, ByteArray value, ExternError *err);

int32_t bbs_verify_proof_context_set_nonce_string(uint64_t handle,
                                                  FfiStr message,
                                                  ExternError *err);

int32_t bbs_verify_proof_context_set_nonce_bytes(uint64_t handle,
                                                 ByteArray value,
                                                 ExternError *err);

int32_t bbs_verify_proof_context_set_nonce_prehashed(uint64_t handle,
                                                     ByteArray value,
                                                     ExternError *err);

int32_t bbs_verify_proof_context_finish(uint64_t handle, ExternError *err);

void free_verify_sign_proof(uint64_t v, ExternError *err);

uint64_t bbs_verify_blind_commitment_context_init(ExternError *err);

int32_t bbs_verify_blind_commitment_context_add_blinded(uint64_t handle,
                                                        uint32_t index,
                                                        ExternError *err);

int32_t bbs_verify_blind_commitment_context_set_public_key(uint64_t handle,
                                                           ByteArray value,
                                                           ExternError *err);

int32_t bbs_verify_blind_commitment_context_set_nonce_string(uint64_t handle,
                                                             FfiStr message,
                                                             ExternError *err);

int32_t bbs_verify_blind_commitment_context_set_nonce_bytes(uint64_t handle,
                                                            ByteArray value,
                                                            ExternError *err);

int32_t bbs_verify_blind_commitment_context_set_nonce_prehashed(uint64_t handle,
                                                                ByteArray value,
                                                                ExternError *err);

int32_t bbs_verify_blind_commitment_context_set_proof(uint64_t handle,
                                                      ByteArray value,
                                                      ExternError *err);

int32_t bbs_verify_blind_commitment_context_finish(uint64_t handle, ExternError *err);

int32_t bls_secret_key_size();

int32_t bls_public_key_g2_size();

int32_t blinding_factor_size();

int32_t bls_public_key_g1_size();

int32_t bls_generate_g2_key(ByteArray seed,
                            ByteBuffer *public_key,
                            ByteBuffer *secret_key,
                            ExternError *err);

int32_t bls_generate_g1_key(ByteArray seed,
                            ByteBuffer *public_key,
                            ByteBuffer *secret_key,
                            ExternError *err);

int32_t bls_generate_blinded_g2_key(ByteArray seed,
                                    ByteBuffer *public_key,
                                    ByteBuffer *secret_key,
                                    ByteBuffer *blinding_factor,
                                    ExternError *err);

int32_t bls_generate_blinded_g1_key(ByteArray seed,
                                    ByteBuffer *public_key,
                                    ByteBuffer *secret_key,
                                    ByteBuffer *blinding_factor,
                                    ExternError *err);

int32_t bls_get_public_key(ByteArray secret_key, ByteBuffer *public_key, ExternError *err);

int32_t bls_secret_key_to_bbs_key(ByteArray secret_key,
                                  uint32_t message_count,
                                  ByteBuffer *public_key,
                                  ExternError *err);

int32_t bls_public_key_to_bbs_key(ByteArray d_public_key,
                                  uint32_t message_count,
                                  ByteBuffer *public_key,
                                  ExternError *err);

} // extern "C"
