Added ExpandedExclusionSpans
diff --git a/src/main/java/de/ids_mannheim/korap/query/spans/ExpandedExclusionSpans.java b/src/main/java/de/ids_mannheim/korap/query/spans/ExpandedExclusionSpans.java
new file mode 100644
index 0000000..853b1a3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/spans/ExpandedExclusionSpans.java
@@ -0,0 +1,211 @@
+package de.ids_mannheim.korap.query.spans;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.AtomicReaderContext;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermContext;
+import org.apache.lucene.search.spans.Spans;
+import org.apache.lucene.util.Bits;
+
+import de.ids_mannheim.korap.query.SpanExpansionQuery;
+
+public class ExpandedExclusionSpans extends SimpleSpans{
+	
+	private int min, max;
+	private int direction;
+	private List<CandidateSpan> candidateSpans;
+	private boolean hasMoreNotClause;
+	private Spans notClause;
+	
+	public ExpandedExclusionSpans(SpanExpansionQuery spanExpansionQuery,
+			AtomicReaderContext context, Bits acceptDocs,
+			Map<Term, TermContext> termContexts) throws IOException {
+		super(spanExpansionQuery, context, acceptDocs, termContexts);
+		
+		if (spanExpansionQuery.getSecondClause() == null){
+			throw new IllegalArgumentException("The SpanExpansionQuery " +
+				"is not valid. The spanquery to exclude (notClause) cannot " +
+				"be null.");
+		}
+		
+		/*if (spanExpansionQuery.getMin() < 1){
+			throw new IllegalArgumentException("Min occurrence for notClause " +
+				"must be at least 1.");
+		}*/
+		
+		this.min = spanExpansionQuery.getMin();
+		this.max = spanExpansionQuery.getMax();
+		this.direction = spanExpansionQuery.getDirection();
+		
+		this.notClause = secondSpans;
+		this.hasMoreNotClause = notClause.next();		
+		
+		candidateSpans = new ArrayList<CandidateSpan>();		
+		hasMoreSpans = firstSpans.next();
+	}
+
+	@Override
+	public boolean next() throws IOException {
+		matchPayload.clear();
+		isStartEnumeration = false;
+		return advance();
+	}
+
+	private boolean advance() throws IOException {
+		while (hasMoreSpans || candidateSpans.size() > 0){
+			if (candidateSpans.size() > 0){				
+				CandidateSpan cs = candidateSpans.get(0);
+				matchDocNumber = cs.getDoc();
+				matchStartPosition = cs.getStart();
+				matchEndPosition = cs.getEnd();
+				matchPayload = cs.getPayloads();
+				candidateSpans.remove(0);
+				return true;
+			}
+			else if (!hasMoreNotClause || notClause.doc() > firstSpans.doc()){
+				generateCandidates(min, max, direction);
+				hasMoreSpans = firstSpans.next();				
+			}
+			else findMatches();
+		}
+		return false;
+	}
+	
+	private void findMatches() throws IOException {
+		while (hasMoreNotClause &&	notClause.doc() <= firstSpans.doc()){
+			
+			if (notClause.doc() == firstSpans.doc()){ 
+				
+				if (direction < 0 ){ // left
+					int counter = max;
+					int maxPos = max;
+					CandidateSpan lastNotClause = null;
+					while (hasMoreNotClause && 
+							notClause.start() < firstSpans.start()){
+						
+						// between max and firstspan.start()
+						if (notClause.start() >= firstSpans.start() - counter){
+							maxPos = firstSpans.start() - notClause.start() -1;
+							lastNotClause = new CandidateSpan(notClause);
+							counter--;
+						} 
+						if (!notClause.next()) hasMoreNotClause = false;
+					}									
+					
+					// if a notClause is between max and firstspan.start, 
+					// then maxPos = last NotClause pos -1
+					generateCandidates(min, maxPos, direction);
+					
+					if (lastNotClause != null)
+						while ((hasMoreSpans = firstSpans.next())
+								// the next notClause is not in between max and firstspan.start()
+								&& notClause.start() > firstSpans.start()
+								// the last notClause is in between max and firstspan.start()
+								&& lastNotClause.getStart() < firstSpans.start()								
+								&& lastNotClause.getStart() >= firstSpans.start() - max
+							){
+														
+							maxPos = firstSpans.start() - lastNotClause.getStart() -1;
+							generateCandidates(min, maxPos, direction);
+						}
+					else hasMoreSpans = firstSpans.next();
+				}
+				
+				else { // right
+					
+					int expansionEnd = firstSpans.end() + max;
+					int maxPos = max;
+					boolean isFound = false;
+					
+					CandidateSpan firstNotClause = null;
+					//System.out.println("main start:"+firstSpans.start());
+					while (hasMoreNotClause && notClause.start() < expansionEnd){
+						// between firstspan.end() and expansionEnd
+						if (!isFound && notClause.start() >= firstSpans.start()){							
+							maxPos = notClause.start() - firstSpans.start() -1;
+							firstNotClause = new CandidateSpan(notClause);
+							isFound = true;
+						}						
+						if (!notClause.next()) hasMoreNotClause = false;
+					}
+					// if a notClause is between firstSpan.end and max
+					// then maxPos = the first notClause pos -1 
+					generateCandidates(min, maxPos, direction);
+					
+					if (firstNotClause !=null){
+						while ((hasMoreSpans = firstSpans.next()) 
+								// in between
+								&& firstNotClause.getStart() < firstSpans.end() + max
+								&& firstNotClause.getStart() >= firstSpans.start())								
+							{
+							//System.out.println("first start:"+firstNotClause.getStart()+", main start:"+firstSpans.start());
+							maxPos = firstNotClause.getStart() - firstSpans.start() -1;
+							generateCandidates(min, maxPos, direction);
+						} 
+					}
+					else hasMoreSpans = firstSpans.next();
+				}
+				
+				
+				break;
+			}
+			
+			else if (!notClause.next()) hasMoreNotClause = false;
+		}
+	}
+	
+	private void generateCandidates(int minPos, int maxPos, int direction) 
+			throws IOException {
+		int counter;
+		int start, end;
+				
+		if (direction < 0 ) { // left
+			counter = maxPos;
+			while (counter >= min){
+				start = firstSpans.start() - counter;
+				if (start > -1 ){
+					
+				end = firstSpans.end();
+				//System.out.println(start+","+end);
+				candidateSpans.add(new CandidateSpan(start, end, firstSpans.doc(), 
+						firstSpans.cost(), firstSpans.getPayload()));
+				}
+				counter --;
+			}
+		}
+		else { // right
+			counter = minPos;
+			while(counter <= maxPos){
+				start = firstSpans.start();
+				end = firstSpans.end() + counter;
+				//System.out.println(start+","+end);
+				candidateSpans.add(new CandidateSpan(start, end, firstSpans.doc(), 
+						firstSpans.cost(), firstSpans.getPayload()));
+				counter++;
+			}			
+		}
+	}
+	
+	
+	@Override
+	public boolean skipTo(int target) throws IOException {
+		if (hasMoreSpans && (firstSpans.doc() < target)){
+  			if (!firstSpans.skipTo(target)){
+  				hasMoreSpans = false;
+  				return false;
+  			}
+  		} 		
+  		matchPayload.clear();
+  		return advance();
+	}
+
+	@Override
+	public long cost() {
+		// TODO Auto-generated method stub
+		return 0;
+	}
+}