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

namespace Lucene.Net.Search
{
	
	/// <summary> Expert: A hit queue for sorting by hits by terms in more than one field.
	/// Uses <code>FieldCache.DEFAULT</code> for maintaining internal term lookup tables.
	/// 
	/// <p>Created: Dec 8, 2003 12:56:03 PM
	/// 
	/// </summary>
	/// <author>   Tim Jones (Nacimiento Software)
	/// </author>
	/// <since>   lucene 1.4
	/// </since>
	/// <version>  $Id: FieldSortedHitQueue.java 354819 2005-12-07 17:48:37Z yonik $
	/// </version>
	/// <seealso cref="Searcher.Search(Query,Filter,int,Sort)">
	/// </seealso>
	/// <seealso cref="FieldCache">
	/// </seealso>
	public class FieldSortedHitQueue : PriorityQueue
	{
		internal static void Close(IndexReader reader) 
		{ 
			lock (Comparators.SyncRoot) 
			{ 
				System.Collections.Hashtable readerCache = (System.Collections.Hashtable) Comparators[reader]; 
				if (readerCache != null) 
				{ 
					readerCache.Clear(); 
					readerCache = null;
				} 

				Comparators.Remove(reader);
			} 
		}

		private class AnonymousClassScoreDocComparator : ScoreDocComparator
		{
			public AnonymousClassScoreDocComparator(int[] fieldOrder)
			{
				InitBlock(fieldOrder);
			}
			private void  InitBlock(int[] fieldOrder)
			{
				this.fieldOrder = fieldOrder;
			}

            private int[] fieldOrder;
			
			public int Compare(ScoreDoc i, ScoreDoc j)
			{
				int fi = fieldOrder[i.doc];
				int fj = fieldOrder[j.doc];
				if (fi < fj)
					return - 1;
				if (fi > fj)
					return 1;
				return 0;
			}
			
			public virtual System.IComparable SortValue(ScoreDoc i)
			{
				return (System.Int32) fieldOrder[i.doc];
			}
			
			public virtual int SortType()
			{
				return SortField.INT;
			}
		}
		private class AnonymousClassScoreDocComparator1 : ScoreDocComparator
		{
			public AnonymousClassScoreDocComparator1(float[] fieldOrder)
			{
				InitBlock(fieldOrder);
			}
			private void  InitBlock(float[] fieldOrder)
			{
				this.fieldOrder = fieldOrder;
			}
			private float[] fieldOrder;
			
			public int Compare(ScoreDoc i, ScoreDoc j)
			{
				float fi = fieldOrder[i.doc];
				float fj = fieldOrder[j.doc];
				if (fi < fj)
					return - 1;
				if (fi > fj)
					return 1;
				return 0;
			}
			
			public virtual System.IComparable SortValue(ScoreDoc i)
			{
				return (float) fieldOrder[i.doc];
			}
			
			public virtual int SortType()
			{
				return SortField.FLOAT;
			}
		}
		private class AnonymousClassScoreDocComparator2 : ScoreDocComparator
		{
			public AnonymousClassScoreDocComparator2(Lucene.Net.Search.StringIndex index)
			{
				InitBlock(index);
			}
			private void  InitBlock(Lucene.Net.Search.StringIndex index)
			{
				this.index = index;
			}
			private Lucene.Net.Search.StringIndex index;
			
			public int Compare(ScoreDoc i, ScoreDoc j)
			{
				int fi = index.order[i.doc];
				int fj = index.order[j.doc];
				if (fi < fj)
					return - 1;
				if (fi > fj)
					return 1;
				return 0;
			}
			
			public virtual System.IComparable SortValue(ScoreDoc i)
			{
				return index.lookup[index.order[i.doc]];
			}
			
