/**
 * Copyright (C) 2010 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.function.xxforms;

import org.orbeon.oxf.xforms.function.XFormsFunction;
import org.orbeon.saxon.expr.Expression;
import org.orbeon.saxon.expr.PathMap;
import org.orbeon.saxon.expr.XPathContext;
import org.orbeon.saxon.om.Item;
import org.orbeon.saxon.om.NodeInfo;
import org.orbeon.saxon.om.SequenceIterator;
import org.orbeon.saxon.sort.AtomicComparer;
import org.orbeon.saxon.sort.SortKeyDefinition;
import org.orbeon.saxon.sort.SortKeyEvaluator;
import org.orbeon.saxon.sort.SortedIterator;
import org.orbeon.saxon.trans.XPathException;
import org.orbeon.saxon.value.Value;

/**
 * exf:sort() function
 */
public class XXFormsSort extends XFormsFunction {

    @Override
    public SequenceIterator iterate(XPathContext xpathContext) throws XPathException {

        final Expression sequenceToSortExpression = argument[0];
        final Expression sortKeyExpression = argument[1];

        return sort(xpathContext, null, sequenceToSortExpression, sortKeyExpression);
    }

    protected SequenceIterator sort(XPathContext xpathContext, final XPathContext keyXPathContext, Expression sequenceToSortExpression, final Expression sortKeyExpression) throws XPathException {
        final SortKeyEvaluator sortKeyEvaluator = new SortKeyEvaluator() {
            public Item evaluateSortKey(int n, XPathContext context) throws XPathException {

                // The context may be provided from "outside" (e.g. because the expression was compiled at runtime).
                // In that case, use it, and make sure it has the correct current iterator.

                final XPathContext sortKeyContext; {
                    if (keyXPathContext != null) {
                        sortKeyContext = keyXPathContext;
                        sortKeyContext.setCurrentIterator(context.getCurrentIterator());
                    } else {
                        sortKeyContext = context;
                    }
                }

                Item c = sortKeyExpression.evaluateItem(sortKeyContext);
                if (c instanceof NodeInfo) {
                    final Value v = ((NodeInfo)c).atomize();
                    if (v.getLength() == 0) {
                        c = null;
                    } else if (v.getLength() == 1) {
                        c = v.itemAt(0);
                    } else {
                        throw new XPathException("error in xxf:sort() - a node has a typed value of length > 1");
                    }
                }
                return c;
            }
        };
        final SortKeyDefinition sortKeyDefinition = getSortKeyDefinition(sortKeyExpression);
        final AtomicComparer comparer = sortKeyDefinition.makeComparator(xpathContext);
        final AtomicComparer[] comparers = { comparer };
        return new SortedIterator(xpathContext, sequenceToSortExpression.iterate(xpathContext), sortKeyEvaluator, comparers);
    }
    
    private SortKeyDefinition getSortKeyDefinition(Expression sortKeyExpression) {
        final Expression datatypeExpression = (argument.length > 2) ? argument[2] : null;
        final Expression orderExpression = (argument.length > 3) ? argument[3] : null;
        final Expression caseOrderExpression = (argument.length > 4) ? argument[4] : null;

//        Expression langExpression = argument[5];// new in XSLT 2.0
//        Expression collationExpression = argument[6];// new in XSLT 2.0
//        Expression stableExpression = argument[7];// new in XSLT 2.0

        final SortKeyDefinition sortKeyDefinition = new SortKeyDefinition();
        sortKeyDefinition.setSortKey(sortKeyExpression);

        if (datatypeExpression != null)
            sortKeyDefinition.setDataTypeExpression(datatypeExpression);
        if (orderExpression != null)
            sortKeyDefinition.setOrder(orderExpression);
        if (caseOrderExpression != null)
            sortKeyDefinition.setCaseOrder(caseOrderExpression);

//        sortKey.setLanguage(langExpression);
//        sortKey.setCollationName(collationExpression);
//        sortKey.setStable(stableExpression);

        return sortKeyDefinition;
    }

    @Override
    public PathMap.PathMapNodeSet addToPathMap(PathMap pathMap, PathMap.PathMapNodeSet pathMapNodeSet) {
        final PathMap.PathMapNodeSet target = argument[0].addToPathMap(pathMap, pathMapNodeSet);

        final SortKeyDefinition sortKeyDefinition = getSortKeyDefinition(argument[1]);

        // Sort key
        sortKeyDefinition.getSortKey().addToPathMap(pathMap, target);

        // Sort key parameters
        Expression e = sortKeyDefinition.getOrder();
        if (e != null) {
            e.addToPathMap(pathMap, pathMapNodeSet);
        }
        e = sortKeyDefinition.getCaseOrder();
        if (e != null) {
            e.addToPathMap(pathMap, pathMapNodeSet);
        }
        e = sortKeyDefinition.getDataTypeExpression();
        if (e != null) {
            e.addToPathMap(pathMap, pathMapNodeSet);
        }
        e = sortKeyDefinition.getLanguage();
        if (e != null) {
            e.addToPathMap(pathMap, pathMapNodeSet);
        }
        e = sortKeyDefinition.getCollationNameExpression();
        if (e != null) {
            e.addToPathMap(pathMap, pathMapNodeSet);
        }

        return target;
    }
}
