/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
%module cproton

%{
#include <proton/connection_driver.h>
#include <proton/engine.h>
#include <proton/handlers.h>
#include <proton/message.h>
#include <proton/messenger.h>
#include <proton/reactor.h>
#include <proton/sasl.h>
#include <proton/ssl.h>
#include <proton/types.h>
#include <proton/url.h>
%}

/*
NOTE: According to ccache-swig man page: "Known problems are using
preprocessor directives within %inline blocks and the use of ’#pragma SWIG’."
This includes using macros in an %inline section.

Keep preprocessor directives and macro expansions in the normal header section.
*/

%include <cstring.i>

%cstring_output_withsize(char *OUTPUT, size_t *OUTPUT_SIZE)
%cstring_output_allocate_size(char **ALLOC_OUTPUT, size_t *ALLOC_SIZE, free(*$1));
%cstring_output_maxsize(char *OUTPUT, size_t MAX_OUTPUT_SIZE)

%{
#if !defined(RSTRING_LEN)
#  define RSTRING_LEN(x) (RSTRING(x)->len)
#  define RSTRING_PTR(x) (RSTRING(x)->ptr)
#endif
%}

/* Treat pn_handle_t like uintptr_t - syntactically it is a C void* but really it's just an int */
%apply uintptr_t { pn_handle_t };

%typemap(in) pn_bytes_t {
  if ($input == Qnil) {
    $1.start = NULL;
    $1.size = 0;
  } else {
    $1.start = RSTRING_PTR($input);
    if (!$1.start) {
      $1.size = 0;
    }
    $1.size = RSTRING_LEN($input);
  }
}

%typemap(out) pn_bytes_t {
  $result = rb_str_new($1.start, $1.size);
}

%typemap(in) pn_atom_t
{
  if ($input == Qnil)
    {
      $1.type = PN_NULL;
    }
  else
    {
      switch(TYPE($input))
        {
        case T_TRUE:
          $1.type = PN_BOOL;
          $1.u.as_bool = true;
          break;

        case T_FALSE:
          $1.type = PN_BOOL;
          $1.u.as_bool = false;
          break;

        case T_FLOAT:
          $1.type = PN_FLOAT;
          $1.u.as_float = NUM2DBL($input);
          break;

        case T_STRING:
          $1.type = PN_STRING;
          $1.u.as_bytes.start = RSTRING_PTR($input);
          if ($1.u.as_bytes.start)
            {
              $1.u.as_bytes.size = RSTRING_LEN($input);
            }
          else
            {
              $1.u.as_bytes.size = 0;
            }
          break;

        case T_FIXNUM:
          $1.type = PN_INT;
          $1.u.as_int = FIX2LONG($input);
          break;

        case T_BIGNUM:
          $1.type = PN_LONG;
          $1.u.as_long = NUM2LL($input);
          break;

        }
    }
}

%typemap(out) pn_atom_t
{
  switch($1.type)
    {
    case PN_NULL:
      $result = Qnil;
      break;

    case PN_BOOL:
      $result = $1.u.as_bool ? Qtrue : Qfalse;
      break;

    case PN_BYTE:
      $result = INT2NUM($1.u.as_byte);
      break;

    case PN_UBYTE:
      $result = UINT2NUM($1.u.as_ubyte);
      break;

    case PN_SHORT:
      $result = INT2NUM($1.u.as_short);
      break;

    case PN_USHORT:
      $result = UINT2NUM($1.u.as_ushort);
      break;

    case PN_INT:
      $result = INT2NUM($1.u.as_int);
      break;

     case PN_UINT:
      $result = UINT2NUM($1.u.as_uint);
      break;

    case PN_LONG:
      $result = LL2NUM($1.u.as_long);
      break;

    case PN_ULONG:
      $result = ULL2NUM($1.u.as_ulong);
      break;

    case PN_FLOAT:
      $result = rb_float_new($1.u.as_float);
      break;

    case PN_DOUBLE:
      $result = rb_float_new($1.u.as_double);
      break;

    case PN_STRING:
      $result = rb_str_new($1.u.as_bytes.start, $1.u.as_bytes.size);
      break;

    default:
       break;
    }
}

