package org.apache.lucene.search;
import java.io.IOException;
import java.util.Map;
import org.apache.lucene.search.TermAutomatonQuery.EnumAndScorer;
import org.apache.lucene.search.TermAutomatonQuery.TermAutomatonWeight;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.PriorityQueue;
import org.apache.lucene.util.RamUsageEstimator;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.RunAutomaton;
class TermAutomatonScorer extends Scorer {
private final EnumAndScorer[] subs;
private final EnumAndScorer[] subsOnDoc;
private final PriorityQueue<EnumAndScorer> docIDQueue;
private final PriorityQueue<EnumAndScorer> posQueue;
private final RunAutomaton runAutomaton;
private final Map<Integer,BytesRef> idToTerm;
private PosState[] positions;
int posShift;
private final int anyTermID;
private final LeafSimScorer docScorer;
private int numSubsOnDoc;
private final long cost;
private int docID = -1;
private int freq;
public TermAutomatonScorer(TermAutomatonWeight weight, EnumAndScorer[] subs, int anyTermID, Map<Integer,BytesRef> idToTerm, LeafSimScorer docScorer) throws IOException {
super(weight);
this.runAutomaton = new TermRunAutomaton(weight.automaton, subs.length);
this.docScorer = docScorer;
this.idToTerm = idToTerm;
this.subs = subs;
this.docIDQueue = new DocIDQueue(subs.length);
this.posQueue = new PositionQueue(subs.length);
this.anyTermID = anyTermID;
this.subsOnDoc = new EnumAndScorer[subs.length];
this.positions = new PosState[4];
for(int i=0;i<this.positions.length;i++) {
this.positions[i] = new PosState();
}
long cost = 0;
for(EnumAndScorer sub : subs) {
if (sub != null) {
cost += sub.posEnum.cost();
subsOnDoc[numSubsOnDoc++] = sub;
}
}
this.cost = cost;
}
private static class DocIDQueue extends PriorityQueue<EnumAndScorer> {
public DocIDQueue(int maxSize) {
super(maxSize);
}
@Override
protected boolean lessThan(EnumAndScorer a, EnumAndScorer b) {
return a.posEnum.docID() < b.posEnum.docID();
}
}
private static class PositionQueue extends PriorityQueue<EnumAndScorer> {
public PositionQueue(int maxSize) {
super(maxSize);
}
@Override
protected boolean lessThan(EnumAndScorer a, EnumAndScorer b) {
return a.pos < b.pos;
}
}
private void popCurrentDoc() {
assert numSubsOnDoc == 0;
assert docIDQueue.size() > 0;
subsOnDoc[numSubsOnDoc++] = docIDQueue.pop();
docID = subsOnDoc[0].posEnum.docID();
while (docIDQueue.size() > 0 && docIDQueue.top().posEnum.docID() == docID) {
subsOnDoc[numSubsOnDoc++] = docIDQueue.pop();
}
}
private void pushCurrentDoc() {
for(int i=0;i<numSubsOnDoc;i++) {
docIDQueue.add(subsOnDoc[i]);
}
numSubsOnDoc = 0;
}
@Override
public DocIdSetIterator iterator() {
return new DocIdSetIterator() {
@Override
public int docID() {
return docID;
}
@Override
public long cost() {
return cost;
}
@Override
public int nextDoc() throws IOException {
for(int i=0;i<numSubsOnDoc;i++) {
EnumAndScorer sub = subsOnDoc[i];
if (sub.posEnum.nextDoc() != NO_MORE_DOCS) {
sub.posLeft = sub.posEnum.freq()-1;
sub.pos = sub.posEnum.nextPosition();
}
}
pushCurrentDoc();
return doNext();
}
@Override
public int advance(int target) throws IOException {
if (docIDQueue.size() > 0) {
EnumAndScorer top = docIDQueue.top();
while (top.posEnum.docID() < target) {
if (top.posEnum.advance(target) != NO_MORE_DOCS) {
top.posLeft = top.posEnum.freq()-1;
top.pos = top.posEnum.nextPosition();
}
top = docIDQueue.updateTop();
}
}
for(int i=0;i<numSubsOnDoc;i++) {
EnumAndScorer sub = subsOnDoc[i];
if (sub.posEnum.advance(target) != NO_MORE_DOCS) {
sub.posLeft = sub.posEnum.freq()-1;
sub.pos = sub.posEnum.nextPosition();
}
}
pushCurrentDoc();
return doNext();
}
private int doNext() throws IOException {
assert numSubsOnDoc == 0;
assert docIDQueue.top().posEnum.docID() > docID;
while (true) {
popCurrentDoc();
if (docID == NO_MORE_DOCS) {
return docID;
}
countMatches();
if (freq > 0) {
return docID;
}
for(int i=0;i<numSubsOnDoc;i++) {
EnumAndScorer sub = subsOnDoc[i];
if (sub.posEnum.nextDoc() != NO_MORE_DOCS) {
sub.posLeft = sub.posEnum.freq()-1;
sub.pos = sub.posEnum.nextPosition();
}
}
pushCurrentDoc();
}
}
};
}
private PosState getPosition(int pos) {
return positions[pos-posShift];
}
private void shift(int pos) {
int limit = pos-posShift;
for(int i=0;i<limit;i++) {
positions[i].count = 0;
}
posShift = pos;
}
private void countMatches() throws IOException {
freq = 0;
for(int i=0;i<numSubsOnDoc;i++) {
posQueue.add(subsOnDoc[i]);
}
int lastPos = -1;
posShift = -1;
while (posQueue.size() != 0) {
EnumAndScorer sub = posQueue.pop();
final int pos = sub.pos;
if (posShift == -1) {
posShift = pos;
}
if (pos+1-posShift >= positions.length) {
PosState[] newPositions = new PosState[ArrayUtil.oversize(pos+1-posShift, RamUsageEstimator.NUM_BYTES_OBJECT_REF)];
System.arraycopy(positions, 0, newPositions, 0, positions.length);
for(int i=positions.length;i<newPositions.length;i++) {
newPositions[i] = new PosState();
}
positions = newPositions;
}
PosState posState;
PosState nextPosState;
if (lastPos != -1) {
if (anyTermID != -1) {
int startLastPos = lastPos;
while (lastPos < pos) {
posState = getPosition(lastPos);
if (posState.count == 0 && lastPos > startLastPos) {
lastPos = pos;
break;
}
nextPosState = getPosition(lastPos+1);
for(int i=0;i<posState.count;i++) {
int state = runAutomaton.step(posState.states[i], anyTermID);
if (state != -1) {
nextPosState.add(state);
}
}
lastPos++;
}
}
}
posState = getPosition(pos);
nextPosState = getPosition(pos+1);
if (posState.count == 0 && nextPosState.count == 0) {
shift(pos);
posState = getPosition(pos);
nextPosState = getPosition(pos+1);
}
for(int i=0;i<posState.count;i++) {
int state = runAutomaton.step(posState.states[i], sub.termID);
if (state != -1) {
nextPosState.add(state);
if (runAutomaton.isAccept(state)) {
freq++;
}
}
}
int state = runAutomaton.step(0, sub.termID);
if (state != -1) {
nextPosState.add(state);
if (runAutomaton.isAccept(state)) {
freq++;
}
}
if (sub.posLeft > 0) {
sub.pos = sub.posEnum.nextPosition();
sub.posLeft--;
posQueue.add(sub);
}
lastPos = pos;
}
int limit = lastPos+1-posShift;
for(int i=0;i<=limit;i++) {
positions[i].count = 0;
}
}
@Override
public String toString() {
return "TermAutomatonScorer(" + weight + ")";
}
@Override
public int docID() {
return docID;
}
@Override
public float score() throws IOException {
return docScorer.score(docID, freq);
}
@Override
public float getMaxScore(int upTo) throws IOException {
return docScorer.getSimScorer().score(Float.MAX_VALUE, 1L);
}
static class TermRunAutomaton extends RunAutomaton {
public TermRunAutomaton(Automaton a, int termCount) {
super(a, termCount);
}
}
private static class PosState {
int[] states = new int[2];
int count;
public void add(int state) {
if (states.length == count) {
states = ArrayUtil.grow(states);
}
states[count++] = state;
}
}
}