%module rlibmemcached
%{
#include <libmemcached/visibility.h>
#include <libmemcached/memcached.h>
%}

%warnfilter(SWIGWARN_RUBY_WRONG_NAME) memcached_st;
%warnfilter(SWIGWARN_RUBY_WRONG_NAME) memcached_server_st;
%warnfilter(SWIGWARN_RUBY_WRONG_NAME) memcached_stat_st;
%warnfilter(SWIGWARN_RUBY_WRONG_NAME) memcached_string_st;
%warnfilter(SWIGWARN_RUBY_WRONG_NAME) memcached_result_st;

%include "typemaps.i"
%include "libmemcached/visibility.h"

//// Memory management

// Register libmemcached's struct free function to prevent memory leaks
%freefunc memcached_st "memcached_free";
%freefunc memcached_stat_st "memcached_stat_free";
%freefunc memcached_server_st "memcached_server_free";

// Register which functions generate new objects
%newobject memcached_create;
%newobject memcached_clone;
%newobject memcached_stat_get_value;
// %newobject memcached_stat;

// %trackobjects; // Doesn't fix any interesting leaks

typedef const struct memcached_server_st *memcached_server_instance_st;

//// Input maps

%apply unsigned short { in_port_t };
%apply unsigned short { uint8_t };
%apply unsigned int { uint16_t };
%apply unsigned int { uint32_t server_failure_counter };
%apply unsigned int { uint32_t user_spec_len };
%apply unsigned long { uint32_t flags, uint32_t offset, uint32_t weight, time_t expiration };
%apply unsigned long long { uint64_t data, uint64_t cas };

// Array of strings map for multiget
%typemap(in) (const char * const *keys, const size_t *key_length, size_t number_of_keys) {
  unsigned int i;
  Check_Type($input, T_ARRAY);
  $3 = (unsigned int) RARRAY_LEN($input);
  $2 = (size_t *) malloc(($3+1)*sizeof(size_t));
  $1 = (char **) malloc(($3+1)*sizeof(char *));
  for(i = 0; i < $3; i ++) {
    Check_Type(RARRAY_PTR($input)[i], T_STRING);
    $2[i] = RSTRING_LEN(RARRAY_PTR($input)[i]);
    $1[i] = StringValuePtr(RARRAY_PTR($input)[i]);
  }
}

%typemap(freearg) (const char * const *keys, const size_t *key_length, size_t number_of_keys) {
   free($1);
   free($2);
}

// Generic strings
%typemap(in) (const char *str, size_t len) {
 $1 = STR2CSTR($input);
 $2 = (size_t) RSTRING_LEN($input);
};

// Void type strings without lengths for prefix_key callback
%typemap(in) (void *data) {
  if ( (size_t) RSTRING_LEN($input) == 0) {
    $1 = NULL;
  } else {
    $1 = STR2CSTR($input);
  }
};

%apply (const char *str, size_t len) {
  (const char *namespace, size_t namespace_length),
  (const char *key, size_t key_length),
  (const char *value, size_t value_length)
};

// Key strings with same master key
// This will have to go if people actually want to set the master key separately
%typemap(in) (const char *master_key, size_t master_key_length, const char *key, size_t key_length) {
 $3 = $1 = STR2CSTR($input);
 $4 = $2 = (size_t) RSTRING_LEN($input);
};


//// Output maps

%apply unsigned short *OUTPUT {memcached_return_t *error}
%apply unsigned int *OUTPUT {uint32_t *flags}
%apply size_t *OUTPUT {size_t *value_length}
%apply unsigned long long *OUTPUT {uint64_t *value}

// Uint64
%typemap(out) (uint64_t) {
  $result = ULL2NUM($1);
};

// Uint32
%typemap(out) (uint32_t) {
 $result = UINT2NUM($1);
};

// String for memcached_fetch
%typemap(in, numinputs=0) (char *key, size_t *key_length) {
  char string[256];
  size_t length = 0;
  $1 = string;
  $2 = &length;
};

// Strings with lengths
%typemap(argout) (char *key, size_t *key_length) {
  rb_ary_push($result, rb_str_new($1, *$2));
}

// Array of strings
// Only used by memcached_stat_get_keys() and not performance-critical
%typemap(out) (char **) {
  int i;
  VALUE ary = rb_ary_new();
  $result = rb_ary_new();

  for(i=0; $1[i] != NULL; i++) {
    rb_ary_store(ary, i, rb_str_new2($1[i]));
  }
  rb_ary_push($result, ary);
  free($1);
};

//// SWIG includes, for functions, constants, and structs