%typemap (in) pn_decimal32_t
{
  $1 = NUM2UINT($input);
}

%typemap (out) pn_decimal32_t
{
  $result = UINT2NUM($1);
}

%typemap (in) pn_decimal64_t
{
  $1 = NUM2ULL($input);
}

%typemap (out) pn_decimal64_t
{
  $result = ULL2NUM($1);
}

%typemap (in) pn_decimal128_t
{
  int index;

  for(index = 0; index < 16; index++)
    {
      VALUE element = rb_ary_entry($input, index);
      $1.bytes[16 - (index + 1)] = FIX2INT(element);
    }
}

%typemap (out) pn_decimal128_t
{
  int index;

  $result = rb_ary_new2(16);
  for(index = 0; index < 16; index++)
    {
      rb_ary_store($result, 16 - (index + 1), CHR2FIX($1.bytes[index]));
    }
}

%typemap (in) pn_uuid_t
{
  int index;

  for(index = 0; index < 16; index++)
    {
      VALUE element = rb_ary_entry($input, index);
      $1.bytes[16 - (index + 1)] = FIX2INT(element);
    }
}

%typemap (out) pn_uuid_t
{
  int index;

  $result = rb_ary_new2(16);
  for(index = 0; index < 16; index++)
    {
      rb_ary_store($result, 16 - (index + 1), CHR2FIX($1.bytes[index]));
    }
}

int pn_message_encode(pn_message_t *msg, char *OUTPUT, size_t *OUTPUT_SIZE);
%ignore pn_message_encode;

ssize_t pn_link_send(pn_link_t *transport, char *STRING, size_t LENGTH);
%ignore pn_link_send;

%rename(pn_link_recv) wrap_pn_link_recv;
%inline %{
  int wrap_pn_link_recv(pn_link_t *link, char *OUTPUT, size_t *OUTPUT_SIZE) {
    ssize_t sz = pn_link_recv(link, OUTPUT, *OUTPUT_SIZE);
    if (sz >= 0) {
      *OUTPUT_SIZE = sz;
    } else {
      *OUTPUT_SIZE = 0;
    }
    return sz;
  }
%}
%ignore pn_link_recv;

ssize_t pn_transport_input(pn_transport_t *transport, char *STRING, size_t LENGTH);
%ignore pn_transport_input;

%rename(pn_transport_output) wrap_pn_transport_output;
%inline %{
  int wrap_pn_transport_output(pn_transport_t *transport, char *OUTPUT, size_t *OUTPUT_SIZE) {
    ssize_t sz = pn_transport_output(transport, OUTPUT, *OUTPUT_SIZE);
    if (sz >= 0) {
      *OUTPUT_SIZE = sz;
    } else {
      *OUTPUT_SIZE = 0;
    }
    return sz;
  }
%}
%ignore pn_transport_output;

%rename(pn_transport_peek) wrap_pn_transport_peek;
%inline %{
  int wrap_pn_transport_peek(pn_transport_t *transport, char *OUTPUT, size_t *OUTPUT_SIZE) {
    ssize_t sz = pn_transport_peek(transport, OUTPUT, *OUTPUT_SIZE);
    if(sz >= 0) {
      *OUTPUT_SIZE = sz;
    } else {
      *OUTPUT_SIZE = 0;
    }
    return sz;
  }
%}
%ignore pn_transport_peek;

%rename(pn_delivery) wrap_pn_delivery;
%inline %{
  pn_delivery_t *wrap_pn_delivery(pn_link_t *link, char *STRING, size_t LENGTH) {
    return pn_delivery(link, pn_dtag(STRING, LENGTH));
  }
%}
%ignore pn_delivery;

