#define IN_LIBEXSLT
#include "libexslt/libexslt.h"

#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#include <libxslt/xsltutils.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/extensions.h>

#include "exslt.h"

/**
 * exsltSetsDifferenceFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathDifference for use by the XPath processor
 */
static void
exsltSetsDifferenceFunction (xmlXPathParserContextPtr ctxt, int nargs) {
    xmlNodeSetPtr arg1, arg2, ret;

    if (nargs != 2) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    arg2 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    arg1 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt)) {
        xmlXPathFreeNodeSet(arg2);
	return;
    }

    ret = xmlXPathDifference(arg1, arg2);

    if (ret != arg1)
	xmlXPathFreeNodeSet(arg1);
    xmlXPathFreeNodeSet(arg2);

    xmlXPathReturnNodeSet(ctxt, ret);
}

/**
 * exsltSetsIntersectionFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathIntersection for use by the XPath processor
 */
static void
exsltSetsIntersectionFunction (xmlXPathParserContextPtr ctxt, int nargs) {
    xmlNodeSetPtr arg1, arg2, ret;

    if (nargs != 2) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    arg2 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    arg1 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt)) {
        xmlXPathFreeNodeSet(arg2);
	return;
    }

    ret = xmlXPathIntersection(arg1, arg2);

    xmlXPathFreeNodeSet(arg1);
    xmlXPathFreeNodeSet(arg2);

    xmlXPathReturnNodeSet(ctxt, ret);
}

/**
 * exsltSetsDistinctFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathDistinct for use by the XPath processor
 */
static void
exsltSetsDistinctFunction (xmlXPathParserContextPtr ctxt, int nargs) {
    xmlXPathObjectPtr obj;
    xmlNodeSetPtr ns, ret;
    int boolval = 0;
    void *user = NULL;

    if (nargs != 1) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    if (ctxt->value != NULL) {
        boolval = ctxt->value->boolval;
	user = ctxt->value->user;
	ctxt->value->boolval = 0;
	ctxt->value->user = NULL;
    }
    ns = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    /* !!! must be sorted !!! */
    ret = xmlXPathDistinctSorted(ns);

	if (ret != ns)
		xmlXPathFreeNodeSet(ns);

    obj = xmlXPathWrapNodeSet(ret);
    obj->user = user;
    obj->boolval = boolval;
    valuePush((ctxt), obj);
}

/**
 * exsltSetsHasSameNodesFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathHasSameNodes for use by the XPath processor
 */
static void
exsltSetsHasSameNodesFunction (xmlXPathParserContextPtr ctxt,
			      int nargs) {
    xmlNodeSetPtr arg1, arg2;
    int ret;

    if (nargs != 2) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    arg2 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    arg1 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt)) {
        xmlXPathFreeNodeSet(arg2);
	return;
    }

    ret = xmlXPathHasSameNodes(arg1, arg2);

    xmlXPathFreeNodeSet(arg1);
    xmlXPathFreeNodeSet(arg2);

    xmlXPathReturnBoolean(ctxt, ret);
}

/**
 * exsltSetsLeadingFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathLeading for use by the XPath processor
 */
static void
exsltSetsLeadingFunction (xmlXPathParserContextPtr ctxt, int nargs) {
    xmlNodeSetPtr arg1, arg2, ret;

    if (nargs != 2) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    arg2 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    arg1 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt)) {
	xmlXPathFreeNodeSet(arg2);
	return;
    }

    /*  If the second node set is empty, then the first node set is
     * returned.
     */
    if (xmlXPathNodeSetIsEmpty(arg2)) {
	xmlXPathReturnNodeSet(ctxt, arg1);

	xmlXPathFreeNodeSet(arg2);

	return;
    }
    /* !!! must be sorted */
    ret = xmlXPathNodeLeadingSorted(arg1, xmlXPathNodeSetItem(arg2, 0));

    xmlXPathFreeNodeSet(arg1);
    xmlXPathFreeNodeSet(arg2);

    xmlXPathReturnNodeSet(ctxt, ret);
}

/**
 * exsltSetsTrailingFunction:
 * @ctxt:  an XPath parser context
 * @nargs:  the number of arguments
 *
 * Wraps #xmlXPathTrailing for use by the XPath processor
 */
static void
exsltSetsTrailingFunction (xmlXPathParserContextPtr ctxt, int nargs) {
    xmlNodeSetPtr arg1, arg2, ret;

    if (nargs != 2) {
	xmlXPathSetArityError(ctxt);
	return;
    }

    arg2 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt))
	return;

    arg1 = xmlXPathPopNodeSet(ctxt);
    if (xmlXPathCheckError(ctxt)) {
	xmlXPathFreeNodeSet(arg2);
	return;
    }

    /*  If the second node set is empty, then the first node set is
     * returned.
     */
    if (xmlXPathNodeSetIsEmpty(arg2)) {
	xmlXPathReturnNodeSet(ctxt, arg1);

	xmlXPathFreeNodeSet(arg2);

	return;
    }
    /* !!! mist be sorted */
    ret = xmlXPathNodeTrailingSorted(arg1, xmlXPathNodeSetItem(arg2, 0));

    xmlXPathFreeNodeSet(arg1);
    xmlXPathFreeNodeSet(arg2);

    xmlXPathReturnNodeSet(ctxt, ret);
}

/**
 * exsltSetsRegister:
 *
 * Registers the EXSLT - Sets module
 */

void
exsltSetsRegister (void) {
    xsltRegisterExtModuleFunction ((const xmlChar *) "difference",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsDifferenceFunction);
    xsltRegisterExtModuleFunction ((const xmlChar *) "intersection",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsIntersectionFunction);
    xsltRegisterExtModuleFunction ((const xmlChar *) "distinct",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsDistinctFunction);
    xsltRegisterExtModuleFunction ((const xmlChar *) "has-same-node",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsHasSameNodesFunction);
    xsltRegisterExtModuleFunction ((const xmlChar *) "leading",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsLeadingFunction);
    xsltRegisterExtModuleFunction ((const xmlChar *) "trailing",
				   EXSLT_SETS_NAMESPACE,
				   exsltSetsTrailingFunction);
}

/**
 * exsltSetsXpathCtxtRegister:
 *
 * Registers the EXSLT - Sets module for use outside XSLT
 */
int
exsltSetsXpathCtxtRegister (xmlXPathContextPtr ctxt, const xmlChar *prefix)
{
    if (ctxt
        && prefix
        && !xmlXPathRegisterNs(ctxt,
                               prefix,
                               (const xmlChar *) EXSLT_SETS_NAMESPACE)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "difference",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsDifferenceFunction)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "intersection",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsIntersectionFunction)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "distinct",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsDistinctFunction)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "has-same-node",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsHasSameNodesFunction)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "leading",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsLeadingFunction)
        && !xmlXPathRegisterFuncNS(ctxt,
                                   (const xmlChar *) "trailing",
                                   (const xmlChar *) EXSLT_SETS_NAMESPACE,
                                   exsltSetsTrailingFunction)) {
        return 0;
    }
    return -1;
}
