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 org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.DefaultSimilarity;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.Similarity;
/**
 * The BoostingQuery class can be used to effectively demote results that match a given query. 
 * Unlike the "NOT" clause, this still selects documents that contain undesirable terms, 
 * but reduces their overall score:
 *
 *     Query balancedQuery = new BoostingQuery(positiveQuery, negativeQuery, 0.01f);
 * In this scenario the positiveQuery contains the mandatory, desirable criteria which is used to 
 * select all matching documents, and the negativeQuery contains the undesirable elements which 
 * are simply used to lessen the scores. Documents that match the negativeQuery have their score 
 * multiplied by the supplied "boost" parameter, so this should be less than 1 to achieve a 
 * demoting effect
 * 
 * This code was originally made available here: [WWW] http://marc.theaimsgroup.com/?l=lucene-user&m=108058407130459&w=2
 * and is documented here: http://wiki.apache.org/lucene-java/CommunityContributions
 */
public class BoostingQuery extends Query {
    private float boost;                            // the amount to boost by
    private Query match;                            // query to match
    private Query context;                          // boost when matches too

    public BoostingQuery(Query match, Query context, float boost) {
      this.match = match;
      this.context = (Query)context.clone();        // clone before boost
      this.boost = boost;

      this.context.setBoost(0.0f);                      // ignore context-only matches
    }

    @Override
    public Query rewrite(IndexReader reader) throws IOException {
      BooleanQuery result = new BooleanQuery() {

        @Override
        public Similarity getSimilarity(Searcher searcher) {
          return new DefaultSimilarity() {

            @Override
            public float coord(int overlap, int max) {
              switch (overlap) {

              case 1:                               // matched only one clause
                return 1.0f;                        // use the score as-is

              case 2:                               // matched both clauses
                return boost;                       // multiply by boost

              default:
                return 0.0f;
                
              }
            }
          };
        }
      };

      result.add(match, BooleanClause.Occur.MUST);
      result.add(context, BooleanClause.Occur.SHOULD);

      return result;
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + Float.floatToIntBits(boost);
      result = prime * result + ((context == null) ? 0 : context.hashCode());
      result = prime * result + ((match == null) ? 0 : match.hashCode());
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;
      if (obj == null)
        return false;
      if (getClass() != obj.getClass())
        return false;
      BoostingQuery other = (BoostingQuery) obj;
      if (Float.floatToIntBits(boost) != Float.floatToIntBits(other.boost))
        return false;
      if (context == null) {
        if (other.context != null)
          return false;
      } else if (!context.equals(other.context))
        return false;
      if (match == null) {
        if (other.match != null)
          return false;
      } else if (!match.equals(other.match))
        return false;
      return true;
    }

    @Override
    public String toString(String field) {
      return match.toString(field) + "/" + context.toString(field);
    }
  }