// Suppress "Warning(451): Setting a const char * variable may leak memory." on pn_delivery_tag_t
%warnfilter(451) pn_delivery_tag_t;
%rename(pn_delivery_tag) wrap_pn_delivery_tag;
%inline %{
  void wrap_pn_delivery_tag(pn_delivery_t *delivery, char **ALLOC_OUTPUT, size_t *ALLOC_SIZE) {
    pn_delivery_tag_t tag = pn_delivery_tag(delivery);
    *ALLOC_OUTPUT = malloc(tag.size);
    *ALLOC_SIZE = tag.size;
    memcpy(*ALLOC_OUTPUT, tag.start, tag.size);
  }
%}
%ignore pn_delivery_tag;

bool pn_ssl_get_cipher_name(pn_ssl_t *ssl, char *OUTPUT, size_t MAX_OUTPUT_SIZE);
%ignore pn_ssl_get_cipher_name;

bool pn_ssl_get_protocol_name(pn_ssl_t *ssl, char *OUTPUT, size_t MAX_OUTPUT_SIZE);
%ignore pn_ssl_get_protocol_name;

/* TODO aconway 2018-02-14: Remove RB_BLOCKING_CALL once messenger is deprecated */

/* Don't use %inline sections for #define */
%{
#if defined(RUBY_USE_rb_thread_call_without_gvl)

  #include <ruby/thread.h>
  typedef void *non_blocking_return_t;
  #define RB_BLOCKING_CALL (VALUE)rb_thread_call_without_gvl

#elif defined(RUBY_USE_rb_thread_blocking_region)

  typedef VALUE non_blocking_return_t;
  #define RB_BLOCKING_CALL rb_thread_blocking_region

#endif
%}

%rename(pn_messenger_send) wrap_pn_messenger_send;
%rename(pn_messenger_recv) wrap_pn_messenger_recv;
%rename(pn_messenger_work) wrap_pn_messenger_work;

%inline %{

#if defined(RB_BLOCKING_CALL)

    static non_blocking_return_t pn_messenger_send_no_gvl(void *args) {
    VALUE result = Qnil;
    pn_messenger_t *messenger = (pn_messenger_t *)((void **)args)[0];
    int *limit = (int *)((void **)args)[1];

    int rc = pn_messenger_send(messenger, *limit);

    result = INT2NUM(rc);
    return (non_blocking_return_t )result;
    }

    static non_blocking_return_t pn_messenger_recv_no_gvl(void *args) {
    VALUE result = Qnil;
    pn_messenger_t *messenger = (pn_messenger_t *)((void **)args)[0];
    int *limit = (int *)((void **)args)[1];

    int rc = pn_messenger_recv(messenger, *limit);

    result = INT2NUM(rc);
    return (non_blocking_return_t )result;
  }

    static non_blocking_return_t pn_messenger_work_no_gvl(void *args) {
      VALUE result = Qnil;
      pn_messenger_t *messenger = (pn_messenger_t *)((void **)args)[0];
      int *timeout = (int *)((void **)args)[1];

      int rc = pn_messenger_work(messenger, *timeout);

      result = INT2NUM(rc);
      return (non_blocking_return_t )result;
    }

#endif

  int wrap_pn_messenger_send(pn_messenger_t *messenger, int limit) {
    int result = 0;

#if defined(RB_BLOCKING_CALL)

    // only release the gil if we're blocking
    if(pn_messenger_is_blocking(messenger)) {
      VALUE rc;
      void* args[2];

      args[0] = messenger;
      args[1] = &limit;

      rc = RB_BLOCKING_CALL(pn_messenger_send_no_gvl,
                            &args, RUBY_UBF_PROCESS, NULL);

      if(RTEST(rc))
        {
          result = FIX2INT(rc);
        }
    }

#else // !defined(RB_BLOCKING_CALL)
    result = pn_messenger_send(messenger, limit);
#endif // defined(RB_BLOCKING_CALL)

    return result;
  }

  int wrap_pn_messenger_recv(pn_messenger_t *messenger, int limit) {
    int result = 0;

#if defined(RB_BLOCKING_CALL)
    // only release the gil if we're blocking
    if(pn_messenger_is_blocking(messenger)) {
      VALUE rc;
      void* args[2];

      args[0] = messenger;
      args[1] = &limit;

      rc = RB_BLOCKING_CALL(pn_messenger_recv_no_gvl,
                            &args, RUBY_UBF_PROCESS, NULL);

      if(RTEST(rc))
        {
          result = FIX2INT(rc);
        }

    } else {
      result = pn_messenger_recv(messenger, limit);
    }
#else // !defined(RB_BLOCKING_CALL)
    result = pn_messenger_recv(messenger, limit);
#endif // defined(RB_BLOCKING_CALL)

      return result;
  }

  int wrap_pn_messenger_work(pn_messenger_t *messenger, int timeout) {
    int result = 0;

#if defined(RB_BLOCKING_CALL)
    // only release the gil if we're blocking
    if(timeout) {
      VALUE rc;
      void* args[2];

      args[0] = messenger;
      args[1] = &timeout;

      rc = RB_BLOCKING_CALL(pn_messenger_work_no_gvl,
                            &args, RUBY_UBF_PROCESS, NULL);

      if(RTEST(rc))
        {
          result = FIX2INT(rc);
        }
    } else {
      result = pn_messenger_work(messenger, timeout);
    }
#else
    result = pn_messenger_work(messenger, timeout);
#endif

    return result;
  }

%}

