/*
 * 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 PriorityQueue = Lucene.Net.Util.PriorityQueue;

namespace Lucene.Net.Search
{
	
	/// <summary> Expert: Collects sorted results from Searchable's and collates them.
	/// The elements put into this queue must be of type FieldDoc.
	/// 
	/// <p>Created: Feb 11, 2004 2:04:21 PM
	/// 
	/// </summary>
	/// <author>   Tim Jones (Nacimiento Software)
	/// </author>
	/// <since>   lucene 1.4
	/// </since>
	/// <version>  $Id: FieldDocSortedHitQueue.java 332431 2005-11-11 03:13:10Z yonik $
	/// </version>
	class FieldDocSortedHitQueue : PriorityQueue
	{
		
		// this cannot contain AUTO fields - any AUTO fields should
		// have been resolved by the time this class is used.
		internal volatile SortField[] fields;
		
		// used in the case where the fields are sorted by locale
		// based strings
		internal volatile System.Globalization.CompareInfo[] collators;
		
		
		/// <summary> Creates a hit queue sorted by the given list of fields.</summary>
		/// <param name="fields">Field names, in priority order (highest priority first).
		/// </param>
		/// <param name="size"> The number of hits to retain.  Must be greater than zero.
		/// </param>
		internal FieldDocSortedHitQueue(SortField[] fields, int size)
		{
			this.fields = fields;
			this.collators = HasCollators(fields);
			Initialize(size);
		}
		
		
		/// <summary> Allows redefinition of sort fields if they are <code>null</code>.
		/// This is to handle the case using ParallelMultiSearcher where the
		/// original list contains AUTO and we don't know the actual sort
		/// type until the values come back.  The fields can only be set once.
		/// This method is thread safe.
		/// </summary>
		/// <param name="fields">
		/// </param>
		internal virtual void  SetFields(SortField[] fields)
		{
			lock (this)
			{
				if (this.fields == null)
				{
					this.fields = fields;
					this.collators = HasCollators(fields);
				}
			}
		}
		
		
		/// <summary>Returns the fields being used to sort. </summary>
		internal virtual SortField[] GetFields()
		{
			return fields;
		}
		
		
		/// <summary>Returns an array of collators, possibly <code>null</code>.  The collators
		/// correspond to any SortFields which were given a specific locale.
		/// </summary>
		/// <param name="fields">Array of sort fields.
		/// </param>
		/// <returns> Array, possibly <code>null</code>.
		/// </returns>
		private System.Globalization.CompareInfo[] HasCollators(SortField[] fields)
		{
			if (fields == null)
				return null;
			System.Globalization.CompareInfo[] ret = new System.Globalization.CompareInfo[fields.Length];
			for (int i = 0; i < fields.Length; ++i)
			{
				System.Globalization.CultureInfo locale = fields[i].GetLocale();
				if (locale != null)
					ret[i] = locale.CompareInfo;
			}
			return ret;
		}
		
		
		/// <summary> Returns whether <code>a</code> is less relevant than <code>b</code>.</summary>
		/// <param name="a">ScoreDoc
		/// </param>
		/// <param name="b">ScoreDoc
		/// </param>
		/// <returns> <code>true</code> if document <code>a</code> should be sorted after document <code>b</code>.
		/// </returns>
		public override bool LessThan(System.Object a, System.Object b)
		{
			FieldDoc docA = (FieldDoc) a;
			FieldDoc docB = (FieldDoc) b;
			int n = fields.Length;
			int c = 0;
			for (int i = 0; i < n && c == 0; ++i)
			{
				int type = fields[i].GetType();
				switch (type)
				{
					
					case SortField.SCORE: 
						float r1 = (float) ((System.Single) docA.fields[i]);
						float r2 = (float) ((System.Single) docB.fields[i]);
						if (r1 > r2)
							c = - 1;
						if (r1 < r2)
							c = 1;
						break;
					
					case SortField.DOC: 
					case SortField.INT: 
						int i1 = ((System.Int32) docA.fields[i]);
						int i2 = ((System.Int32) docB.fields[i]);
						if (i1 < i2)
							c = - 1;
						if (i1 > i2)
							c = 1;
						break;
					
					case SortField.STRING: 
						System.String s1 = (System.String) docA.fields[i];
						System.String s2 = (System.String) docB.fields[i];
						// null values need to be sorted first, because of how FieldCache.getStringIndex()
						// works - in that routine, any documents without a value in the given field are
						// put first.  If both are null, the next SortField is used
						if (s1 == null)
							c = (s2 == null)?0:- 1;
						else if (s2 == null)
							c = 1;
						// 
						else if (fields[i].GetLocale() == null)
						{
							c = String.CompareOrdinal(s1, s2);
						}
						else
						{
							//UPGRADE_TODO: The equivalent in .NET for method 'java.text.Collator.compare' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
							c = collators[i].Compare(s1.ToString(), s2.ToString());
						}
						break;
					
					case SortField.FLOAT: 
						//UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Float.floatValue' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
						float f1 = (float) ((System.Single) docA.fields[i]);
						//UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Float.floatValue' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
						float f2 = (float) ((System.Single) docB.fields[i]);
						if (f1 < f2)
							c = - 1;
						if (f1 > f2)
							c = 1;
						break;
					
					case SortField.CUSTOM: 
						c = docA.fields[i].CompareTo(docB.fields[i]);
						break;
					
					case SortField.AUTO: 
						// we cannot handle this - even if we determine the type of object (Float or
						// Integer), we don't necessarily know how to compare them (both SCORE and
						// FLOAT contain floats, but are sorted opposite of each other). Before
						// we get here, each AUTO should have been replaced with its actual value.
						throw new System.SystemException("FieldDocSortedHitQueue cannot use an AUTO SortField");
					
					default: 
						throw new System.SystemException("invalid SortField type: " + type);
					
				}
				if (fields[i].GetReverse())
				{
					c = - c;
				}
			}
			
			// avoid random sort order that could lead to duplicates (bug #31241):
			if (c == 0)
				return docA.doc > docB.doc;
			
			return c > 0;
		}
	}
}