/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#include <documentbuilder.hxx>

#include <string.h>
#include <stdio.h>
#include <stdarg.h>

#include <libxml/xmlerror.h>
#include <libxml/tree.h>

#include <boost/shared_ptr.hpp>

#include <rtl/alloc.h>
#include <rtl/memory.h>
#include <rtl/ustrbuf.hxx>

#include <cppuhelper/implbase1.hxx>

#include <com/sun/star/xml/sax/SAXParseException.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/task/XInteractionHandler.hpp>

#include <ucbhelper/content.hxx>
#include <ucbhelper/commandenvironment.hxx>

#include <node.hxx>
#include <document.hxx>


using ::rtl::OUStringBuffer;
using ::rtl::OString;
using ::com::sun::star::xml::sax::InputSource;
using namespace ucbhelper;
using namespace ::com::sun::star::ucb;
using ::com::sun::star::task::XInteractionHandler;


namespace DOM
{

	class CDefaultEntityResolver : public cppu::WeakImplHelper1< XEntityResolver >
	{
	public:
	    virtual InputSource SAL_CALL resolveEntity( const OUString& sPublicId, const OUString& sSystemId )
			throw (::com::sun::star::uno::RuntimeException)
		{
			InputSource is;
			is.sPublicId = sPublicId;
			is.sSystemId = sSystemId;
			is.sEncoding = OUString();

			try {
				Reference< XCommandEnvironment > aEnvironment(
					new CommandEnvironment(Reference< XInteractionHandler >(),
										   Reference< XProgressHandler >() ));
				Content aContent(sSystemId, aEnvironment);

				is.aInputStream = aContent.openStream();
			} catch (com::sun::star::uno::Exception) {
				OSL_ENSURE(sal_False, "exception in default entity resolver");
				is.aInputStream = Reference< XInputStream >();
			}
			return is;
		}

	};

    CDocumentBuilder::CDocumentBuilder(
            Reference< XMultiServiceFactory > const& xFactory)
        : m_xFactory(xFactory)
        , m_xEntityResolver(new CDefaultEntityResolver())
    {
        // init libxml. libxml will protect itself against multiple
        // initializations so there is no problem here if this gets
        // called multiple times.
        xmlInitParser();
    }

    Reference< XInterface > CDocumentBuilder::_getInstance(const Reference< XMultiServiceFactory >& rSMgr)
    {
        return static_cast< XDocumentBuilder* >(new CDocumentBuilder(rSMgr));
    }

    const char* CDocumentBuilder::aImplementationName = "com.sun.star.comp.xml.dom.DocumentBuilder";
    const char* CDocumentBuilder::aSupportedServiceNames[] = {
        "com.sun.star.xml.dom.DocumentBuilder",
        NULL
    };

    OUString CDocumentBuilder::_getImplementationName()
    {
	    return OUString::createFromAscii(aImplementationName);
    }
    Sequence<OUString> CDocumentBuilder::_getSupportedServiceNames()
    {
	    Sequence<OUString> aSequence;
	    for (int i=0; aSupportedServiceNames[i]!=NULL; i++) {
		    aSequence.realloc(i+1);
		    aSequence[i]=(OUString::createFromAscii(aSupportedServiceNames[i]));
	    }
	    return aSequence;
    }

    Sequence< OUString > SAL_CALL CDocumentBuilder::getSupportedServiceNames()
        throw (RuntimeException)
    {
        return CDocumentBuilder::_getSupportedServiceNames();
    }

    OUString SAL_CALL CDocumentBuilder::getImplementationName()
        throw (RuntimeException)
    {
        return CDocumentBuilder::_getImplementationName();
    }

    sal_Bool SAL_CALL CDocumentBuilder::supportsService(const OUString& aServiceName)
        throw (RuntimeException)
    {
        Sequence< OUString > supported = CDocumentBuilder::_getSupportedServiceNames();
        for (sal_Int32 i=0; i<supported.getLength(); i++)
        {
            if (supported[i] == aServiceName) return sal_True;
        }
        return sal_False;
    }

    Reference< XDOMImplementation > SAL_CALL CDocumentBuilder::getDOMImplementation()
        throw (RuntimeException)
    {

        return Reference< XDOMImplementation >();
    }

    sal_Bool SAL_CALL CDocumentBuilder::isNamespaceAware()
        throw (RuntimeException)
    {
        return sal_True;
    }

    sal_Bool SAL_CALL CDocumentBuilder::isValidating()
        throw (RuntimeException)
    {
        return sal_False;
    }

