package lux.functions;

import java.io.IOException;

import lux.Evaluator;
import lux.index.IndexConfiguration;
import lux.index.field.XmlTextField;
import lux.xpath.FunCall;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.AtomicValue;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.Value;

import org.apache.lucene.index.Fields;
import org.apache.lucene.index.MultiFields;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.util.BytesRef;

/**
 * <code>function lux:field-terms($field-name as xs:string?, $start as xs:string?) as xs:anyAtomicItem*</code>
 * <p>
 * This function accepts the name of a Lucene field, and a starting value, and
 * returns the sequence of terms drawn from the field, ordered according to its
 * natural order, starting with the first term that is >= the starting value.
 * </p>
 * <p>
 * If the $field-name argument is empty, the terms are drawn from the default
 * field defined by the {@link IndexConfiguration}, generally the
 * {@link XmlTextField}.
 * </p>
 */
public class FieldTerms extends ExtensionFunctionDefinition {

    @Override
    public StructuredQName getFunctionQName() {
        return new StructuredQName("lux", FunCall.LUX_NAMESPACE, "field-terms");
    }

    @Override
    public SequenceType[] getArgumentTypes() {
        return new SequenceType[] { SequenceType.OPTIONAL_STRING, SequenceType.OPTIONAL_STRING };
    }

    @Override
    public int getMinimumNumberOfArguments() {
        return 0;
    }

    @Override
    public int getMaximumNumberOfArguments() {
        return 2;
    }

    @Override
    public boolean trustResultType() {
        return true;
    }

    @Override
    public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
        return SequenceType.ATOMIC_SEQUENCE;
    }

    @Override
    public ExtensionFunctionCall makeCallExpression() {
        return new FieldTermsCall();
    }

    class FieldTermsCall extends ExtensionFunctionCall {

        @SuppressWarnings("rawtypes")
        @Override
        public SequenceIterator<? extends Item> call(SequenceIterator<? extends Item>[] arguments, XPathContext context)
                throws XPathException {
            String fieldName = null, start = "";
            if (arguments.length > 0) {
                Item<?> arg0 = arguments[0].next();
                if (arg0 != null) {
                	fieldName = arg0.getStringValue();
                }
                if (arguments.length > 1) {
                    Item<?> arg1 = arguments[1].next();
                    start = arg1 == null ? "" : arg1.getStringValue();
                }
            }
            Evaluator eval = SearchBase.getEvaluator(context);
            try {
                if (fieldName == null) {
                    fieldName = eval.getCompiler().getIndexConfiguration().getDefaultFieldName();
                    if (fieldName == null) {
                        return Value.asIterator(EmptySequence.getInstance());
                    }
                }
                return new TermsIterator(eval, new Term(fieldName, start));
            } catch (IOException e) {
                throw new XPathException("failed getting terms from field " + fieldName, e);
            }
        }

    }

    class TermsIterator implements SequenceIterator<AtomicValue> {
        private TermsEnum terms;
        private final Evaluator eval;
        private Term term;
        private int pos;
        private String current;

        TermsIterator(Evaluator eval, Term term) throws IOException {
            this.term = term;
            this.eval = eval;
            pos = -1;
            createTermsEnum(term);
        }

        private void createTermsEnum(Term t) throws IOException {
            String fieldName = t.field();
            // TODO: get atomic sub readers and iterate values from those 
            /*  From: http://lucene.apache.org/core/4_0_0-BETA/MIGRATE.html

                Note that the MultiFields approach entails a performance
                hit on MultiReaders, as it must merge terms/docs/positions
                on the fly. It's generally better to instead get the
                sequential readers (use oal.util.ReaderUtil) and then step
                through those readers yourself, if you can (this is how
                Lucene drives searches).
            */
            Fields fields = MultiFields.getFields(eval.getSearcher().getIndexReader());
            if (fields != null) {
                terms = fields.terms(fieldName).iterator(null);
                if (t != null) {
                    if (terms.seekCeil(new BytesRef(t.text().getBytes("utf-8"))) == TermsEnum.SeekStatus.END) {
                        pos = -1;
                    } else {
                        current = terms.term().utf8ToString();
                    }
                }
            }
        }

        @Override
        public AtomicValue next() throws XPathException {
            try {
                if (current == null) {
                    return null;
                }
                String value = current;
                BytesRef bytesRef = terms.next();
                if (bytesRef == null) {
                    pos = -1;
                    current = null;
                } else {
                    ++pos;
                    current = bytesRef.utf8ToString();
                }
                return new net.sf.saxon.value.StringValue(value);
            } catch (IOException e) {
                throw new XPathException(e);
            }
        }

        @Override
        public AtomicValue current() {
            return new net.sf.saxon.value.StringValue(current.toString());
        }

        @Override
        public int position() {
            return pos;
        }

        @Override
        public void close() {
        }

        @Override
        public SequenceIterator<AtomicValue> getAnother() throws XPathException {
            try {
                return new TermsIterator(eval, term);
            } catch (IOException e) {
                throw new XPathException(e);
            }
        }

        @Override
        public int getProperties() {
            return 0;
        }

    }

}

/*
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/.
 */

@@ -733,16 +733,54 @@
 x.Term;%0A
+import org.apache.lucene.index.Terms;%0A
 import o
@@ -4885,17 +4885,28 @@
         
-t
+Terms fieldT
 erms = f
@@ -4927,16 +4927,98 @@
 eldName)
+;%0A                if (fieldTerms != null) %7B%0A                    terms = fieldTerms
 .iterato
@@ -5038,24 +5038,28 @@
             
+    
 if (t != nul
@@ -5055,32 +5055,36 @@
 f (t != null) %7B%0A
+    
                 
@@ -5148,17 +5148,17 @@
 f-8%22))) 
-=
+!
 = TermsE
@@ -5203,67 +5203,8 @@
     
-  
-  pos = -1;%0A                    %7D else %7B%0A                
     
@@ -5238,32 +5238,58 @@
 utf8ToString();%0A
+                        %7D%0A
                 

patch NPE when requested field does not exist