/* $Id: ruby_xslt_stylesheet.c 42 2007-12-07 06:09:35Z transami $ */

/* See the LICENSE file for copyright and distribution information. */

#include "libxslt.h"
#include "ruby_xslt_stylesheet.h"

VALUE cXSLTStylesheet;

/* call-seq: 
 *   sheet.apply => (true|false)
 * 
 * Apply this stylesheet transformation to the source
 * document.
 */
VALUE
ruby_xslt_stylesheet_apply(int argc, VALUE *argv, VALUE self) {
  ruby_xslt_stylesheet *xss;
  ruby_xml_document_t *rxd;
  const char **params;
  VALUE parameter, tmp;
  int i, len;

  Data_Get_Struct(self, ruby_xslt_stylesheet, xss);

  if (NIL_P(xss->xml_doc_obj))
    rb_raise(rb_eArgError, "Need a document object");

  Data_Get_Struct(xss->xml_doc_obj, ruby_xml_document_t, rxd);

  params = NULL;

  switch(argc) {
  case 0:
    break;
  case 1:
    parameter = argv[0];
#if RUBY_VERSION_CODE >= 180
    if (TYPE(parameter) == T_HASH) {
      /* Convert parameter to an array */
      parameter = rb_hash_to_a(parameter);
    }
#endif

    if (TYPE(parameter) == T_ARRAY) {
      /* A hash is better than an array, but we can live with an array of arrays */
      len = RARRAY(parameter)->len;
      params = (void *)ALLOC_N(char *, (len * 2) + 2);
      for (i=0; i < RARRAY(parameter)->len; i++) {
	tmp = RARRAY(parameter)->ptr[i];

	Check_Type(tmp, T_ARRAY);
	Check_Type(RARRAY(tmp)->ptr[0], T_STRING);
	Check_Type(RARRAY(tmp)->ptr[1], T_STRING);

	params[2*i] = RSTRING(RARRAY(tmp)->ptr[0])->ptr;
	params[2*i+1] = RSTRING(RARRAY(tmp)->ptr[1])->ptr;
      }
      params[2*i] = params[2*i+1] = 0;
    } else {
      /* I should test to see if the object responds to to_a and to_h before calling this, but oh well */
      rb_raise(rb_eTypeError, "xslt_stylesheet_appy: expecting a hash or an array of arrays as a parameter");
    }

    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  xss->parsed = ruby_xml_document_wrap(xsltApplyStylesheet(xss->xsp,
							   rxd->doc, params));

  if (params) {
    ruby_xfree(params);
  }

  if (xss->parsed == Qnil)
    return(Qfalse);
  else
    return(Qtrue);
}


/* call-seq: 
 *   sheet.debug(to = $stdout) => (true|false)
 * 
 * Output a debug dump of this stylesheet to the specified output
 * stream (an instance of IO, defaults to $stdout). Requires
 * libxml/libxslt be compiled with debugging enabled. If this
 * is not the case, a warning is triggered and the method returns
 * false.
 */
VALUE
ruby_xslt_stylesheet_debug(int argc, VALUE *argv, VALUE self) {
#ifdef LIBXML_DEBUG_ENABLED
  OpenFile *fptr;
  VALUE io;
  FILE *out;
  ruby_xml_document_t *parsed;
  ruby_xslt_stylesheet *xss;

  Data_Get_Struct(self, ruby_xslt_stylesheet, xss);
  if (NIL_P(xss->parsed))
    rb_raise(eXMLXSLTStylesheetRequireParsedDoc, "must have a parsed XML result");

  switch (argc) {
  case 0:
    io = rb_stdout;
    break;
  case 1:
    io = argv[0];
    if (rb_obj_is_kind_of(io, rb_cIO) == Qfalse)
      rb_raise(rb_eTypeError, "need an IO object");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  Data_Get_Struct(xss->parsed, ruby_xml_document_t, parsed);
  if (parsed->doc == NULL)
    return(Qnil);

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  xmlDebugDumpDocument(out, parsed->doc);
  return(Qtrue);
#else
  rb_warn("libxml/libxslt was compiled without debugging support.  Please recompile libxml/libxslt and their Ruby modules");
  return(Qfalse);
#endif
}


void
ruby_xslt_stylesheet_free(ruby_xslt_stylesheet *xss) {
  if (xss->xsp != NULL) {
    xsltFreeStylesheet(xss->xsp);
    xss->xsp = NULL;
  }

  ruby_xfree(xss);
}


void
ruby_xslt_stylesheet_mark(ruby_xslt_stylesheet *xss) {
  if (!NIL_P(xss->parsed))      rb_gc_mark(xss->parsed);
  if (!NIL_P(xss->xml_doc_obj)) rb_gc_mark(xss->xml_doc_obj);

  switch (xss->data_type) {
  case RUBY_LIBXSLT_SRC_TYPE_FILE:
    if (xss->data != NULL)
      rb_gc_mark((VALUE)xss->data);
    break;
  }
}


VALUE
ruby_xslt_stylesheet_new(VALUE class, xsltStylesheetPtr xsp) {
  ruby_xslt_stylesheet *xss;
  VALUE rval;

  rval=Data_Make_Struct(cXSLTStylesheet,ruby_xslt_stylesheet,ruby_xslt_stylesheet_mark,
			ruby_xslt_stylesheet_free,xss);
  xss->xsp = xsp;
  xss->xml_doc_obj = Qnil;
  xss->parsed = Qnil;
  xss->data_type = RUBY_LIBXSLT_SRC_TYPE_NULL;
  xss->data = NULL;

  return rval;
}

// TODO should this automatically apply the sheet if not already,
//      given that we're unlikely to do much else with it?

/* call-seq: 
 *   sheet.print(to = $stdout) => number_of_bytes
 * 
 * Output the result of the transform to the specified output
 * stream (an IO instance, defaults to $stdout). You *must* call
 * +apply+ before this method or an exception will be raised.
 */
VALUE
ruby_xslt_stylesheet_print(int argc, VALUE *argv, VALUE self) {
  OpenFile *fptr;
  VALUE io;
  FILE *out;
  ruby_xml_document_t *parsed;
  ruby_xslt_stylesheet *xss;
  int bytes;

  Data_Get_Struct(self, ruby_xslt_stylesheet, xss);
  if (NIL_P(xss->parsed))
    rb_raise(eXMLXSLTStylesheetRequireParsedDoc, "must have a parsed XML result");

  switch (argc) {
  case 0:
    io = rb_stdout;
    break;
  case 1:
    io = argv[0];
    if (rb_obj_is_kind_of(io, rb_cIO) == Qfalse)
      rb_raise(rb_eTypeError, "need an IO object");
    break;
  default:
    rb_raise(rb_eArgError, "wrong number of arguments (0 or 1)");
  }

  Data_Get_Struct(xss->parsed, ruby_xml_document_t, parsed);
  if (parsed->doc == NULL)
    return(Qnil);

  GetOpenFile(io, fptr);
  rb_io_check_writable(fptr);
  out = GetWriteFile(fptr);
  bytes = xsltSaveResultToFile(out, parsed->doc, xss->xsp);

  return(INT2NUM(bytes));
}

// TODO this, too. Either way, to_s probably should have prereqs
//      like this, for one thing it makes IRB use tricky...

/* call-seq: 
 *   sheet.to_s => "result"
 * 
 * Obtain the result of the transform as a string. You *must* call
 * +apply+ before this method or an exception will be raised.
 */
VALUE
ruby_xslt_stylesheet_to_s(VALUE self) {
  ruby_xml_document_t *parsed;
  ruby_xslt_stylesheet *xss;
  xmlChar *str;
  int len;

  Data_Get_Struct(self, ruby_xslt_stylesheet, xss);
  if (NIL_P(xss->parsed))
    rb_raise(eXMLXSLTStylesheetRequireParsedDoc, "must have a parsed XML result");
  Data_Get_Struct(xss->parsed, ruby_xml_document_t, parsed);
  if (parsed->doc == NULL)
    return(Qnil);

  xsltSaveResultToString(&str, &len, parsed->doc, xss->xsp);
  if (str == NULL)
    return(Qnil);
  else
    return(rb_str_new((const char*)str,len));
}



/* call-seq: 
 *   sheet.save(io) => true
 * 
 * Save the result of the transform to the supplied open
 * file (an IO instance). You *must* call +apply+ before 
 * this method or an exception will be raised.
 */
VALUE
ruby_xslt_stylesheet_save(VALUE self, VALUE io) {
  ruby_xml_document_t *parsed;
  ruby_xslt_stylesheet *xss;
  OpenFile *fptr;

  if (rb_obj_is_kind_of(io, rb_cIO) == Qfalse)
    rb_raise(rb_eArgError, "Only accept IO objects for saving");

  GetOpenFile(io, fptr);

  Data_Get_Struct(self, ruby_xslt_stylesheet, xss);
  Data_Get_Struct(xss->parsed, ruby_xml_document_t, parsed);

  xsltSaveResultToFile(fptr->f, parsed->doc, xss->xsp);

  return(Qtrue);
}

#ifdef RDOC_NEVER_DEFINED
  mXML = rb_define_module("XML");
  cXSLT = rb_define_class_under(mXML, "XSLT", rb_cObject);
#endif

void
ruby_init_xslt_stylesheet(void) {
  cXSLTStylesheet = rb_define_class_under(cXSLT, "Stylesheet", rb_cObject);
  eXMLXSLTStylesheetRequireParsedDoc =
    rb_define_class_under(cXSLTStylesheet, "RequireParsedDoc", rb_eException);

  rb_define_method(cXSLTStylesheet, "apply", ruby_xslt_stylesheet_apply, -1);
  rb_define_method(cXSLTStylesheet, "debug", ruby_xslt_stylesheet_debug, -1);
  rb_define_method(cXSLTStylesheet, "print", ruby_xslt_stylesheet_print, -1);
  rb_define_method(cXSLTStylesheet, "to_s", ruby_xslt_stylesheet_to_s, 0);
  rb_define_method(cXSLTStylesheet, "save", ruby_xslt_stylesheet_save, 1);
}