%include "libmemcached/visibility.h"
%include "libmemcached/memcached.h"
%include "libmemcached/return.h"
%include "libmemcached/strerror.h"
%include "libmemcached/version.h"
%include "libmemcached/constants.h"
%include "libmemcached/get.h"
%include "libmemcached/storage.h"
%include "libmemcached/result.h"
%include "libmemcached/server.h"
%include "libmemcached/sasl.h"
%include "libmemcached/callback.h"
%include "libmemcached/behavior.h"
%include "libmemcached/array.h"
%include "libmemcached/quit.h"
%include "libmemcached/flush.h"
%include "libmemcached/delete.h"
%include "libmemcached/stats.h"
%include "libmemcached/auto.h"
%include "libmemcached/error.h"
%include "libmemcached/touch.h"

//// Custom C functions

VALUE rb_str_new_by_ref(char *ptr, long len);
%{
VALUE rb_str_new_by_ref(char *ptr, long len)
{
    NEWOBJ(str, struct RString);
    OBJSETUP(str, rb_cString, T_STRING);
#ifdef RSTRING_NOEMBED
    /* Ruby 1.9 */
    str->as.heap.ptr = ptr;
    str->as.heap.len = len;
    str->as.heap.aux.capa = len + 1;
    // Set STR_NOEMBED
    FL_SET(str, FL_USER1);
#else
    /* Ruby 1.8 */
    str->ptr = ptr;
    str->len = len;
    str->aux.capa = 0;
#endif
    return (VALUE)str;
}
%}

//// Manual wrappers

// Single get
VALUE memcached_get_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, memcached_return_t *error);
%{
VALUE memcached_get_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, memcached_return_t *error) {
  size_t value_length = 0;
  char *value = memcached_get(ptr, key, key_length, &value_length, flags, error);
  return rb_str_new_by_ref(value, value_length);
};
%}

VALUE memcached_get_len_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, uint32_t user_spec_len, memcached_return_t *error);
%{
VALUE memcached_get_len_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, uint32_t user_spec_len, memcached_return_t *error) {
  size_t value_length = 0;
  char *value = memcached_get_len(ptr, key, key_length, &value_length, flags, user_spec_len, error);
  return rb_str_new_by_ref(value, value_length);
};
%}

VALUE memcached_get_from_last_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, memcached_return_t *error);
%{
VALUE memcached_get_from_last_rvalue(memcached_st *ptr, const char *key, size_t key_length, uint32_t *flags, memcached_return_t *error) {
  size_t value_length = 0;
  char *value = memcached_get_from_last(ptr, key, key_length, &value_length, flags, error);
  return rb_str_new_by_ref(value, value_length);
};
%}

// Multi get
VALUE memcached_fetch_rvalue(memcached_st *ptr, char *key, size_t *key_length, uint32_t *flags, memcached_return_t *error);
%{
VALUE memcached_fetch_rvalue(memcached_st *ptr, char *key, size_t *key_length, uint32_t *flags, memcached_return_t *error) {
  size_t value_length = 0;
  VALUE ary = rb_ary_new();
  char *value = memcached_fetch(ptr, key, key_length, &value_length, flags, error);
  VALUE str;
  if (value || (!value && *error == MEMCACHED_SUCCESS))
    str = rb_str_new_by_ref(value, value_length);
  else
    str = Qnil;
  rb_ary_push(ary, str);
  return ary;
};
%}

// Ruby isn't aware that the pointer is an array... there is probably a better way to do this
memcached_server_st *memcached_select_server_at(memcached_st *in_ptr, int index);
%{
memcached_server_st *memcached_select_server_at(memcached_st *in_ptr, int index) {
  return &(in_ptr->servers[index]);
};
%}

// Same, but for stats
memcached_stat_st *memcached_select_stat_at(memcached_st *in_ptr, memcached_stat_st *stat_ptr, int index);
%{
memcached_stat_st *memcached_select_stat_at(memcached_st *in_ptr, memcached_stat_st *stat_ptr, int index) {
  return &(stat_ptr[index]);
};
%}

// Wrap only hash function
// Uint32
VALUE memcached_generate_hash_rvalue(const char *key, size_t key_length, memcached_hash_t hash_algorithm);
%{
VALUE memcached_generate_hash_rvalue(const char *key, size_t key_length, memcached_hash_t hash_algorithm) {
  return UINT2NUM(memcached_generate_hash_value(key, key_length, hash_algorithm));
};
%}


// Initialization for SASL
%init %{
  if (sasl_client_init(NULL) != SASL_OK) {
    fprintf(stderr, "Failed to initialized SASL.\n");
  }
%}