%ignore pn_messenger_send;
%ignore pn_messenger_recv;
%ignore pn_messenger_work;

%{
typedef struct Pn_rbkey_t {
  void *registry;
  char *method;
  char *key_value;
} Pn_rbkey_t;

void Pn_rbkey_initialize(void *vp_rbkey) {
  Pn_rbkey_t *rbkey = (Pn_rbkey_t*)vp_rbkey;
  assert(rbkey);
  rbkey->registry = NULL;
  rbkey->method = NULL;
  rbkey->key_value = NULL;
}

void Pn_rbkey_finalize(void *vp_rbkey) {
  Pn_rbkey_t *rbkey = (Pn_rbkey_t*)vp_rbkey;
  if(rbkey && rbkey->registry && rbkey->method && rbkey->key_value) {
    rb_funcall((VALUE )rbkey->registry, rb_intern(rbkey->method), 1, rb_str_new2(rbkey->key_value));
  }
  if(rbkey->key_value) {
    free(rbkey->key_value);
    rbkey->key_value = NULL;
  }
}

/* NOTE: no macro or preprocessor definitions in %inline sections */
#define CID_Pn_rbkey CID_pn_void
#define Pn_rbkey_inspect NULL
#define Pn_rbkey_compare NULL
#define Pn_rbkey_hashcode NULL

pn_class_t* Pn_rbkey__class(void) {
    static pn_class_t clazz = PN_CLASS(Pn_rbkey);
    return &clazz;
}

Pn_rbkey_t *Pn_rbkey_new(void) {
    return (Pn_rbkey_t *) pn_class_new(Pn_rbkey__class(), sizeof(Pn_rbkey_t));
}
%}

pn_class_t* Pn_rbkey__class(void);
Pn_rbkey_t *Pn_rbkey_new(void);

%inline %{

Pn_rbkey_t *Pn_rbkey_new(void);

void Pn_rbkey_set_registry(Pn_rbkey_t *rbkey, void *registry) {
  assert(rbkey);
  rbkey->registry = registry;
}

void *Pn_rbkey_get_registry(Pn_rbkey_t *rbkey) {
  assert(rbkey);
  return rbkey->registry;
}

void Pn_rbkey_set_method(Pn_rbkey_t *rbkey, char *method) {
  assert(rbkey);
  rbkey->method = method;
}

char *Pn_rbkey_get_method(Pn_rbkey_t *rbkey) {
  assert(rbkey);
  return rbkey->method;
}

void Pn_rbkey_set_key_value(Pn_rbkey_t *rbkey, char *key_value) {
  assert(rbkey);
  rbkey->key_value = malloc(strlen(key_value) + 1);
  strncpy(rbkey->key_value, key_value, strlen(key_value) + 1);
}

char *Pn_rbkey_get_key_value(Pn_rbkey_t *rbkey) {
  assert(rbkey);
  return rbkey->key_value;
}

Pn_rbkey_t *pni_void2rbkey(void *object) {
  return (Pn_rbkey_t *)object;
}

VALUE pn_void2rb(void *object) {
  return (VALUE )object;
}

void *pn_rb2void(VALUE object) {
  return (void *)object;
}

VALUE pni_address_of(void *object) {
  return ULL2NUM((unsigned long )object);
}

%}

