package org.apache.lucene.search;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

import org.apache.lucene.util.Bits;

/** Internal document-at-a-time scorers used to deal with stupid coord() computation */
class BooleanTopLevelScorers {
  
  /** 
   * Used when there is more than one scorer in a query, but a segment
   * only had one non-null scorer. This just wraps that scorer directly
   * to factor in coord().
   */
  static class BoostedScorer extends FilterScorer {
    final float boost;
    
    BoostedScorer(Scorer in, float boost) {
      super(in);
      this.boost = boost;
    }

    @Override
    public float score() throws IOException {
      return in.score() * boost;
    }

    @Override
    public Collection<ChildScorer> getChildren() {
      return Collections.singleton(new ChildScorer(in, "BOOSTED"));
    }
  }

  /**
   * Used when there is more than one scorer in a query, but a segment
   * only had one non-null scorer.
   */
  static class BoostedBulkScorer extends BulkScorer {

    final BulkScorer in;
    final float boost;

    BoostedBulkScorer(BulkScorer scorer, float boost) {
      this.in = scorer;
      this.boost = boost;
    }

    @Override
    public int score(LeafCollector collector, Bits acceptDocs, int min, int max) throws IOException {
      final LeafCollector wrapped = new FilterLeafCollector(collector) {
        @Override
        public void setScorer(Scorer scorer) throws IOException {
          super.setScorer(new BoostedScorer(scorer, boost));
        }
      };
      return in.score(wrapped, acceptDocs, min, max);
    }

    @Override
    public long cost() {
      return in.cost();
    }

  }

  /** 
   * Used when there are both mandatory and optional clauses, but minShouldMatch
   * dictates that some of the optional clauses must match. The query is a conjunction,
   * but must compute coord based on how many optional subscorers matched (freq).
   */
  static class CoordinatingConjunctionScorer extends ConjunctionScorer {
    private final float coords[];
    private final int reqCount;
    private final Scorer req;
    private final Scorer opt;
    
    CoordinatingConjunctionScorer(Weight weight, float coords[], Scorer req, int reqCount, Scorer opt) {
      super(weight, Arrays.asList(req, opt), Arrays.asList(req, opt));
      this.coords = coords;
      this.req = req;
      this.reqCount = reqCount;
      this.opt = opt;
    }
    
    @Override
    public float score() throws IOException {
      return (req.score() + opt.score()) * coords[reqCount + opt.freq()];
    }
  }
  
  /** 
   * Used when there are mandatory clauses with one optional clause: we compute
   * coord based on whether the optional clause matched or not.
   */
  static class ReqSingleOptScorer extends ReqOptSumScorer {
    // coord factor if just the required part matches
    private final float coordReq;
    // coord factor if both required and optional part matches 
    private final float coordBoth;
    
    public ReqSingleOptScorer(Scorer reqScorer, Scorer optScorer, float coordReq, float coordBoth) {
      super(reqScorer, optScorer);
      this.coordReq = coordReq;
      this.coordBoth = coordBoth;
    }
    
    @Override
    public float score() throws IOException {
      int curDoc = reqScorer.docID();
      float reqScore = reqScorer.score();
      if (optScorer == null) {
        return reqScore * coordReq;
      }
      
      int optScorerDoc = optScorer.docID();
      if (optScorerDoc < curDoc && (optScorerDoc = optScorer.advance(curDoc)) == NO_MORE_DOCS) {
        optScorer = null;
        return reqScore * coordReq;
      }
      
      return optScorerDoc == curDoc ? (reqScore + optScorer.score()) * coordBoth : reqScore * coordReq;
    }
  }

  /** 
   * Used when there are mandatory clauses with optional clauses: we compute
   * coord based on how many optional subscorers matched (freq).
   */
  static class ReqMultiOptScorer extends ReqOptSumScorer {
    private final int requiredCount;
    private final float coords[];
    
    public ReqMultiOptScorer(Scorer reqScorer, Scorer optScorer, int requiredCount, float coords[]) {
      super(reqScorer, optScorer);
      this.requiredCount = requiredCount;
      this.coords = coords;
    }
    
    @Override
    public float score() throws IOException {
      int curDoc = reqScorer.docID();
      float reqScore = reqScorer.score();
      if (optScorer == null) {
        return reqScore * coords[requiredCount];
      }
      
      int optScorerDoc = optScorer.docID();
      if (optScorerDoc < curDoc && (optScorerDoc = optScorer.advance(curDoc)) == NO_MORE_DOCS) {
        optScorer = null;
        return reqScore * coords[requiredCount];
      }
      
      return optScorerDoc == curDoc ? (reqScore + optScorer.score()) * coords[requiredCount + optScorer.freq()] : reqScore * coords[requiredCount];
    }
  }
}