			public virtual int SortType()
			{
				return SortField.STRING;
			}
		}
		private class AnonymousClassScoreDocComparator3 : ScoreDocComparator
		{
			public AnonymousClassScoreDocComparator3(System.Globalization.CompareInfo collator, System.String[] index)
			{
				InitBlock(collator, index);
			}
			private void  InitBlock(System.Globalization.CompareInfo collator, System.String[] index)
			{
				this.collator = collator;
				this.index = index;
			}
			private System.Globalization.CompareInfo collator;
			private System.String[] index;
			
			public int Compare(ScoreDoc i, ScoreDoc j)
			{
				return collator.Compare(index[i.doc].ToString(), index[j.doc].ToString());
			}
			
			public virtual System.IComparable SortValue(ScoreDoc i)
			{
				return index[i.doc];
			}
			
			public virtual int SortType()
			{
				return SortField.STRING;
			}
		}
		
		/// <summary> Creates a hit queue sorted by the given list of fields.</summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fields">Field names, in priority order (highest priority first).  Cannot be <code>null</code> or empty.
		/// </param>
		/// <param name="size"> The number of hits to retain.  Must be greater than zero.
		/// </param>
		/// <throws>  IOException </throws>
		public FieldSortedHitQueue(IndexReader reader, SortField[] fields, int size)
		{
			int n = fields.Length;
			comparators = new ScoreDocComparator[n];
			this.fields = new SortField[n];
			for (int i = 0; i < n; ++i)
			{
				System.String fieldname = fields[i].GetField();
				comparators[i] = GetCachedComparator(reader, fieldname, fields[i].GetType(), fields[i].GetLocale(), fields[i].GetFactory());
				
				if (comparators[i].SortType() == SortField.STRING)
				{
					this.fields[i] = new SortField(fieldname, fields[i].GetLocale(), fields[i].GetReverse());
				}
				else
				{
					this.fields[i] = new SortField(fieldname, comparators[i].SortType(), fields[i].GetReverse());
				}
			}
			Initialize(size);
		}
		
		
		/// <summary>Stores a comparator corresponding to each field being sorted by </summary>
		protected internal ScoreDocComparator[] comparators;
		
		/// <summary>Stores the sort criteria being used. </summary>
		protected internal SortField[] fields;
		
		/// <summary>Stores the maximum score value encountered, needed for normalizing. </summary>
		protected internal float maxscore = System.Single.NegativeInfinity;
		
		/// <summary>returns the maximum score encountered by elements inserted via insert()</summary>
		public virtual float GetMaxScore()
		{
			return maxscore;
		}
		
		// The signature of this method takes a FieldDoc in order to avoid
		// the unneeded cast to retrieve the score.
		// inherit javadoc
		public virtual bool Insert(FieldDoc fdoc)
		{
			maxscore = System.Math.Max(maxscore, fdoc.score);
			return base.Insert(fdoc);
		}
		
		// This overrides PriorityQueue.insert() so that insert(FieldDoc) that
		// keeps track of the score isn't accidentally bypassed.  
		// inherit javadoc
		public override bool Insert(System.Object fdoc)
		{
			return Insert((FieldDoc) fdoc);
		}
		
		/// <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)
		{
			ScoreDoc docA = (ScoreDoc) a;
			ScoreDoc docB = (ScoreDoc) b;
			
			// run comparators
			int n = comparators.Length;
			int c = 0;
			for (int i = 0; i < n && c == 0; ++i)
			{
				c = (fields[i].reverse)?comparators[i].Compare(docB, docA):comparators[i].Compare(docA, docB);
			}
			// avoid random sort order that could lead to duplicates (bug #31241):
			if (c == 0)
				return docA.doc > docB.doc;
			return c > 0;
		}
		
		
		/// <summary> Given a FieldDoc object, stores the values used
		/// to sort the given document.  These values are not the raw
		/// values out of the index, but the internal representation
		/// of them.  This is so the given search hit can be collated
		/// by a MultiSearcher with other search hits.
		/// </summary>
		/// <param name="doc"> The FieldDoc to store sort values into.
		/// </param>
		/// <returns>  The same FieldDoc passed in.
		/// </returns>
		/// <seealso cref="Searchable.Search(Weight,Filter,int,Sort)">
		/// </seealso>
		internal virtual FieldDoc FillFields(FieldDoc doc)
		{
			int n = comparators.Length;
			System.IComparable[] fields = new System.IComparable[n];
			for (int i = 0; i < n; ++i)
				fields[i] = comparators[i].SortValue(doc);
			doc.fields = fields;
			//if (maxscore > 1.0f) doc.score /= maxscore;   // normalize scores
			return doc;
		}
		
		
		/// <summary>Returns the SortFields being used by this hit queue. </summary>
		internal virtual SortField[] GetFields()
		{
			return fields;
		}
		