    Reference< XDocument > SAL_CALL CDocumentBuilder::newDocument()
        throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        // create a new document
        xmlDocPtr pDocument = xmlNewDoc((const xmlChar*)"1.0");
        Reference< XDocument > const xRet(
                CDocument::CreateCDocument(pDocument).get());
        return xRet;
    }

	static OUString make_error_message(xmlParserCtxtPtr ctxt)
	{
		OUStringBuffer buf;
		buf.appendAscii(ctxt->lastError.message);
		buf.appendAscii("Line: ");
		buf.append(static_cast<sal_Int32>(ctxt->lastError.line));
		buf.appendAscii("\nColumn: ");
		buf.append(static_cast<sal_Int32>(ctxt->lastError.int2));
		OUString msg = buf.makeStringAndClear();
		return msg;
	}

	// -- callbacks and context struct for parsing from stream
	// -- c-linkage, so the callbacks can be used by libxml
	extern "C" {

	// context struct passed to IO functions
	typedef struct context {
		CDocumentBuilder *pBuilder;
		Reference< XInputStream > rInputStream;
		bool close;
		bool freeOnClose;
	} context_t;

    static int xmlIO_read_func( void *context, char *buffer, int len)
    {
		// get the context...
        context_t *pctx = static_cast<context_t*>(context);
		if (!pctx->rInputStream.is())
			return -1;
		try {
			// try to read the requested number of bytes
            Sequence< sal_Int8 > chunk(len);
            int nread = pctx->rInputStream->readBytes(chunk, len);

            // copy bytes to the provided buffer
            rtl_copyMemory(buffer, chunk.getConstArray(), nread);
            return nread;
		} catch (com::sun::star::uno::Exception& ex) {
            (void) ex;
            OSL_ENSURE(sal_False, OUStringToOString(ex.Message, RTL_TEXTENCODING_UTF8).getStr());
            return -1;
        }
    }

    static int xmlIO_close_func(void* context)
	{
		// get the context...
		context_t *pctx = static_cast<context_t*>(context);
		if (!pctx->rInputStream.is())
			return 0;
        try
        {
			if (pctx->close)
				pctx->rInputStream->closeInput();
			if (pctx->freeOnClose)
				delete pctx;
            return 0;
		} catch (com::sun::star::uno::Exception& ex) {
            (void) ex;
            OSL_ENSURE(sal_False, OUStringToOString(ex.Message, RTL_TEXTENCODING_UTF8).getStr());
            return -1;
        }
    }

	static xmlParserInputPtr resolve_func(void *ctx,
                                const xmlChar *publicId,
                                const xmlChar *systemId)
	{
		// get the CDocumentBuilder object
		xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr)ctx;
		CDocumentBuilder *builder = static_cast< CDocumentBuilder* >(ctxt->_private);
		Reference< XEntityResolver > resolver = builder->getEntityResolver();
		OUString sysid;
		if (systemId != 0)
			sysid = OUString((sal_Char*)systemId, strlen((char*)systemId), RTL_TEXTENCODING_UTF8);
		OUString pubid;
		if (publicId != 0)
			pubid = OUString((sal_Char*)publicId, strlen((char*)publicId), RTL_TEXTENCODING_UTF8);

		// resolve the entity
		InputSource src = resolver->resolveEntity(pubid, sysid);

		// create IO context on heap because this call will no longer be on the stack
		// when IO is actually performed through the callbacks. The close function must
		// free the memory which is indicated by the freeOnClose field in the context struct
		context_t *c = new context_t;
		c->pBuilder = builder;
		c->rInputStream = src.aInputStream;
		c->close = true;
		c->freeOnClose = true;

		// set up the inputBuffer and inputPtr for libxml
		xmlParserInputBufferPtr pBuffer =
			xmlParserInputBufferCreateIO(xmlIO_read_func, xmlIO_close_func, c, XML_CHAR_ENCODING_NONE);
		xmlParserInputPtr pInput =
					xmlNewIOInputStream(ctxt, pBuffer, XML_CHAR_ENCODING_NONE);
		return pInput;
	}

#if 0
	static xmlParserInputPtr external_entity_loader(const char *URL, const char * /*ID*/, xmlParserCtxtPtr ctxt)
	{
		// just call our resolver function using the URL as systemId
		return resolve_func(ctxt, 0, (const xmlChar*)URL);
	}
