/*
 * target_ruby.c
 *
 * Target language specific functions for openwsman swig plugins
 *
 * Here: Ruby
 */

/*****************************************************************************
* Copyright (C) 2008 Novell Inc. All rights reserved.
* Copyright (C) 2008 SUSE Linux Products GmbH. All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 
*   - Redistributions of source code must retain the above copyright notice,
*     this list of conditions and the following disclaimer.
* 
*   - Redistributions in binary form must reproduce the above copyright notice,
*     this list of conditions and the following disclaimer in the documentation
*     and/or other materials provided with the distribution.
* 
*   - Neither the name of Novell Inc. nor of SUSE Linux Products GmbH nor the
*     names of its contributors may be used to endorse or promote products
*     derived from this software without specific prior written permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Novell Inc. OR SUSE Linux Products GmbH OR
* THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/

/* load <RB_PLUGIN_FILE>.rb */
#define PLUGIN_FILE "openwsmanplugin"

/* expect 'module <RB_PLUGIN_MODULE>' inside */
#define PLUGIN_MODULE "Openwsman"

/*
 * load_module
 * separate function for rb_require so it can be wrapped into rb_protect()
 */

static VALUE
load_module()
{
  ruby_script(PLUGIN_FILE);
  return rb_require(PLUGIN_FILE);
}


/*
 * create_plugin (called from rb_protect)
 * load Ruby provider and create plugin instance
 * 
 */

static VALUE
create_plugin(VALUE args)
{
  return rb_funcall2(_TARGET_MODULE, rb_intern("create_plugin"), 0, NULL);
}


/*
 * call_plugin
 * call function of plugin instance
 * 
 * I args: pointer to array of at least 3 values
 *         args[0] -> (VALUE) instance
 *         args[1] -> (VALUE) id of function
 *         args[2] -> (int) number of arguments
 *         args[3...n] -> (VALUE) arguments
 */

static VALUE
call_plugin(VALUE args)
{
  VALUE *values = (VALUE *)args;
  return rb_funcall3(values[0], values[1], (int)values[2], values+3);
}



/*
 * get Ruby exception trace -> char *
 * 
 */

#define TB_ERROR(str) {tbstr = str; goto cleanup;}
static char *
get_exc_trace()
{
    VALUE exception = rb_gv_get("$!"); /* get last exception */
    VALUE reason = rb_funcall(exception, rb_intern("to_s"), 0 );
    VALUE trace = rb_gv_get("$@"); /* get last exception trace */
    VALUE backtrace = rb_funcall(trace, rb_intern("join"), 1, rb_str_new("\n\t", 2));

    return fmtstr("%s\n\t%s", StringValuePtr(reason), StringValuePtr(backtrace)); 
}


/*
 * Global Ruby initializer
 * loads the Ruby interpreter
 * init threads
 */

static int
RbGlobalInitialize( )
{
  int error;

  if (_TARGET_INIT)
    {
      return 0; 
    }
  _TARGET_INIT=1;//true
  
  debug("Ruby: Loading");
  
  ruby_init();
  ruby_init_loadpath();
  ruby_script(PLUGIN_FILE);
  extern void SWIG_init();
  SWIG_init();

  /* load module */
  rb_protect(load_module, Qnil, &error);
  if (error)
    {
      char *trace = get_exc_trace();

      error("Ruby: import '%s' failed: %s", PLUGIN_FILE, trace);
      return -1;
    }
  _TARGET_MODULE = rb_const_get(rb_cModule, rb_intern(PLUGIN_MODULE));
  if (NIL_P(_TARGET_MODULE))
    {
      error("Ruby: import '%s' doesn't define module '%s'", PLUGIN_MODULE);
      return -1;
    }  
  debug("RbGlobalInitialize() succeeded -> module %s @ %p", PLUGIN_MODULE, _TARGET_MODULE); 
  return 0; 
}


/*---------------------------------------------------------------*/

/*
 * local (per plugin) Ruby initializer
 * keeps track of reference count
 * 
 * self: handle (from dlopen)
 * data: user data return
 */

static int
TargetInitialize( void *self, void **data )
{
  VALUE args[2];
  int error;

  debug("TargetInitialize(Ruby)");
  /* Set _PLUGIN_INIT, protected by _PLUGIN_INIT_MUTEX
   * so we call ruby_finalize() only once.
   */
  if (pthread_mutex_lock(&_PLUGIN_INIT_MUTEX))
  {
      perror("Can't lock _PLUGIN_INIT_MUTEX");
      abort();
  }
  error = RbGlobalInitialize( ); 
  pthread_mutex_unlock(&_PLUGIN_INIT_MUTEX);
  if (error != 0)
  {
     goto exit;
  }

  debug("TargetInitialize(Ruby) called");

  *data = (void *)rb_protect(create_plugin, (VALUE)args, &error);
  if (error)
    {
      char *trace = get_exc_trace();
      error("Ruby: FAILED creating:", trace);
    }
  debug("Created plugin: klass @ %p", *data);
exit:
  debug("Initialize() %s", (error == 0)? "succeeded":"failed");
  return error;
}


/*
 * TargetCall
 * Call function 'opname' with nargs arguments within instance
 * 
 * doc: in_doc from context, needed for fault generation
 * instance: module
 * opname: name of method to call
 * nargs: number of arguments
 * ...: arguments as varargs
 * 
 */