		/// <summary>Internal cache of comparators. Similar to FieldCache, only
		/// caches comparators instead of term values. 
		/// </summary>
		internal static readonly System.Collections.IDictionary Comparators = new System.Collections.Hashtable();
		
		/// <summary>Returns a comparator if it is in the cache. </summary>
		internal static ScoreDocComparator Lookup(IndexReader reader, System.String field, int type, System.Globalization.CultureInfo locale, System.Object factory)
		{
			FieldCacheImpl.Entry entry = (factory != null) ? new FieldCacheImpl.Entry(field, factory) : new FieldCacheImpl.Entry(field, type, locale);
			lock (Comparators.SyncRoot)
			{
				System.Collections.Hashtable readerCache = (System.Collections.Hashtable) Comparators[reader];
				if (readerCache == null)
					return null;
				return (ScoreDocComparator) readerCache[entry];
			}
		}
		
		/// <summary>Stores a comparator into the cache. </summary>
		internal static System.Object Store(IndexReader reader, System.String field, int type, System.Globalization.CultureInfo locale, System.Object factory, System.Object value_Renamed)
		{
			FieldCacheImpl.Entry entry = (factory != null) ? new FieldCacheImpl.Entry(field, factory) : new FieldCacheImpl.Entry(field, type, locale);
			lock (Comparators.SyncRoot)
			{
				System.Collections.Hashtable readerCache = (System.Collections.Hashtable) Comparators[reader];
				if (readerCache == null)
				{
					readerCache = new System.Collections.Hashtable();
					Comparators[reader] = readerCache;
				}
				System.Object tempObject;
				tempObject = readerCache[entry];
				readerCache[entry] = value_Renamed;
				return tempObject;
			}
		}
		
		internal static ScoreDocComparator GetCachedComparator(IndexReader reader, System.String fieldname, int type, System.Globalization.CultureInfo locale, SortComparatorSource factory)
		{
			if (type == SortField.DOC)
				return Lucene.Net.Search.ScoreDocComparator_Fields.INDEXORDER;
			if (type == SortField.SCORE)
				return Lucene.Net.Search.ScoreDocComparator_Fields.RELEVANCE;
			ScoreDocComparator comparator = Lookup(reader, fieldname, type, locale, factory);
			if (comparator == null)
			{
				switch (type)
				{
					
					case SortField.AUTO: 
						comparator = ComparatorAuto(reader, fieldname);
						break;
					
					case SortField.INT: 
						comparator = ComparatorInt(reader, fieldname);
						break;
					
					case SortField.FLOAT: 
						comparator = ComparatorFloat(reader, fieldname);
						break;
					
					case SortField.STRING: 
						if (locale != null)
							comparator = ComparatorStringLocale(reader, fieldname, locale);
						else
							comparator = ComparatorString(reader, fieldname);
						break;
					
					case SortField.CUSTOM: 
						comparator = factory.NewComparator(reader, fieldname);
						break;
					
					default: 
						throw new System.SystemException("unknown field type: " + type);
					
				}
				Store(reader, fieldname, type, locale, factory, comparator);
			}
			return comparator;
		}
		
