/*
 * 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.
 */

using System;
using IndexReader = Lucene.Net.Index.IndexReader;

namespace Lucene.Net.Search
{
	
	/// <summary>The abstract base class for queries.
	/// <p>Instantiable subclasses are:
	/// <ul>
	/// <li> {@link TermQuery}
	/// <li> {@link MultiTermQuery}
	/// <li> {@link BooleanQuery}
	/// <li> {@link WildcardQuery}
	/// <li> {@link PhraseQuery}
	/// <li> {@link PrefixQuery}
	/// <li> {@link MultiPhraseQuery}
	/// <li> {@link FuzzyQuery}
	/// <li> {@link RangeQuery}
	/// <li> {@link Lucene.Net.search.spans.SpanQuery}
	/// </ul>
	/// <p>A parser for queries is contained in:
	/// <ul>
	/// <li>{@link Lucene.Net.queryParser.QueryParser QueryParser}
	/// </ul>
	/// </summary>
	[Serializable]
	public abstract class Query : System.ICloneable
	{
		private float boost = 1.0f; // query boost factor
		
		/// <summary>Sets the boost for this query clause to <code>b</code>.  Documents
		/// matching this clause will (in addition to the normal weightings) have
		/// their score multiplied by <code>b</code>.
		/// </summary>
		public virtual void  SetBoost(float b)
		{
			boost = b;
		}
		
		/// <summary>Gets the boost for this clause.  Documents matching
		/// this clause will (in addition to the normal weightings) have their score
		/// multiplied by <code>b</code>.   The boost is 1.0 by default.
		/// </summary>
		public virtual float GetBoost()
		{
			return boost;
		}
		
        /// <summary>Prints a query to a string, with <code>field</code> assumed to be the 
        /// default field and omitted.
        /// <p>The representation used is one that is supposed to be readable
        /// by {@link org.apache.lucene.queryParser.QueryParser QueryParser}. However,
        /// there are the following limitations:
        /// <ul>
        /// <li>If the query was created by the parser, the printed
        /// representation may not be exactly what was parsed. For example,
        /// characters that need to be escaped will be represented without
        /// the required backslash.</li>
        /// <li>Some of the more complicated queries (e.g. span queries)
        /// don't have a representation that can be parsed by QueryParser.</li>
        /// </ul>
        /// </summary>
        public abstract System.String ToString(System.String field);
		
		/// <summary>Prints a query to a string. </summary>
		public override System.String ToString()
		{
			return ToString("");
		}
		
		/// <summary>Expert: Constructs an appropriate Weight implementation for this query.
		/// 
		/// <p>Only implemented by primitive queries, which re-write to themselves.
		/// </summary>
		protected internal virtual Weight CreateWeight(Searcher searcher)
		{
			throw new System.NotSupportedException();
		}
		
		/// <summary>Expert: Constructs and initializes a Weight for a top-level query. </summary>
		public virtual Weight Weight(Searcher searcher)
		{
			Query query = searcher.Rewrite(this);
			Weight weight = query.CreateWeight(searcher);
			float sum = weight.SumOfSquaredWeights();
			float norm = GetSimilarity(searcher).QueryNorm(sum);
			weight.Normalize(norm);
			return weight;
		}
		
		/// <summary>Expert: called to re-write queries into primitive queries. For example,
		/// a PrefixQuery will be rewritten into a BooleanQuery that consists
		/// of TermQuerys.
		/// </summary>
		public virtual Query Rewrite(IndexReader reader)
		{
			return this;
		}
		
