/*
 * 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 "XPathCAPI.h"



#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/sax/SAXException.hpp>

#include <xalanc/Include/XalanAutoPtr.hpp>

#include <xalanc/XalanDOM/XalanDocument.hpp>



#include <xalanc/PlatformSupport/XalanTranscodingServices.hpp>



#include <xalanc/XPath/XObjectFactory.hpp>
#include <xalanc/XPath/XPath.hpp>
#include <xalanc/XPath/XPathConstructionContextDefault.hpp>
#include <xalanc/XPath/XPathEvaluator.hpp>
#include <xalanc/XPath/XPathExecutionContextDefault.hpp>
#include <xalanc/XPath/XPathFactoryDefault.hpp>



#include <xalanc/XalanSourceTree/XalanSourceTreeInit.hpp>
#include <xalanc/XalanSourceTree/XalanSourceTreeDOMSupport.hpp>
#include <xalanc/XalanSourceTree/XalanSourceTreeParserLiaison.hpp>



static bool fInitialized = false;
static bool fTerminated = false;




using xalanc::XalanDOMChar;
using xalanc::XalanDOMString;
using xalanc::XalanOutputTranscoder;
using xalanc::XPath;
using xalanc::XPathEvaluator;
using xalanc::XalanSourceTreeInit;
using xalanc::XalanTranscodingServices;
using xalanc::XalanMemMgrs;


static XalanSourceTreeInit*     theSourceTreeInit = 0;



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanXPathAPIInitialize()
{
    if (fInitialized == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_CANNOT_REINITIALIZE;
    }
    else
    {
        int theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            using xercesc::XMLPlatformUtils;

            XMLPlatformUtils::Initialize();

            try
            {
                XPathEvaluator::initialize(XalanMemMgrs::getDefaultXercesMemMgr());

                try
                {
                    theSourceTreeInit = new XalanSourceTreeInit(XalanMemMgrs::getDefaultXercesMemMgr());
                }
                catch(...)
                {
                    XPathEvaluator::terminate();

                    throw;
                }
            }
            catch(...)
            {
                XMLPlatformUtils::Terminate();

                throw;
            }

            fInitialized = true;
        }
        catch(...)
        {
            fTerminated = true;

            theResult = XALAN_XPATH_API_ERROR_INITIALIZATION_FAILED;
        }

        return theResult;
    }
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanXPathAPITerminate()
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else
    {
        int theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            using xercesc::XMLPlatformUtils;

            delete theSourceTreeInit;

            theSourceTreeInit = 0;

            XPathEvaluator::terminate();

            XMLPlatformUtils::Terminate();

            fTerminated = true;
        }
        catch(...)
        {
            theResult = XALAN_XPATH_API_ERROR_TERMINATION_FAILED;
        }

        return theResult;
    }
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanCreateXPathEvaluator(XalanXPathEvaluatorHandle*    theHandle)
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else if (theHandle == 0)
    {
        return XALAN_XPATH_API_ERROR_INVALID_PARAMETER;
    }
    else
    {
        int theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            *theHandle = new XPathEvaluator(XalanMemMgrs::getDefaultXercesMemMgr());
        }
        catch(...)
        {
            theResult = XALAN_XPATH_API_ERROR_UNKNOWN;
        }

        return theResult;
    }
}



inline XPathEvaluator*
getEvaluator(XalanXPathEvaluatorHandle  theHandle)
{
    assert(theHandle != 0);

    return static_cast<XPathEvaluator*>(theHandle);
}



inline XPath*
getXPath(XalanXPathHandle   theHandle)
{
    assert(theHandle != 0);

    return static_cast<XPath*>(theHandle);
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanDestroyXPathEvaluator(XalanXPathEvaluatorHandle    theXalanHandle)
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else if (theXalanHandle == 0)
    {
        return XALAN_XPATH_API_ERROR_INVALID_PARAMETER;
    }
    else
    {
        int theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            delete getEvaluator(theXalanHandle);
        }
        catch(...)
        {
            theResult = XALAN_XPATH_API_ERROR_UNKNOWN;
        }

        return theResult;
    }
}


typedef XalanTranscodingServices::size_type     size_type;

inline int
transcodeString(
            XalanOutputTranscoder*  theTranscoder,
            const char*             theString,
            XalanDOMChar*           theChars,
            unsigned char*          theCharsCount,
            size_type               theLength,
            XalanDOMString&         theResultString)
{
    assert(theTranscoder != 0);
    assert(theString != 0);
    assert(theChars != 0);
    assert(theCharsCount != 0);

    size_type   theSourceCharsTranscoded = 0;
    size_type   theTargetBytesUsed = 0;

    const XalanTranscodingServices::eCode   theCode = theTranscoder->transcode(
                    reinterpret_cast<const XalanOutputTranscoder::XalanXMLByte*>(theString),
                    theLength,
                    theChars,
                    theLength,
                    theSourceCharsTranscoded,
                    theTargetBytesUsed,
                    theCharsCount);

    if (theCode != XalanTranscodingServices::OK)
    {
        return XALAN_XPATH_API_ERROR_TRANSCODING;
    }
    else
    {
        assert(XalanDOMString::size_type(theTargetBytesUsed) == theTargetBytesUsed);

        theResultString.assign(theChars, XalanDOMString::size_type(theTargetBytesUsed));

        return XALAN_XPATH_API_SUCCESS;
    }
}



inline int
transcodeString(
            const char*         theString,
            const char*         theStringEncoding,
            XalanDOMString&     theResultString)
{
    assert(theString != 0);

    int     theResult = XALAN_XPATH_API_SUCCESS;

    if (theStringEncoding == 0 || XalanDOMString::length(theStringEncoding) == 0)
    {
        theResultString = theString;
    }
    else
    {
        XalanTranscodingServices::eCode     theCode = XalanTranscodingServices::OK;

        XalanOutputTranscoder* const    theTranscoder = 
            XalanTranscodingServices::makeNewTranscoder(
                        XalanMemMgrs::getDefaultXercesMemMgr(),
                        XalanDOMString(theStringEncoding, XalanMemMgrs::getDefaultXercesMemMgr()),
                        theCode,
                        1024);

        if (theCode == XalanTranscodingServices::UnsupportedEncoding)
        {
            theResult = XALAN_XPATH_API_ERROR_UNSUPPORTED_ENCODING;
        }
        else if (theCode != XalanTranscodingServices::OK)
        {
            theResult = XALAN_XPATH_API_ERROR_UNKNOWN;
        }
        else
        {
            assert(theTranscoder != 0);

            // Since UTF-16 can represent any Unicode value in
            // one 16-bit value, we'll use the length of the
            // string as the maximum length of the resulting
            // transcoded string.  This doesn't account for
            // surrogate pairs, but those are for private use
            // only right now, so we don't really need to
            // worry about them.
            const XalanDOMString::size_type     theLength = XalanDOMString::length(theString);

            // Only use a dynamically-allocated array for very long
            // XPath expressions.
            const XalanDOMString::size_type     maxStackArraySize = 100;

            if (theLength >= maxStackArraySize)
            {
                using xalanc::XalanArrayAutoPtr;

                XalanArrayAutoPtr<unsigned char>    theCharsCount(new unsigned char[theLength + 1]);
                XalanArrayAutoPtr<XalanDOMChar>     theChars(new XalanDOMChar[theLength + 1]);

                theResult = transcodeString(
                                theTranscoder,
                                theString,
                                theChars.get(),
                                theCharsCount.get(),
                                theLength,
                                theResultString);
            }
            else
            {
                unsigned char   theCharsCount[maxStackArraySize];
                XalanDOMChar    theChars[maxStackArraySize];

                theResult = transcodeString(
                                theTranscoder,
                                theString,
                                theChars,
                                theCharsCount,
                                theLength,
                                theResultString);
            }

            XalanTranscodingServices::destroyTranscoder(theTranscoder);
        }
    }

    return theResult;
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanCreateXPath(
            XalanXPathEvaluatorHandle   theXalanHandle,
            const char*                 theXPathExpression,
            const char*                 theXPathExpressionEncoding,
            XalanXPathHandle*           theXPathHandle)
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else if (theXalanHandle == 0 || theXPathHandle == 0 || theXPathExpression == 0 || XalanDOMString::length(theXPathExpression) == 0)
    {
        return XALAN_XPATH_API_ERROR_INVALID_PARAMETER;
    }
    else
    {
        int     theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            XPathEvaluator* const   theEvaluator = getEvaluator(theXalanHandle);
            assert(theEvaluator != 0);

            XalanDOMString  theExpressionString(XalanMemMgrs::getDefaultXercesMemMgr());

            theResult = transcodeString(
                theXPathExpression,
                theXPathExpressionEncoding,
                theExpressionString);

            if (theResult == XALAN_XPATH_API_SUCCESS)
            {
                const XalanDOMChar* const   thePointer = theExpressionString.c_str();

                using xalanc::length;

                if (length(thePointer) == 0)
                {
                    theResult = XALAN_XPATH_API_ERROR_TRANSCODING;
                }
                else
                {
                    *theXPathHandle = theEvaluator->createXPath(thePointer);
                }
            }
        }
        catch(...)
        {
            theResult = XALAN_XPATH_API_ERROR_INVALID_XPATH;
        }

        return theResult;
    }
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanDestroyXPath(
            XalanXPathEvaluatorHandle   theXalanHandle,
            XalanXPathHandle            theXPathHandle)
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else if (theXalanHandle == 0 || theXPathHandle == 0)
    {
        return XALAN_XPATH_API_ERROR_INVALID_PARAMETER;
    }
    else
    {
        int theResult = XALAN_XPATH_API_SUCCESS;

        try
        {
            XPathEvaluator* const   theEvaluator = getEvaluator(theXalanHandle);
            assert(theEvaluator != 0);

            if (theEvaluator->destroyXPath(getXPath(theXPathHandle)) == false)
            {
                theResult = XALAN_XPATH_API_ERROR_INVALID_XPATH;
            }
        }
        catch(...)
        {
            theResult = XALAN_XPATH_API_ERROR_UNKNOWN;
        }

        return theResult;
    }
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanEvaluateXPathAsBoolean(
            XalanXPathEvaluatorHandle   theXalanHandle,
            XalanXPathHandle            theXPathHandle,
            const char*                 theXML,
            int*                        theResult)
{
    if (fInitialized == false)
    {
        return XALAN_XPATH_API_ERROR_NOT_INITIALIZED;
    }
    else if (fTerminated == true)
    {
        return XALAN_XPATH_API_ERROR_ALREADY_TERMINATED;
    }
    else if (theXalanHandle == 0 || theXPathHandle == 0 || theXML == 0)
    {
        return XALAN_XPATH_API_ERROR_INVALID_PARAMETER;
    }
    else
    {
        int theError = XALAN_XPATH_API_SUCCESS;

        using xercesc::SAXException;

        try
        {
            XPathEvaluator* const   theEvaluator = getEvaluator(theXalanHandle);
            assert(theEvaluator != 0);

            XPath* const    theXPath = getXPath(theXPathHandle);
            assert(theXPath != 0);

            using xalanc::XalanDocument;
            using xalanc::XalanSourceTreeDOMSupport;
            using xalanc::XalanSourceTreeParserLiaison;

            XalanSourceTreeDOMSupport       theDOMSupport;
            XalanSourceTreeParserLiaison    theLiaison(theDOMSupport, XalanMemMgrs::getDefaultXercesMemMgr());

            // Hook the two together...
            theDOMSupport.setParserLiaison(&theLiaison);

            using xercesc::MemBufInputSource;

            // Create an input source...
            const MemBufInputSource     theInputSource(
                                            reinterpret_cast<const XMLByte*>(theXML),
                                            XalanDOMString::length(theXML),
                                            "SourceXML",
                                            false);

            // Parse the document...
            XalanDocument* const    theDocument =
                            theLiaison.parseXMLStream(theInputSource);
            assert(theDocument != 0);

            if (theEvaluator->evaluate(
                    theDOMSupport,
                    theDocument,
                    *theXPath)->boolean(theEvaluator->getExecutionContext()) == true)
            {
                *theResult = 1;
            }
            else
            {
                *theResult = 0;
            }
        }
        catch(const SAXException&)
        {
            theError = XALAN_XPATH_API_ERROR_BAD_XML;
        }
        catch(...)
        {
            theError = XALAN_XPATH_API_ERROR_UNKNOWN;
        }

        return theError;
    }
}



XALAN_XPATHCAPI_EXPORT_FUNCTION(int)
XalanEvaluateXPathExpressionAsBoolean(
            XalanXPathEvaluatorHandle   theXalanHandle,
            const char*                 theXPathExpression,
            const char*                 theXPathExpressionEncoding,
            const char*                 theXML,
            int*                        theResult)
{
    XalanXPathHandle    theXPathHandle = 0;

    int     theError = XalanCreateXPath(
                    theXalanHandle,
                    theXPathExpression,
                    theXPathExpressionEncoding,
                    &theXPathHandle);

    if (theError == XALAN_XPATH_API_SUCCESS)
    {
        assert(theXPathHandle != 0);

        theError = XalanEvaluateXPathAsBoolean(theXalanHandle, theXPathHandle, theXML, theResult);

        const int   theDestroyResult =
            XalanDestroyXPath(theXalanHandle, theXPathHandle);

        if (theDestroyResult != XALAN_XPATH_API_SUCCESS)
        {
            theError = theDestroyResult;
        }
    }

    return theError;
}