int pn_ssl_get_peer_hostname(pn_ssl_t *ssl, char *OUTPUT, size_t *OUTPUT_SIZE);
%ignore pn_ssl_get_peer_hostname;

%inline %{

  VALUE pni_ruby_get_proton_module() {
    VALUE mQpid = rb_define_module("Qpid");
    return rb_define_module_under(mQpid, "Proton");
  }

  void pni_ruby_add_to_registry(VALUE key, VALUE value) {
    VALUE result = rb_funcall(pni_ruby_get_proton_module(), rb_intern("add_to_registry"), 2, key, value);
  }

  VALUE pni_ruby_get_from_registry(VALUE key) {
     return rb_funcall(pni_ruby_get_proton_module(), rb_intern("get_from_registry"), 1, key);
  }

  void pni_ruby_delete_from_registry(VALUE stored_key) {
    rb_funcall(pni_ruby_get_proton_module(), rb_intern("delete_from_registry"), 1, stored_key);
  }

  typedef struct {
    VALUE handler_key;
  } Pni_rbhandler_t;

  static Pni_rbhandler_t *pni_rbhandler(pn_handler_t *handler) {
    return (Pni_rbhandler_t *) pn_handler_mem(handler);
  }

  static void pni_rbdispatch(pn_handler_t *handler, pn_event_t *event, pn_event_type_t type) {
    Pni_rbhandler_t *rbh = pni_rbhandler(handler);
    VALUE rbhandler = pni_ruby_get_from_registry(rbh->handler_key);

    rb_funcall(rbhandler, rb_intern("dispatch"), 2, SWIG_NewPointerObj(event, SWIGTYPE_p_pn_event_t, 0), INT2FIX(type));
  }

  static void pni_rbhandler_finalize(pn_handler_t *handler) {
    Pni_rbhandler_t *rbh = pni_rbhandler(handler);
    pni_ruby_delete_from_registry(rbh->handler_key);
  }

  pn_handler_t *pn_rbhandler(VALUE handler) {
    pn_handler_t *chandler = pn_handler_new(pni_rbdispatch, sizeof(Pni_rbhandler_t), pni_rbhandler_finalize);
    Pni_rbhandler_t *rhy = pni_rbhandler(chandler);

    VALUE ruby_key = rb_class_new_instance(0, NULL, rb_cObject);
    pni_ruby_add_to_registry(ruby_key, handler);

    rhy->handler_key = ruby_key;

    return chandler;
  }


  /* Helpers for working with pn_connection_driver_t */

  size_t pni_connection_driver_read_size(pn_connection_driver_t* d) {
    return pn_connection_driver_read_buffer(d).size;
  }

  size_t pni_connection_driver_write_size(pn_connection_driver_t* d) {
    return pn_connection_driver_write_buffer(d).size;
  }

  pn_connection_t *pni_connection_driver_connection(pn_connection_driver_t* d) {
    return d->connection;
  }

  pn_transport_t *pni_connection_driver_transport(pn_connection_driver_t* d) {
    return d->transport;
  }

  size_t pni_connection_driver_read_copy(pn_connection_driver_t* d, char *STRING, size_t LENGTH ) {
    pn_rwbytes_t rbuf = pn_connection_driver_read_buffer(d);
    size_t n = LENGTH < rbuf.size ? LENGTH : rbuf.size;
    memcpy(rbuf.start, STRING, n);
    pn_connection_driver_read_done(d, n);
    return n;
  }

  pn_connection_driver_t *pni_connection_driver() {
    pn_connection_driver_t *d = (pn_connection_driver_t*)malloc(sizeof(*d));
    if (pn_connection_driver_init(d, NULL, NULL) != 0) {
      free(d);
      return NULL;
    }
    return d;
  }

%}

%include "proton/cproton.i"
