/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2001-04 Wolfgang M. Meier (wolfgang@exist-db.org) 
*  and others (see http://exist-db.org)
*
*  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
*  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.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* 
*  $Id: PreorderedValueSequence.java 4042 2006-08-13 16:22:37Z wolfgang_m $
*/
package org.exist.xquery.value;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;

import org.exist.dom.ContextItem;
import org.exist.dom.NodeProxy;
import org.exist.dom.NodeSet;
import org.exist.xquery.Constants;
import org.exist.xquery.Expression;
import org.exist.xquery.OrderSpec;
import org.exist.xquery.XPathException;

/**
 * A sequence that sorts its items in the order specified by the order specs
 * of an "order by" clause. Used by {@link org.exist.xquery.ForExpr}.
 * 
 * For better performance, the whole input sequence is sorted in one single step.
 * However, this only works if every order expression returns a result of type
 * node.
 * 
 * @author wolf
 */
public class PreorderedValueSequence extends AbstractSequence {
	
	private OrderSpec orderSpecs[];
	private OrderedNodeProxy[] nodes;
	
	public PreorderedValueSequence(OrderSpec specs[], Sequence input, int contextId) throws XPathException {
		this.orderSpecs = specs;
		nodes = new OrderedNodeProxy[input.getLength()];
		int j = 0;
		for(SequenceIterator i = input.unorderedIterator(); i.hasNext(); j++) {
			NodeProxy p = (NodeProxy)i.nextItem();
			nodes[j] = new OrderedNodeProxy(p);
			p.addContextNode(contextId, nodes[j]);
		}
		processAll();
	}
	
	private void processAll() throws XPathException {
		for(int i = 0; i < orderSpecs.length; i++) {
			Expression expr = orderSpecs[i].getSortExpression();
			NodeSet result = expr.eval(null).toNodeSet();
			for(Iterator j = result.iterator(); j.hasNext(); ) {
				NodeProxy p = (NodeProxy)j.next();
				ContextItem context = p.getContext();
				//TODO : review to consider transverse context
				while(context != null) {
					if(context.getNode() instanceof OrderedNodeProxy) {
						OrderedNodeProxy cp = (OrderedNodeProxy)context.getNode();
						cp.values[i] = p.atomize();
					}
					context = context.getNextDirect();
				}
			}
		}
	}

    public void clearContext(int contextId) {
        for (int i = 0; i < nodes.length; i++) {
            nodes[i].clearContext(contextId);
        }
    }
    
	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#getItemType()
	 */
	public int getItemType() {
		return Type.NODE;
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#iterate()
	 */
	public SequenceIterator iterate() throws XPathException {
		sort();
		return new PreorderedValueSequenceIterator();
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#unorderedIterator()
	 */
	public SequenceIterator unorderedIterator() {
		return new PreorderedValueSequenceIterator();
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#getLength()
	 */
	public int getLength() {
		return nodes.length;
	}
	
    public boolean isEmpty() {
    	return nodes.length == 0;
    }

    public boolean hasOne() {
    	return nodes.length == 1;
    }    

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#add(org.exist.xquery.value.Item)
	 */
	public void add(Item item) throws XPathException {
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.AbstractSequence#itemAt(int)
	 */
	public Item itemAt(int pos) {
		return nodes[pos];
	}

	/* (non-Javadoc)
	 * @see org.exist.xquery.value.Sequence#toNodeSet()
	 */
	public NodeSet toNodeSet() throws XPathException {
		return null;
	}
	
	/* (non-Javadoc)
     * @see org.exist.xquery.value.Sequence#removeDuplicates()
     */
    public void removeDuplicates() {
        // TODO: is this ever relevant?
    }
    
	private void sort() {
		Arrays.sort(nodes, new OrderedComparator());
	}
	
	private class OrderedComparator implements Comparator {
		
		/* (non-Javadoc)
		 * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
		 */
		public int compare(Object o1, Object o2) {
			final OrderedNodeProxy p1 = (OrderedNodeProxy)o1;
			final OrderedNodeProxy p2 = (OrderedNodeProxy)o2;
			int cmp = 0;
			AtomicValue a, b;
			for(int i = 0; i < p1.values.length; i++) {
				try {
					a = p1.values[i];
					b = p2.values[i];
					if(a == AtomicValue.EMPTY_VALUE && b != AtomicValue.EMPTY_VALUE) {
						if((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0)
							cmp = Constants.INFERIOR;
						else
							cmp = Constants.SUPERIOR;
					} else if(a != AtomicValue.EMPTY_VALUE && b == AtomicValue.EMPTY_VALUE) {
						if((orderSpecs[i].getModifiers() & OrderSpec.EMPTY_LEAST) != 0)
							cmp = Constants.SUPERIOR;
						else
							cmp = Constants.INFERIOR;
					} else
						cmp = a.compareTo(orderSpecs[i].getCollator(), b);
					if((orderSpecs[i].getModifiers() & OrderSpec.DESCENDING_ORDER) != 0)
						cmp = cmp * -1;
					if(cmp != Constants.EQUAL)
						break;
				} catch (XPathException e) {
				}
			}
			return cmp;
		}
	}
	
	private class OrderedNodeProxy extends NodeProxy {
		
		AtomicValue[] values;
		
		public OrderedNodeProxy(NodeProxy p) {
			super(p);
			values = new AtomicValue[orderSpecs.length];
			for(int i = 0; i < values.length; i++)
				values[i] = AtomicValue.EMPTY_VALUE;
		}
	}
	
	private class PreorderedValueSequenceIterator implements SequenceIterator {
		
		int pos = 0;
		
		/* (non-Javadoc)
		 * @see org.exist.xquery.value.SequenceIterator#hasNext()
		 */
		public boolean hasNext() {
			return pos < nodes.length;
		}
		
		/* (non-Javadoc)
		 * @see org.exist.xquery.value.SequenceIterator#nextItem()
		 */
		public Item nextItem() {
			if(pos < nodes.length)
				return nodes[pos++];
			return null;
		}
	}
}
