#include "xslt.h"

extern int xmlLoadExtDtdDefaultValue;

char* parse( char* xml, int iXmlType, char *xsl, int iXslType ) {
  xsltStylesheetPtr cur = NULL;
  xmlDocPtr docxml = NULL, docxsl = NULL , res = NULL ;
  int out;

  xmlChar *doc_txt_ptr;
  int doc_txt_len;

  /** Act: Disable debug mode */
  xsltDebugSetDefaultTrace( XSLT_TRACE_NONE );
  xmlSubstituteEntitiesDefault( 1 );
  xmlLoadExtDtdDefaultValue = 1;
  
  /** Act: Parse XSL */
  if( iXslType == RUBY_XSLT_XSLSRC_TYPE_STR ) {
    docxsl = xmlParseMemory( xsl, strlen( xsl ) );
    // docxsl = xmlParseDoc( xmlCharStrdup( xsl ) );
    if( docxsl == NULL ) {
      xsltTransformError( NULL, NULL, NULL, "XML::XSLT: XSL parsing error\n" );
      return( NULL );
    }

    cur = xsltParseStylesheetDoc( docxsl );
    if( cur == NULL ) {
      xsltTransformError( NULL, NULL, NULL, "XML::XSLT: XSL Stylesheet parsing error\n" );
      return( NULL );
    }
  } else if( iXslType == RUBY_XSLT_XSLSRC_TYPE_FILE ) {
    cur = xsltParseStylesheetFile( xsl );
    if( cur == NULL ) {
      xsltTransformError( NULL, NULL, NULL, "XML::XSLT: XSL Stylesheet parsing error\n" );
      return( NULL );
    }
  }
  
  /** Act: Parse XML */
  if( iXmlType == RUBY_XSLT_XMLSRC_TYPE_STR ) {
    docxml = xmlParseMemory( xml, strlen( xml ) );
    if( docxml == NULL ) {
      xsltTransformError( NULL, NULL, NULL, "XML::XSLT: XML parsing error\n" );
      return( NULL );
    }
  } else if( iXmlType == RUBY_XSLT_XMLSRC_TYPE_FILE ) {
	docxml = xmlParseFile( xml );
    if( docxml == NULL ) {
      xsltTransformError( NULL, NULL, NULL, "XML::XSLT: XML parsing error\n" );
      return( NULL );
    }
  }
  
  res = xsltApplyStylesheet( cur, docxml, NULL );
  if( res == NULL ) {
    xsltTransformError( NULL, NULL, NULL, "XML::XSLT: Stylesheet apply error\n" );
    return( NULL );
  }
  
  out = xsltSaveResultToString( &doc_txt_ptr, &doc_txt_len, res, cur );

  xsltFreeStylesheet(cur);
  xmlFreeDoc(res);
  xmlFreeDoc(docxml);
//  if( iXslType == RUBY_XSLT_XSLSRC_TYPE_STR ) { xmlFreeDoc(docxsl); }

  xsltCleanupGlobals();
  xmlCleanupParser();
  return((char*)doc_txt_ptr);
}

void ruby_xslt_free( RbTxslt *pRbTxslt ) {
  if (pRbTxslt != NULL)
    free(pRbTxslt);
}


void ruby_xslt_mark( RbTxslt *pRbTxslt ) {
  if( pRbTxslt == NULL ) return;
  if( !NIL_P(pRbTxslt->xXmlData) ) rb_gc_mark( pRbTxslt->xXmlData );
  if( !NIL_P(pRbTxslt->xXslData) ) rb_gc_mark( pRbTxslt->xXslData );
}

/** o = XML::XSLT.new() */
VALUE ruby_xslt_new( VALUE class ) {
  RbTxslt *pRbTxslt;

  pRbTxslt = (RbTxslt *)malloc(sizeof(RbTxslt));
  if( pRbTxslt == NULL )
    rb_raise(rb_eNoMemError, "No memory left for XSLT struct");

  pRbTxslt->iXmlType = RUBY_XSLT_XMLSRC_TYPE_NULL;
  pRbTxslt->xXmlData = Qnil;
  pRbTxslt->iXslType = RUBY_XSLT_XSLSRC_TYPE_NULL;
  pRbTxslt->xXslData = Qnil;

  return( Data_Wrap_Struct( class, ruby_xslt_mark, ruby_xslt_free, pRbTxslt ) );
}