		/// <summary> Returns a comparator for sorting hits according to a field containing integers.</summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fieldname"> Field containg integer values.
		/// </param>
		/// <returns>  Comparator for sorting hits.
		/// </returns>
		/// <throws>  IOException If an error occurs reading the index. </throws>
		internal static ScoreDocComparator ComparatorInt(IndexReader reader, System.String fieldname)
		{
			System.String field = String.Intern(fieldname);
			int[] fieldOrder = Lucene.Net.Search.FieldCache_Fields.DEFAULT.GetInts(reader, field);
			return new AnonymousClassScoreDocComparator(fieldOrder);
		}
		
		/// <summary> Returns a comparator for sorting hits according to a field containing floats.</summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fieldname"> Field containg float values.
		/// </param>
		/// <returns>  Comparator for sorting hits.
		/// </returns>
		/// <throws>  IOException If an error occurs reading the index. </throws>
		internal static ScoreDocComparator ComparatorFloat(IndexReader reader, System.String fieldname)
		{
			System.String field = String.Intern(fieldname);
			float[] fieldOrder = Lucene.Net.Search.FieldCache_Fields.DEFAULT.GetFloats(reader, field);
			return new AnonymousClassScoreDocComparator1(fieldOrder);
		}
		
		/// <summary> Returns a comparator for sorting hits according to a field containing strings.</summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fieldname"> Field containg string values.
		/// </param>
		/// <returns>  Comparator for sorting hits.
		/// </returns>
		/// <throws>  IOException If an error occurs reading the index. </throws>
		internal static ScoreDocComparator ComparatorString(IndexReader reader, System.String fieldname)
		{
			System.String field = String.Intern(fieldname);
			Lucene.Net.Search.StringIndex index = Lucene.Net.Search.FieldCache_Fields.DEFAULT.GetStringIndex(reader, field);
			return new AnonymousClassScoreDocComparator2(index);
		}
		
		/// <summary> Returns a comparator for sorting hits according to a field containing strings.</summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fieldname"> Field containg string values.
		/// </param>
		/// <returns>  Comparator for sorting hits.
		/// </returns>
		/// <throws>  IOException If an error occurs reading the index. </throws>
		internal static ScoreDocComparator ComparatorStringLocale(IndexReader reader, System.String fieldname, System.Globalization.CultureInfo locale)
		{
			System.Globalization.CompareInfo collator = locale.CompareInfo;
			System.String field = String.Intern(fieldname);
			System.String[] index = Lucene.Net.Search.FieldCache_Fields.DEFAULT.GetStrings(reader, field);
			return new AnonymousClassScoreDocComparator3(collator, index);
		}
		
		/// <summary> Returns a comparator for sorting hits according to values in the given field.
		/// The terms in the field are looked at to determine whether they contain integers,
		/// floats or strings.  Once the type is determined, one of the other static methods
		/// in this class is called to get the comparator.
		/// </summary>
		/// <param name="reader"> Index to use.
		/// </param>
		/// <param name="fieldname"> Field containg values.
		/// </param>
		/// <returns>  Comparator for sorting hits.
		/// </returns>
		/// <throws>  IOException If an error occurs reading the index. </throws>
		internal static ScoreDocComparator ComparatorAuto(IndexReader reader, System.String fieldname)
		{
			System.String field = String.Intern(fieldname);
			System.Object lookupArray = Lucene.Net.Search.FieldCache_Fields.DEFAULT.GetAuto(reader, field);
			if (lookupArray is Lucene.Net.Search.StringIndex)
			{
				return ComparatorString(reader, field);
			}
			else if (lookupArray is int[])
			{
				return ComparatorInt(reader, field);
			}
			else if (lookupArray is float[])
			{
				return ComparatorFloat(reader, field);
			}
			else if (lookupArray is System.String[])
			{
				return ComparatorString(reader, field);
			}
			else
			{
				throw new System.SystemException("unknown data type in field '" + field + "'");
			}
		}
	}
}