package org.exist.dom;

import java.io.StringReader;
import java.util.Iterator;

import org.exist.EXistException;
import org.exist.numbering.NodeId;
import org.exist.security.User;
import org.exist.security.xacml.AccessContext;
import org.exist.security.xacml.NullAccessContextException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.util.OrderedLinkedList;
import org.exist.xquery.AnalyzeContextInfo;
import org.exist.xquery.Constants;
import org.exist.xquery.PathExpr;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.parser.XQueryLexer;
import org.exist.xquery.parser.XQueryParser;
import org.exist.xquery.parser.XQueryTreeParser;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.SequenceIterator;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import antlr.collections.AST;

public class SortedNodeSet extends AbstractNodeSet {

	private PathExpr expr;
	private OrderedLinkedList list = new OrderedLinkedList();	
	private String sortExpr;
	private BrokerPool pool;
	private User user = null;
	private AccessContext accessCtx;

	private SortedNodeSet() {}
	public SortedNodeSet(BrokerPool pool, User user, String sortExpr, AccessContext accessCtx) {
		this.sortExpr = sortExpr;
		this.pool = pool;
		this.user = user;
		if(accessCtx == null)
			throw new NullAccessContextException();
		this.accessCtx = accessCtx;
	}
	
    public boolean isEmpty() {
    	return list.size() == 0;
    }

    public boolean hasOne() {
    	return list.size() == 1;
    }    

	public void addAll(Sequence other) throws XPathException {
		addAll(other.toNodeSet());
	}
	
	public void addAll(NodeSet other) {
		long start = System.currentTimeMillis();		
		DocumentSet docs = new DocumentSet();
		for (Iterator i = other.iterator(); i.hasNext();) {
            NodeProxy p = (NodeProxy)i.next();
			docs.add(p.getDocument());
		}
		DBBroker broker = null;
		try {
			broker = pool.get(user);
			XQueryContext context = new XQueryContext(broker, accessCtx);
			XQueryLexer lexer = new XQueryLexer(context, new StringReader(sortExpr));
			XQueryParser parser = new XQueryParser(lexer);
			XQueryTreeParser treeParser = new XQueryTreeParser(context);
			parser.xpath();
			if (parser.foundErrors()) {
                //TODO : error ?
				LOG.debug(parser.getErrorMessage());
			}
			AST ast = parser.getAST();
			LOG.debug("generated AST: " + ast.toStringTree());
			expr = new PathExpr(context);
			treeParser.xpath(ast, expr);
			if (treeParser.foundErrors()) {
				LOG.debug(treeParser.getErrorMessage());
			}
			expr.analyze(new AnalyzeContextInfo());
			for (SequenceIterator i = other.iterate(); i.hasNext();) {
                NodeProxy p = (NodeProxy) i.nextItem();
                IteratorItem item = new IteratorItem(broker, p, expr, docs, context);
				list.add(item);
			}
		} catch (antlr.RecognitionException re) {
			LOG.debug(re);
		} catch (antlr.TokenStreamException tse) {
			LOG.debug(tse);
		} catch (EXistException e) {
			LOG.debug("Exception during sort", e);
		} catch (XPathException e) {
			LOG.debug("Exception during sort", e);
		} finally {
			pool.release(broker);
		}
		LOG.debug(
			"sort-expression found "
				+ list.size()
				+ " in "
				+ (System.currentTimeMillis() - start)
				+ "ms.");
	}

	public void addAll(NodeList other) {
		if (!(other instanceof NodeSet))
			throw new RuntimeException("not implemented!");
		addAll((NodeSet) other);
	}

	public boolean contains(NodeProxy proxy) {		
		for (Iterator i = list.iterator(); i.hasNext();) {
            NodeProxy p = ((IteratorItem) i.next()).proxy;
			if (p.compareTo(proxy) == 0)
				return true;
		}
		return false;
	}

	public NodeProxy get(int pos) {
		final IteratorItem item = (IteratorItem) list.get(pos);
		return item == null ? null : item.proxy;
	}