/** xmlData = o.xml */
VALUE ruby_xslt_xml_str_get( VALUE self ) {
  RbTxslt *pRbTxslt;
  Data_Get_Struct( self, RbTxslt, pRbTxslt );
  
  if( pRbTxslt->iXmlType == RUBY_XSLT_XMLSRC_TYPE_NULL )
    return( Qnil );
  else
    return( (VALUE)pRbTxslt->xXmlData );
}

/** o.xml = "<...>" */
VALUE ruby_xslt_xml_str_set( VALUE self, VALUE xml_doc_str ) {
  RbTxslt *pRbTxslt;
  Check_Type( xml_doc_str, T_STRING );

  Data_Get_Struct( self, RbTxslt, pRbTxslt );
/*
  if( pRbTxslt->iXmlType != RUBY_XSLT_XMLSRC_TYPE_NULL )
    return( Qnil );
*/
  
  pRbTxslt->iXmlType = RUBY_XSLT_XMLSRC_TYPE_STR;
  pRbTxslt->xXmlData = xml_doc_str;
  return( xml_doc_str );
}

/** o.xmlfile="file.xml" */
VALUE ruby_xslt_xml_file_set( VALUE self, VALUE xml_doc_file ) {
  RbTxslt *pRbTxslt;
  Check_Type( xml_doc_file, T_STRING );

  Data_Get_Struct( self, RbTxslt, pRbTxslt );
/*
  if( pRbTxslt->iXmlType != RUBY_XSLT_XMLSRC_TYPE_NULL )
    return( Qnil );
*/
  
  pRbTxslt->iXmlType = RUBY_XSLT_XMLSRC_TYPE_FILE;
  pRbTxslt->xXmlData = xml_doc_file;
  return( xml_doc_file );
}

/** xslData = o.xsl */
VALUE ruby_xslt_xsl_str_get( VALUE self ) {
  RbTxslt *pRbTxslt;
  Data_Get_Struct( self, RbTxslt, pRbTxslt );
  
  if( pRbTxslt->iXslType == RUBY_XSLT_XSLSRC_TYPE_NULL )
    return( Qnil );
  else
    return( (VALUE)pRbTxslt->xXslData );
}

/** o.xsl = "<...>" */
VALUE ruby_xslt_xsl_str_set( VALUE self, VALUE xsl_doc_str ) {
  RbTxslt *pRbTxslt;
  Check_Type( xsl_doc_str, T_STRING );

  Data_Get_Struct( self, RbTxslt, pRbTxslt );
/*
  if( pRbTxslt->iXslType != RUBY_XSLT_XSLSRC_TYPE_NULL )
    return( Qnil );
*/

  pRbTxslt->iXslType = RUBY_XSLT_XSLSRC_TYPE_STR;
  pRbTxslt->xXslData = xsl_doc_str;
  return( xsl_doc_str );
}

/** o.xslfile = "file.xsl" */
VALUE ruby_xslt_xsl_file_set( VALUE self, VALUE xsl_doc_file ) {
  RbTxslt *pRbTxslt;
  Check_Type( xsl_doc_file, T_STRING );

  Data_Get_Struct( self, RbTxslt, pRbTxslt );
/*
  if( pRbTxslt->iXslType != RUBY_XSLT_XSLSRC_TYPE_NULL )
    return( Qnil );
*/

  pRbTxslt->iXslType = RUBY_XSLT_XSLSRC_TYPE_FILE;
  pRbTxslt->xXslData = xsl_doc_file;
  return( xsl_doc_file );
}

/** out = o.serve */
VALUE ruby_xslt_serve( VALUE self ) {
  RbTxslt *pRbTxslt;
  char *xOut;
  VALUE rOut;
  
  Data_Get_Struct( self, RbTxslt, pRbTxslt );

  if( pRbTxslt->iXslType != RUBY_XSLT_XSLSRC_TYPE_NULL &&
      pRbTxslt->iXmlType != RUBY_XSLT_XMLSRC_TYPE_NULL ) {
    xOut = parse( STR2CSTR( pRbTxslt->xXmlData ), pRbTxslt->iXmlType, 
                  STR2CSTR( pRbTxslt->xXslData ), pRbTxslt->iXslType );
    if( xOut == NULL ) {
      rOut = Qnil;
    } else {
      rOut = rb_str_new2( xOut );
    }
  } else {
    rOut = Qnil;
  }
  
  return( rOut );
}

