/***************************************************************************
    copyright            : (C) 2003-2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "xslthandler.h"
#include "../latin1literal.h"
#include "../tellico_debug.h"
#include "../tellico_utils.h"

#include <tqdom.h>
#include <tqtextcodec.h>

#include <kurl.h>

extern "C" {
#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <libxslt/extensions.h>

#include <libexslt/exslt.h>
}

// I don't want any network I/O at all
static const int xml_options = XML_PARSE_NOENT | XML_PARSE_NONET | XML_PARSE_NOCDATA;
static const int xslt_options = xml_options;

/* some functions to pass to the XSLT libs */
static int writeToTQString(void* context, const char* buffer, int len) {
  TQString* t = static_cast<TQString*>(context);
  *t += TQString::fromUtf8(buffer, len);
  return len;
}

static void closeTQString(void* context) {
  TQString* t = static_cast<TQString*>(context);
  *t += TQString::fromLatin1("\n");
}

using Tellico::XSLTHandler;

XSLTHandler::XMLOutputBuffer::XMLOutputBuffer() : m_res(TQString()) {
  m_buf = xmlOutputBufferCreateIO((xmlOutputWriteCallback)writeToTQString,
                                  (xmlOutputCloseCallback)closeTQString,
                                  &m_res, 0);
  if(m_buf) {
    m_buf->written = 0;
  } else {
    myDebug() << "XMLOutputBuffer::XMLOutputBuffer() - error writing output buffer!" << endl;
  }
}

XSLTHandler::XMLOutputBuffer::~XMLOutputBuffer() {
  if(m_buf) {
    xmlOutputBufferClose(m_buf); //also flushes
    m_buf = 0;
  }
}

int XSLTHandler::s_initCount = 0;

XSLTHandler::XSLTHandler(const TQCString& xsltFile_) :
    m_stylesheet(0),
    m_docIn(0),
    m_docOut(0) {
  init();
  TQString file = KURL::encode_string(TQString::fromLocal8Bit(xsltFile_));
  if(!file.isEmpty()) {
    xmlDocPtr xsltDoc = xmlReadFile(file.utf8(), NULL, xslt_options);
    m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
    if(!m_stylesheet) {
      myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltFile_ << endl;
    }
  }
}

XSLTHandler::XSLTHandler(const KURL& xsltURL_) :
    m_stylesheet(0),
    m_docIn(0),
    m_docOut(0) {
  init();
  if(xsltURL_.isValid() && xsltURL_.isLocalFile()) {
    xmlDocPtr xsltDoc = xmlReadFile(xsltURL_.encodedPathAndQuery().utf8(), NULL, xslt_options);
    m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
    if(!m_stylesheet) {
      myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltURL_.path() << endl;
    }
  }
}

XSLTHandler::XSLTHandler(const TQDomDocument& xsltDoc_, const TQCString& xsltFile_, bool translate_) :
    m_stylesheet(0),
    m_docIn(0),
    m_docOut(0) {
  init();
  TQString file = KURL::encode_string(TQString::fromLocal8Bit(xsltFile_));
  if(!xsltDoc_.isNull() && !file.isEmpty()) {
    setXSLTDoc(xsltDoc_, file.utf8(), translate_);
  }
}

XSLTHandler::~XSLTHandler() {
  if(m_stylesheet) {
    xsltFreeStylesheet(m_stylesheet);
  }

  if(m_docIn) {
    xmlFreeDoc(m_docIn);
  }

  if(m_docOut) {
    xmlFreeDoc(m_docOut);
  }

  --s_initCount;
  if(s_initCount == 0) {
    xsltUnregisterExtModule(EXSLT_STRINGS_NAMESPACE);
    xsltUnregisterExtModule(EXSLT_DYNAMIC_NAMESPACE);
    xsltCleanupGlobals();
    xmlCleanupParser();
  }
}

void XSLTHandler::init() {
  if(s_initCount == 0) {
    xmlSubstituteEntitiesDefault(1);
    xmlLoadExtDtdDefaultValue = 0;

    // register all exslt extensions
    exsltRegisterAll();
  }
  ++s_initCount;

  m_params.clear();
}