		/// <summary>Expert: called when re-writing queries under MultiSearcher.
		/// 
		/// Create a single query suitable for use by all subsearchers (in 1-1
		/// correspondence with queries). This is an optimization of the OR of
		/// all queries. We handle the common optimization cases of equal
		/// queries and overlapping clauses of boolean OR queries (as generated
		/// by MultiTermQuery.rewrite() and RangeQuery.rewrite()).
		/// Be careful overriding this method as queries[0] determines which
		/// method will be called and is not necessarily of the same type as
		/// the other queries.
		/// </summary>
		public virtual Query Combine(Query[] queries)
		{
			System.Collections.Hashtable uniques = new System.Collections.Hashtable();
			for (int i = 0; i < queries.Length; i++)
			{
				Query query = queries[i];
				BooleanClause[] clauses = null;
				// check if we can split the query into clauses
				bool splittable = (query is BooleanQuery);
				if (splittable)
				{
					BooleanQuery bq = (BooleanQuery) query;
					splittable = bq.IsCoordDisabled();
					clauses = bq.GetClauses();
					for (int j = 0; splittable && j < clauses.Length; j++)
					{
						splittable = (clauses[j].GetOccur() == BooleanClause.Occur.SHOULD);
					}
				}
				if (splittable)
				{
					for (int j = 0; j < clauses.Length; j++)
					{
                        Query tmp = clauses[j].GetQuery();
                        if (uniques.Contains(tmp) == false)
                        {
                            uniques.Add(tmp, tmp);
                        }
					}
				}
				else
				{
                    if (uniques.Contains(query) == false)
                    {
                        uniques.Add(query, query);
                    }
				}
			}
			// optimization: if we have just one query, just return it
			if (uniques.Count == 1)
			{
                System.Collections.IDictionaryEnumerator iter = uniques.GetEnumerator();
                iter.MoveNext();
                return iter.Value as Query;
			}
			System.Collections.IDictionaryEnumerator it = uniques.GetEnumerator();
			BooleanQuery result = new BooleanQuery(true);
			while (it.MoveNext())
			{
				result.Add((Query) it.Value, BooleanClause.Occur.SHOULD);
			}
			return result;
		}
		
		/// <summary> Expert: adds all terms occuring in this query to the terms set. Only
		/// works if this query is in its {@link #rewrite rewritten} form.
		/// 
		/// </summary>
		/// <throws>  UnsupportedOperationException if this query is not yet rewritten </throws>
		public virtual void  ExtractTerms(System.Collections.Hashtable terms)
		{
			// needs to be implemented by query subclasses
			throw new System.NotSupportedException();
		}
		
		
		/// <summary>Expert: merges the clauses of a set of BooleanQuery's into a single
		/// BooleanQuery.
		/// 
		/// <p>A utility for use by {@link #Combine(Query[])} implementations.
		/// </summary>
		public static Query MergeBooleanQueries(Query[] queries)
		{
			System.Collections.Hashtable allClauses = new System.Collections.Hashtable();
			for (int i = 0; i < queries.Length; i++)
			{
				BooleanClause[] clauses = ((BooleanQuery) queries[i]).GetClauses();
				for (int j = 0; j < clauses.Length; j++)
				{
					allClauses.Add(clauses[j], clauses[j]);
				}
			}
			
			bool coordDisabled = queries.Length == 0 ? false : ((BooleanQuery) queries[0]).IsCoordDisabled();
			BooleanQuery result = new BooleanQuery(coordDisabled);
            foreach (BooleanClause booleanClause in allClauses.Keys)
            {
                result.Add(booleanClause);
            }
			return result;
		}
		
		/// <summary>Expert: Returns the Similarity implementation to be used for this query.
		/// Subclasses may override this method to specify their own Similarity
		/// implementation, perhaps one that delegates through that of the Searcher.
		/// By default the Searcher's Similarity implementation is returned.
		/// </summary>
		public virtual Similarity GetSimilarity(Searcher searcher)
		{
			return searcher.GetSimilarity();
		}
		
		/// <summary>Returns a clone of this query. </summary>
		public virtual System.Object Clone()
		{
			try
			{
				return (Query) base.MemberwiseClone();
			}
			catch (System.Exception e)
			{
				throw new System.SystemException("Clone not supported: " + e.Message);
			}
		}
	}
}