static int 
TargetCall(WsXmlDocH doc, VALUE instance, const char* opname, int nargs, ...)
{
  int i; 
  VALUE *args, result, op = rb_intern(opname);
  va_list vargs; 
  WsmanStatus status;
  wsman_status_init(&status);

  debug("TargetCall(Ruby): %p.%s", (void *)instance, opname);
  
  /* add instance, op and nargs to the args array, so rb_protect can be called */
  nargs += 3;
  args = (VALUE *)malloc(nargs * sizeof(VALUE));
  if (args == NULL)
    {
      error("Out of memory"); 
      abort();
    }
  args[0] = instance;
  args[1] = op;
  args[2] = (VALUE)(nargs-3);
  if (nargs > 3)
    {
      va_start(vargs, nargs);
      for (i = 3; i < nargs; ++i)
	{
	  args[i] = va_arg(vargs, VALUE);
	}
      va_end(vargs);
    }

  
  /* call the Ruby function
   * possible results:
   *   i nonzero: Exception raised
   *   result == nil: not (or badly) implemented 
   *   result == true: success
   *   result == Array: pair of CMPIStatus rc(int) and msg(string)
   */
  result = rb_protect(call_plugin, (VALUE)args, &i);
  free( args );

  if (i) /* exception ? */
    {
      char *trace = get_exc_trace();
      status.fault_msg = fmtstr("Ruby: calling '%s' failed: %s", opname, trace); 
      status.fault_code = WSMAN_INTERNAL_ERROR;
      status.fault_detail_code = 0;
      error("%s", status.fault_msg);
      return 1;
    }
  
  if (NIL_P(result)) /* not or wrongly implemented */
    {
      status.fault_code = WSA_ENDPOINT_UNAVAILABLE;
      status.fault_detail_code = 0;
      return 1;
    }

  if (result != Qtrue)
    {
      int len;
      VALUE resulta = rb_check_array_type(result);
      
      if (NIL_P(resulta))
	{
	  status.fault_msg = fmtstr("Ruby: calling '%s' returned unknown result", opname);
	  status.fault_code = WSMAN_INTERNAL_ERROR;
	  status.fault_detail_code = 0;
	  return 1;
	}
  
      len = RARRAY_LEN(RARRAY(resulta));
      if (len > 0) 
	{
	  VALUE code = rb_ary_entry(resulta, 0);
	  if (!FIXNUM_P(code))
	    {
	      status.fault_msg = fmtstr("Ruby: calling '%s' returned non-numeric code", opname);
	      status.fault_code = WSMAN_INTERNAL_ERROR;
	      status.fault_detail_code = 0;
	      return 1;
	    }
	  status.fault_code = FIX2LONG(code);
	}
      
      if (len > 1) 
	{
	  VALUE detail = rb_ary_entry(resulta, 1);
	  if (!FIXNUM_P(detail))
	    {
	      status.fault_msg = fmtstr("Ruby: calling '%s' returned non-numeric detail", opname);
	      status.fault_code = WSMAN_INTERNAL_ERROR;
	      status.fault_detail_code = 0;
	      return 1;
	    }
	  status.fault_detail_code = FIX2LONG(detail);
	}
      if (len > 2)
	{
	  VALUE str = rb_ary_entry(resulta, 2);
	  status.fault_msg = StringValuePtr(str);
	}
      wsman_generate_fault( doc, status.fault_code, status.fault_detail_code, status.fault_msg );
    }
  
  /* all is fine */

  return 0;
}


/*
 * TargetCleanup
 */

static void
TargetCleanup( void *self, void *data )
{
  debug("TargetCleanup(Ruby)");
  ruby_finalize();
  _TARGET_MODULE = Qnil;   
  return;
}


static VALUE
call_namespaces(VALUE klass)
{
  return rb_funcall( klass, rb_intern( "namespaces" ), 0 );
}


/*
 * TargetEndpoints
 */

static list_t *
TargetEndpoints( void *self, void *data )
{
    int error;
    VALUE klass = (VALUE)data;
    VALUE rbnamespaces, ary;
    debug("TargetEndpoints(Ruby), data %p, klass %p", data, klass);

    /*
     * Get namespaces
     */
  
    list_t *namespaces = list_create(LISTCOUNT_T_MAX);
    debug("TargetEndpoints(Ruby), calling namespaces");
    rbnamespaces = rb_protect(call_namespaces, klass, &error);
    if (error) {
      char *trace = get_exc_trace();

      error("Ruby: 'namespaces' failed: %s", PLUGIN_FILE, trace);
      return NULL;
    }
    debug("TargetEndpoints(Ruby), called namespaces: %p", rbnamespaces);
    ary = rb_check_array_type( rbnamespaces );
    if (NIL_P(ary)) {
      rb_raise( rb_eArgError, "namespaces is not array");
    }
    int len = RARRAY_LEN(RARRAY(ary));
    if (len <= 0) {
      rb_raise( rb_eArgError, "namespaces returned array with %d elements", len);
    }
    int i;
    for (i = 0; i < len; ++i) {
      lnode_t *node;
      VALUE elem = RARRAY_PTR(RARRAY(ary))[i];
      VALUE pair = rb_check_array_type( elem );
      if (NIL_P(pair)) {
	rb_raise( rb_eArgError, "namespaces must return array of arrays");
      }
      if (RARRAY_LEN(RARRAY(pair)) != 2) {
	rb_raise( rb_eArgError, "namespaces must return array of ['<namespace>','<class_prefix>']");
      }
      WsSupportedNamespaces *ns = (WsSupportedNamespaces *)u_malloc(sizeof(WsSupportedNamespaces));
      ns->ns = StringValuePtr( RARRAY_PTR(RARRAY(pair))[0] );
      ns->class_prefix = StringValuePtr( RARRAY_PTR(RARRAY(pair))[1] );
      node = lnode_create(ns);
      list_append(namespaces, node);
    }

  return namespaces;
}