    public NodeProxy get(DocumentImpl doc, NodeId nodeId) {
        NodeProxy proxy = new NodeProxy(doc, nodeId);
		for (Iterator i = list.iterator(); i.hasNext();) {
            NodeProxy p = ((IteratorItem) i.next()).proxy;
			if (p.compareTo(proxy) == 0)
				return p;
		}
		return null;
    }

    public NodeProxy get(NodeProxy proxy) {
		for (Iterator i = list.iterator(); i.hasNext();) {
            NodeProxy p = ((IteratorItem) i.next()).proxy;
			if (p.compareTo(proxy) == 0)
				return p;
		}
		return null;
	}

	public int getLength() {
		return list.size();
	}

	public Node item(int pos) {
		NodeProxy p = ((IteratorItem) list.get(pos)).proxy;
		return p == null ? null : p.getDocument().getNode(p);
	}

	public Item itemAt(int pos) {
		NodeProxy p = ((IteratorItem) list.get(pos)).proxy;
		return p == null ? null : p;
	}
	
	public NodeSetIterator iterator() {
		return new SortedNodeSetIterator(list.iterator());
	}

	/* (non-Javadoc)
	 * @see org.exist.dom.NodeSet#iterate()
	 */
	public SequenceIterator iterate() throws XPathException {
		return new SortedNodeSetIterator(list.iterator());
	}
	
	/* (non-Javadoc)
	 * @see org.exist.dom.AbstractNodeSet#unorderedIterator()
	 */
	public SequenceIterator unorderedIterator() {
		return new SortedNodeSetIterator(list.iterator());
	}
	
	private final static class SortedNodeSetIterator implements NodeSetIterator, SequenceIterator {

		Iterator pi;

		public SortedNodeSetIterator(Iterator i) {
			pi = i;
		}

		public boolean hasNext() {
			return pi.hasNext();
		}

		public Object next() {
			if (!pi.hasNext())
				return null;
			return ((IteratorItem) pi.next()).proxy;
		}

        public NodeProxy peekNode() {
            return null;
        }
        
		/* (non-Javadoc)
		 * @see org.exist.xquery.value.SequenceIterator#nextItem()
		 */
		public Item nextItem() {
			if (!pi.hasNext())
				return null;
			return ((IteratorItem) pi.next()).proxy;
		}
		
		public void remove() {
		}
        
        public void setPosition(NodeProxy proxy) {
            throw new RuntimeException("NodeSetIterator.setPosition() is not supported by SortedNodeSetIterator");
        }
	}
	
	private static final class IteratorItem extends OrderedLinkedList.Node {
		NodeProxy proxy;
		String value = null;

		public IteratorItem(DBBroker broker, NodeProxy proxy, PathExpr expr, DocumentSet ndocs, 
                XQueryContext context) {
			this.proxy = proxy;
			try {
				Sequence seq = expr.eval(proxy);
				StringBuffer buf = new StringBuffer();
				OrderedLinkedList strings = new OrderedLinkedList();
				Item item;
				for(SequenceIterator i = seq.iterate(); i.hasNext(); ) {
					item = i.nextItem();
					strings.add(new OrderedLinkedList.SimpleNode(item.getStringValue().toUpperCase()));
				}
				for (Iterator j = strings.iterator(); j.hasNext();) 
					buf.append(((OrderedLinkedList.SimpleNode) j.next()).getData());
				value = buf.toString();
			} catch (XPathException e) {
				LOG.warn(e.getMessage(), e);
			}
		}

		public int compareTo(OrderedLinkedList.Node other) {
			IteratorItem o = (IteratorItem) other;
			if (value == null)
				return o.value == null ? Constants.EQUAL : Constants.SUPERIOR;
			if (o.value == null)
				return value == null ? Constants.EQUAL : Constants.INFERIOR;
			return value.compareTo(o.value);
		}

		public boolean equals(OrderedLinkedList.Node other) {
			IteratorItem o = (IteratorItem) other;
			return value.equals(o.value);
		}
	}

	/* (non-Javadoc)
	 * @see org.exist.dom.NodeSet#add(org.exist.dom.NodeProxy)
	 */
	public void add(NodeProxy proxy) {
        LOG.info("Called SortedNodeSet.add()");
	}

}
