/* Author: Martin Povolny (xpovolny@fi.muni.cz) */

#include "ruby_libxml.h"
#include "ruby_xml_input_cbg.h"

/* Document-class: LibXML::XML::InputCallbacks
 *
 * Support for adding custom scheme handlers. */

static ic_scheme *first_scheme = 0;
static int input_callbacks_registered = 0;

static int ic_match(char const *filename);
static void* ic_open(char const *filename);
static int ic_read(void *context, char *buffer, int len);
static int ic_close(void *context);

static void input_callbacks_register_once(void)
{
  if (input_callbacks_registered)
    return;

  xmlRegisterInputCallbacks(ic_match, ic_open, ic_read, ic_close);
  input_callbacks_registered = 1;
}

static char *ic_strdup(const char *string)
{
  size_t length = strlen(string) + 1;
  char *copy = ruby_xmalloc(length);
  memcpy(copy, string, length);
  return copy;
}

static void ic_scheme_free(ic_scheme *scheme)
{
  rb_gc_unregister_address(&scheme->class);
  ruby_xfree(scheme->scheme_name);
  ruby_xfree(scheme);
}

static int ic_match(char const *filename)
{
  ic_scheme *scheme;

  //fprintf( stderr, "ic_match: %s\n", filename );

  scheme = first_scheme;
  while (0 != scheme)
  {
    if (!xmlStrncasecmp(BAD_CAST filename, BAD_CAST scheme->scheme_name, scheme->name_len))
    {
      return 1;
    }
    scheme = scheme->next_scheme;
  }
  return 0;
}

static void* ic_open(char const *filename)
{
  ic_doc_context *ic_doc;
  ic_scheme *scheme;
  VALUE res;

  scheme = first_scheme;
  while (0 != scheme)
  {
    if (!xmlStrncasecmp(BAD_CAST filename, BAD_CAST scheme->scheme_name, scheme->name_len))
    {
      res = rb_funcall(scheme->class, rb_intern("document_query"), 1,
          rb_str_new2(filename));

      ic_doc = ALLOC(ic_doc_context);
      ic_doc->buffer = ic_strdup(StringValueCStr(res));

      ic_doc->bpos = ic_doc->buffer;
      ic_doc->remaining = (int)strlen(ic_doc->buffer);
      return ic_doc;
    }
    scheme = scheme->next_scheme;
  }
  return 0;
}

static int ic_read(void *context, char *buffer, int len)
{
  ic_doc_context *ic_doc;
  int ret_len;
  ic_doc = (ic_doc_context*) context;

  if (len >= ic_doc->remaining)
  {
    ret_len = ic_doc->remaining;
  }
  else
  {
    ret_len = len;
  }
  ic_doc->remaining -= ret_len;
  strncpy(buffer, ic_doc->bpos, ret_len);
  ic_doc->bpos += ret_len;

  return ret_len;
}

static int ic_close(void *context)
{
  ruby_xfree(((ic_doc_context*) context)->buffer);
  ruby_xfree(context);
  return 1;
}

/*
 * call-seq:
 *    register
 *
 * Register a new set of I/O callback for handling parser input.
 */
static VALUE input_callbacks_register_input_callbacks(VALUE self)
{
  input_callbacks_register_once();
  return (Qtrue);
}

/*
 * call-seq:
 *    add_scheme
 *
 * No documentation available.
 */
static VALUE input_callbacks_add_scheme(VALUE self, VALUE scheme_name,
    VALUE class)
{
  ic_scheme *scheme;

  Check_Type(scheme_name, T_STRING);

  input_callbacks_register_once();

  scheme = ALLOC(ic_scheme);
  scheme->next_scheme = 0;
  scheme->scheme_name = ic_strdup(StringValueCStr(scheme_name));
  scheme->name_len = (int)strlen(scheme->scheme_name);
  scheme->class = class;
  rb_gc_register_address(&scheme->class);

  //fprintf( stderr, "registered: %s, %d, %s\n", scheme->scheme_name, scheme->name_len, scheme->class );

  if (0 == first_scheme)
    first_scheme = scheme;
  else
  {
    ic_scheme *pos;
    pos = first_scheme;
    while (0 != pos->next_scheme)
      pos = pos->next_scheme;
    pos->next_scheme = scheme;
  }

  return (Qtrue);
}

/*
 * call-seq:
 *    remove_scheme
 *
 * No documentation available.
 */
static VALUE input_callbacks_remove_scheme(VALUE self, VALUE scheme_name)
{
  char *name;
  ic_scheme *save_scheme, *scheme;

  Check_Type(scheme_name, T_STRING);
  name = StringValuePtr(scheme_name);

  if (0 == first_scheme)
    return Qfalse;

  if (!strncmp(name, first_scheme->scheme_name, first_scheme->name_len))
  {
    save_scheme = first_scheme->next_scheme;

    ic_scheme_free(first_scheme);
    first_scheme = save_scheme;
    return Qtrue;
  }

  scheme = first_scheme;
  while (0 != scheme->next_scheme)
  {
    if (!strncmp(name, scheme->next_scheme->scheme_name,
        scheme->next_scheme->name_len))
    {
      save_scheme = scheme->next_scheme->next_scheme;

      ic_scheme_free(scheme->next_scheme);
      scheme->next_scheme = save_scheme;
      return Qtrue;
    }
    scheme = scheme->next_scheme;
  }
  return Qfalse;
}

void rxml_init_input_callbacks(void)
{
  VALUE cInputCallbacks;
  cInputCallbacks = rb_define_class_under(mXML, "InputCallbacks", rb_cObject);

  /* Class Methods */
  rb_define_singleton_method(cInputCallbacks, "register", input_callbacks_register_input_callbacks, 0);
  rb_define_singleton_method(cInputCallbacks, "add_scheme", input_callbacks_add_scheme, 2);
  rb_define_singleton_method(cInputCallbacks, "remove_scheme", input_callbacks_remove_scheme, 1);
}