/** o.save( "result.xml" ) */
VALUE ruby_xslt_save( VALUE self, VALUE xOutFilename ) {
  RbTxslt *pRbTxslt;
  char *xOut;
  VALUE rOut;
  FILE *fOutFile;
  
  Data_Get_Struct( self, RbTxslt, pRbTxslt );

  if( pRbTxslt->iXslType != RUBY_XSLT_XSLSRC_TYPE_NULL &&
      pRbTxslt->iXmlType != RUBY_XSLT_XMLSRC_TYPE_NULL ) {
    xOut = parse( STR2CSTR( pRbTxslt->xXmlData ), pRbTxslt->iXmlType, 
                  STR2CSTR( pRbTxslt->xXslData ), pRbTxslt->iXslType );
    if( xOut == NULL ) {
      rOut = Qnil;
    } else {
	  fOutFile = fopen( STR2CSTR( xOutFilename ), "w" );
	  if( fOutFile == NULL ) {
		xsltTransformError( NULL, NULL, NULL, "XML::XSLT.save : ERROR, can't create file %s\n", STR2CSTR( xOutFilename ) );
		free( xOut );
		rOut = Qnil;
	  } else {
	    fwrite( xOut, 1, strlen( xOut ), fOutFile );
	    fclose( fOutFile );

        rOut = rb_str_new2( xOut );
	  }
    }
  } else {
    rOut = Qnil;
  }
  return( rOut );
}

VALUE mXML;
VALUE cXSLT;

void Init_xslt( void ) {
  mXML  = rb_define_module( "XML" );
  cXSLT = rb_define_class_under( mXML, "XSLT", rb_cObject );

  rb_define_const( cXSLT, "MAX_DEPTH",            INT2NUM(xsltMaxDepth) );
  rb_define_const( cXSLT, "MAX_SORT",             INT2NUM(XSLT_MAX_SORT) );
  rb_define_const( cXSLT, "ENGINE_VERSION",       rb_str_new2(xsltEngineVersion) );
  rb_define_const( cXSLT, "LIBXSLT_VERSION",      INT2NUM(xsltLibxsltVersion) );
  rb_define_const( cXSLT, "LIBXML_VERSION",       INT2NUM(xsltLibxmlVersion) );
  rb_define_const( cXSLT, "XSLT_NAMESPACE",       rb_str_new2(XSLT_NAMESPACE) );
  rb_define_const( cXSLT, "DEFAULT_VENDOR",       rb_str_new2(XSLT_DEFAULT_VENDOR) );
  rb_define_const( cXSLT, "DEFAULT_VERSION",      rb_str_new2(XSLT_DEFAULT_VERSION) );
  rb_define_const( cXSLT, "DEFAULT_URL",          rb_str_new2(XSLT_DEFAULT_URL) );
  rb_define_const( cXSLT, "NAMESPACE_LIBXSLT",    rb_str_new2(XSLT_LIBXSLT_NAMESPACE) );
  rb_define_const( cXSLT, "NAMESPACE_NORM_SAXON", rb_str_new2(XSLT_NORM_SAXON_NAMESPACE) );
  rb_define_const( cXSLT, "NAMESPACE_SAXON",      rb_str_new2(XSLT_SAXON_NAMESPACE) );
  rb_define_const( cXSLT, "NAMESPACE_XT",         rb_str_new2(XSLT_XT_NAMESPACE) );
  rb_define_const( cXSLT, "NAMESPACE_XALAN",      rb_str_new2(XSLT_XALAN_NAMESPACE) );

  rb_define_singleton_method( cXSLT, "new", ruby_xslt_new, 0 );

  rb_define_method( cXSLT, "xml",       ruby_xslt_xml_str_get,  0 );
  rb_define_method( cXSLT, "xml=",      ruby_xslt_xml_str_set,  1 );
  rb_define_method( cXSLT, "xmlfile=",  ruby_xslt_xml_file_set, 1 );
  rb_define_method( cXSLT, "xsl",       ruby_xslt_xsl_str_get,  0 );
  rb_define_method( cXSLT, "xsl=",      ruby_xslt_xsl_str_set,  1 );
  rb_define_method( cXSLT, "xslfile=",  ruby_xslt_xsl_file_set, 1 );
  rb_define_method( cXSLT, "serve",     ruby_xslt_serve,        0 );
  rb_define_method( cXSLT, "save",      ruby_xslt_save,         1 );
}