#endif

	// default warning handler triggers assertion
	static void warning_func(void * ctx, const char * /*msg*/, ...)
	{
		OUStringBuffer buf(OUString::createFromAscii("libxml2 warning\n"));
		buf.append(make_error_message(static_cast< xmlParserCtxtPtr >(ctx)));
		OString msg = OUStringToOString(buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US);
		OSL_ENSURE(sal_False, msg.getStr());
	}

	// default error handler triggers assertion
	static void error_func(void * ctx, const char * /*msg*/, ...)
	{
		OUStringBuffer buf(OUString::createFromAscii("libxml2 error\n"));
		buf.append(make_error_message(static_cast< xmlParserCtxtPtr >(ctx)));
		OString msg = OUStringToOString(buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US);
		OSL_ENSURE(sal_False, msg.getStr());
	}

	} // extern "C"

    void throwEx(xmlParserCtxtPtr ctxt) {
        OUString msg = make_error_message(ctxt);
        com::sun::star::xml::sax::SAXParseException saxex;
        saxex.Message = msg;
        saxex.LineNumber = static_cast<sal_Int32>(ctxt->lastError.line);
        saxex.ColumnNumber = static_cast<sal_Int32>(ctxt->lastError.int2);
        throw saxex;
    }

    Reference< XDocument > SAL_CALL CDocumentBuilder::parse(const Reference< XInputStream >& is)
        throw (RuntimeException, SAXParseException, IOException)
    {
        if (!is.is()) {
            throw RuntimeException();
        }

        ::osl::MutexGuard const g(m_Mutex);

		// encoding...
		/*
		xmlChar *encstr = (xmlChar*) OUStringToOString(src.sEncoding, RTL_TEXTENCODING_UTF8).getStr();
		xmlCharEncoding enc = xmlParseCharEncoding(encstr);
		*/

        ::boost::shared_ptr<xmlParserCtxt> const pContext(
                xmlNewParserCtxt(), xmlFreeParserCtxt);

		// register error functions to prevent errors being printed
		// on the console
        pContext->_private = this;
        pContext->sax->error = error_func;
        pContext->sax->warning = warning_func;
        pContext->sax->resolveEntity = resolve_func;

		// IO context struct
		context_t c;
		c.pBuilder = this;
		c.rInputStream = is;
		// we did not open the stream, thus we do not close it.
		c.close = false;
		c.freeOnClose = false;
        xmlDocPtr const pDoc = xmlCtxtReadIO(pContext.get(),
                xmlIO_read_func, xmlIO_close_func, &c, 0, 0, 0);

		if (pDoc == 0) {
            throwEx(pContext.get());
        }
        Reference< XDocument > const xRet(
                CDocument::CreateCDocument(pDoc).get());
        return xRet;
    }

	Reference< XDocument > SAL_CALL CDocumentBuilder::parseURI(const OUString& sUri)
		throw (RuntimeException, SAXParseException, IOException)
	{
        ::osl::MutexGuard const g(m_Mutex);

        ::boost::shared_ptr<xmlParserCtxt> const pContext(
                xmlNewParserCtxt(), xmlFreeParserCtxt);
        pContext->_private = this;
        pContext->sax->error = error_func;
        pContext->sax->warning = warning_func;
        pContext->sax->resolveEntity = resolve_func;
		// xmlSetExternalEntityLoader(external_entity_loader);
		OString oUri = OUStringToOString(sUri, RTL_TEXTENCODING_UTF8);
		char *uri = (char*) oUri.getStr();
        xmlDocPtr pDoc = xmlCtxtReadFile(pContext.get(), uri, 0, 0);
		if (pDoc == 0) {
            throwEx(pContext.get());
        }
        Reference< XDocument > const xRet(
                CDocument::CreateCDocument(pDoc).get());
        return xRet;
	}

    void SAL_CALL
    CDocumentBuilder::setEntityResolver(Reference< XEntityResolver > const& xER)
		throw (RuntimeException)
	{
        ::osl::MutexGuard const g(m_Mutex);

        m_xEntityResolver = xER;
    }

	Reference< XEntityResolver > SAL_CALL CDocumentBuilder::getEntityResolver()
		throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        return m_xEntityResolver;
    }

    void SAL_CALL
    CDocumentBuilder::setErrorHandler(Reference< XErrorHandler > const& xEH)
		throw (RuntimeException)
    {
        ::osl::MutexGuard const g(m_Mutex);

        m_xErrorHandler = xEH;
    }
}