void XSLTHandler::setXSLTDoc(const TQDomDocument& dom_, const TQCString& xsltFile_, bool translate_) {
  bool utf8 = true; // XML defaults to utf-8

  // need to find out if utf-8 or not
  const TQDomNodeList childs = dom_.childNodes();
  for(uint j = 0; j < childs.count(); ++j) {
    if(childs.item(j).isProcessingInstruction()) {
      TQDomProcessingInstruction pi = childs.item(j).toProcessingInstruction();
      if(pi.data().lower().contains(TQString::fromLatin1("encoding"))) {
        if(!pi.data().lower().contains(TQString::fromLatin1("utf-8"))) {
          utf8 = false;
//        } else {
//          myDebug() << "XSLTHandler::setXSLTDoc() - PI = " << pi.data() << endl;
        }
        break;
      }
    }
  }

  TQString s;
  if(translate_) {
    s = Tellico::i18nReplace(dom_.toString(0 /* indent */));
  } else {
    s = dom_.toString();
  }

  xmlDocPtr xsltDoc;
  if(utf8) {
    xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.utf8().data()), xsltFile_.data(), NULL, xslt_options);
  } else {
    xsltDoc = xmlReadDoc(reinterpret_cast<xmlChar*>(s.local8Bit().data()), xsltFile_.data(), NULL, xslt_options);
  }

  if(m_stylesheet) {
    xsltFreeStylesheet(m_stylesheet);
  }
  m_stylesheet = xsltParseStylesheetDoc(xsltDoc);
  if(!m_stylesheet) {
    myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer for " << xsltFile_ << endl;
  }
//  xmlFreeDoc(xsltDoc); // this causes a crash for some reason
}

void XSLTHandler::addStringParam(const TQCString& name_, const TQCString& value_) {
  TQCString value = value_;
  value.replace('\'', "&apos;");
  addParam(name_, TQCString("'") + value + TQCString("'"));
}

void XSLTHandler::addParam(const TQCString& name_, const TQCString& value_) {
  m_params.insert(name_, value_);
//  myDebug() << "XSLTHandler::addParam() - " << name_ << ":" << value_ << endl;
}

void XSLTHandler::removeParam(const TQCString& name_) {
  m_params.remove(name_);
}

const TQCString& XSLTHandler::param(const TQCString& name_) {
  return m_params[name_];
}

TQString XSLTHandler::applyStylesheet(const TQString& text_) {
  if(!m_stylesheet) {
    myDebug() << "XSLTHandler::applyStylesheet() - null stylesheet pointer!" << endl;
    return TQString();
  }

  m_docIn = xmlReadDoc(reinterpret_cast<xmlChar*>(text_.utf8().data()), NULL, NULL, xml_options);

  return process();
}

TQString XSLTHandler::process() {
  if(!m_docIn) {
    myDebug() << "XSLTHandler::process() - error parsing input string!" << endl;
    return TQString();
  }

  TQMemArray<const char*> params(2*m_params.count() + 1);
  params[0] = NULL;
  TQMap<TQCString, TQCString>::ConstIterator it = m_params.constBegin();
  TQMap<TQCString, TQCString>::ConstIterator end = m_params.constEnd();
  for(uint i = 0; it != end; ++it) {
    params[i  ] = tqstrdup(it.key());
    params[i+1] = tqstrdup(it.data());
    params[i+2] = NULL;
    i += 2;
  }
  // returns NULL on error
  m_docOut = xsltApplyStylesheet(m_stylesheet, m_docIn, params.data());
  for(uint i = 0; i < 2*m_params.count(); ++i) {
    delete[] params[i];
  }
  if(!m_docOut) {
    myDebug() << "XSLTHandler::applyStylesheet() - error applying stylesheet!" << endl;
    return TQString();
  }

  XMLOutputBuffer output;
  if(output.isValid()) {
    int num_bytes = xsltSaveResultTo(output.buffer(), m_docOut, m_stylesheet);
    if(num_bytes == -1) {
      myDebug() << "XSLTHandler::applyStylesheet() - error saving output buffer!" << endl;
    }
  }
  return output.result();
}

//static
TQDomDocument& XSLTHandler::setLocaleEncoding(TQDomDocument& dom_) {
  const TQDomNodeList childs = dom_.documentElement().childNodes();
  for(unsigned j = 0; j < childs.count(); ++j) {
    if(childs.item(j).isElement() && childs.item(j).nodeName() == Latin1Literal("xsl:output")) {
      TQDomElement e = childs.item(j).toElement();
      const TQString encoding = TQString::fromLatin1(TQTextCodec::codecForLocale()->name());
      e.setAttribute(TQString::fromLatin1("encoding"), encoding);
      break;
    }
  }
  return dom_;
}
