package de.ids_mannheim.korap.query;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermContext;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.Spans;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.ToStringUtils;

import de.ids_mannheim.korap.query.spans.FocusSpans;

/**
 * Modify the span of a match to the boundaries of a certain class.
 * 
 * In case multiple classes are found with the very same number, the
 * span is maximized to start on the first occurrence from the left
 * and end on the last occurrence on the right.
 * 
 * In case the class to modify on is not found in the subquery, the
 * match is ignored.
 * 
 * @author diewald, margaretha
 * 
 * @see FocusSpans
 */
public class SpanFocusQuery extends SimpleSpanQuery {

    private List<Byte> classNumbers = new ArrayList<Byte>();
    private boolean isSorted = true;
    private boolean matchTemporaryClass = false;
    private boolean removeTemporaryClasses = false;
    private int windowSize = 10; // default


    /**
     * Construct a new SpanFocusQuery.
     * 
     * @param firstClause
     *            The nested {@link SpanQuery}, that contains one or
     *            more
     *            classed spans.
     * @param number
     *            The class number to focus on.
     */
    public SpanFocusQuery (SpanQuery sq, byte classNumber) {
        super(sq, true);
        classNumbers.add(classNumber);
    };


    public SpanFocusQuery (SpanQuery sq, List<Byte> classNumbers) {
        super(sq, true);
        this.classNumbers = classNumbers;
    };


    /**
     * Construct a new SpanFocusQuery. The class to focus on defaults
     * to
     * <tt>1</tt>.
     * 
     * @param firstClause
     *            The nested {@link SpanQuery}, that contains one or
     *            more
     *            classed spans.
     */
    public SpanFocusQuery (SpanQuery sq) {
        super(sq, true);
        classNumbers.add((byte) 1);
    };


    @Override
    public String toString (String field) {
        StringBuffer buffer = new StringBuffer();
        buffer.append("focus(");
        if (matchTemporaryClass) {
            buffer.append("#");
        }
        if (classNumbers.size() > 1) {
            buffer.append("[");
            for (int i = 0; i < classNumbers.size(); i++) {
                buffer.append((short) classNumbers.get(i) & 0xFF);
                if (i != classNumbers.size() - 1) {
                    buffer.append(",");
                }
            }
            buffer.append("]");
        }
        else {
            buffer.append((short) classNumbers.get(0) & 0xFF).append(": ");
        }
        buffer.append(this.firstClause.toString());
        if (!this.isSorted()) {
            buffer.append(",sorting");
        }
        buffer.append(')');
        buffer.append(ToStringUtils.boost(getBoost()));
        return buffer.toString();
    };


    @Override
    public Spans getSpans (final LeafReaderContext context, Bits acceptDocs,
            Map<Term, TermContext> termContexts) throws IOException {
        return new FocusSpans(this, context, acceptDocs, termContexts);
    };


    @Override
    public Query rewrite (IndexReader reader) throws IOException {
        SpanFocusQuery clone = null;
        SpanQuery query = (SpanQuery) this.firstClause.rewrite(reader);

        if (query != this.firstClause) {
            if (clone == null)
                clone = this.clone();
            clone.firstClause = query;
        };

        if (clone != null)
            return clone;

        return this;
    };


    @Override
    public SpanFocusQuery clone () {
        SpanFocusQuery spanFocusQuery = new SpanFocusQuery(
                (SpanQuery) this.firstClause.clone(), this.getClassNumbers());
        spanFocusQuery.setBoost(getBoost());
        spanFocusQuery.setMatchTemporaryClass(this.matchTemporaryClass);
        spanFocusQuery.setSorted(this.isSorted);
        spanFocusQuery.setRemoveTemporaryClasses(this.removeTemporaryClasses);
        return spanFocusQuery;
    };


    @Override
    public boolean equals (Object o) {
        if (this == o)
            return true;
        if (!(o instanceof SpanFocusQuery))
            return false;

        final SpanFocusQuery spanFocusQuery = (SpanFocusQuery) o;

        if (!this.firstClause.equals(spanFocusQuery.firstClause))
            return false;
        if (this.getClassNumbers() != spanFocusQuery.getClassNumbers())
            return false;

        // Probably not necessary
        return getBoost() == spanFocusQuery.getBoost();
    };


    @Override
    public int hashCode () {
        int result = firstClause.hashCode();
        for (byte number : classNumbers)
            result = 31 * result + number;
        result += Float.floatToRawIntBits(getBoost());
        return result;
    }


    public List<Byte> getClassNumbers () {
        return classNumbers;
    }


    public void setClassNumbers (List<Byte> classNumbers) {
        this.classNumbers = classNumbers;
    }


    public boolean isSorted () {
        return isSorted;
    }


    public void setSorted (boolean isSorted) {
        this.isSorted = isSorted;
    }


    public boolean matchTemporaryClass () {
        return matchTemporaryClass;
    }


    public void setMatchTemporaryClass (boolean matchTemporaryClass) {
        this.matchTemporaryClass = matchTemporaryClass;
    }


    public boolean removeTemporaryClasses () {
        return removeTemporaryClasses;
    }


    public void setRemoveTemporaryClasses (boolean rem) {
        this.removeTemporaryClasses = rem;
    }


    public int getWindowSize () {
        return windowSize;
    }


    public void setWindowSize (int windowSize) {
        this.windowSize = windowSize;
    }

};
