/* 
 * 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 Lucene.Net.Documents;
using Lucene.Net.Support;
using Document = Lucene.Net.Documents.Document;
using ArrayUtil = Lucene.Net.Util.ArrayUtil;

namespace Lucene.Net.Index
{
	
	/// <summary> Gathers all Fieldables for a document under the same
	/// name, updates FieldInfos, and calls per-field consumers
	/// to process field by field.
	/// 
	/// Currently, only a single thread visits the fields,
	/// sequentially, for processing.
	/// </summary>
	
	sealed class DocFieldProcessorPerThread:DocConsumerPerThread
	{
		private void  InitBlock()
		{
			docFreeList = new PerDoc[1];
		}
		
		internal float docBoost;
		internal int fieldGen;
		internal DocFieldProcessor docFieldProcessor;
		internal FieldInfos fieldInfos;
		internal DocFieldConsumerPerThread consumer;
		
		// Holds all fields seen in current doc
		internal DocFieldProcessorPerField[] fields = new DocFieldProcessorPerField[1];
		internal int fieldCount;
		
		// Hash table for all fields ever seen
		internal DocFieldProcessorPerField[] fieldHash = new DocFieldProcessorPerField[2];
		internal int hashMask = 1;
		internal int totalFieldCount;
		
		internal StoredFieldsWriterPerThread fieldsWriter;
		
		internal DocumentsWriter.DocState docState;
		
		public DocFieldProcessorPerThread(DocumentsWriterThreadState threadState, DocFieldProcessor docFieldProcessor)
		{
			InitBlock();
			this.docState = threadState.docState;
			this.docFieldProcessor = docFieldProcessor;
			this.fieldInfos = docFieldProcessor.fieldInfos;
			this.consumer = docFieldProcessor.consumer.AddThread(this);
			fieldsWriter = docFieldProcessor.fieldsWriter.AddThread(docState);
		}
		
		public override void  Abort()
		{
			for (int i = 0; i < fieldHash.Length; i++)
			{
				DocFieldProcessorPerField field = fieldHash[i];
				while (field != null)
				{
					DocFieldProcessorPerField next = field.next;
					field.Abort();
					field = next;
				}
			}
			fieldsWriter.Abort();
			consumer.Abort();
		}
		
		public System.Collections.Generic.ICollection<DocFieldConsumerPerField> Fields()
		{
		    System.Collections.Generic.ICollection<DocFieldConsumerPerField> fields =
		        new System.Collections.Generic.HashSet<DocFieldConsumerPerField>();
			for (int i = 0; i < fieldHash.Length; i++)
			{
				DocFieldProcessorPerField field = fieldHash[i];
				while (field != null)
				{
					fields.Add(field.consumer);
					field = field.next;
				}
			}
			System.Diagnostics.Debug.Assert(fields.Count == totalFieldCount);
			return fields;
		}
		
		/// <summary>If there are fields we've seen but did not see again
		/// in the last run, then free them up. 
		/// </summary>
		
		internal void  TrimFields(SegmentWriteState state)
		{
			
			for (int i = 0; i < fieldHash.Length; i++)
			{
				DocFieldProcessorPerField perField = fieldHash[i];
				DocFieldProcessorPerField lastPerField = null;
				
				while (perField != null)
				{
					
					if (perField.lastGen == - 1)
					{
						
						// This field was not seen since the previous
						// flush, so, free up its resources now
						
						// Unhash
						if (lastPerField == null)
							fieldHash[i] = perField.next;
						else
							lastPerField.next = perField.next;
						
						if (state.docWriter.infoStream != null)
							state.docWriter.infoStream.WriteLine("  purge field=" + perField.fieldInfo.name);
						
						totalFieldCount--;
					}
					else
					{
						// Reset
						perField.lastGen = - 1;
						lastPerField = perField;
					}
					
					perField = perField.next;
				}
			}
		}
		
		private void  Rehash()
		{
			int newHashSize = (fieldHash.Length * 2);
			System.Diagnostics.Debug.Assert(newHashSize > fieldHash.Length);
			
			DocFieldProcessorPerField[] newHashArray = new DocFieldProcessorPerField[newHashSize];
			
			// Rehash
			int newHashMask = newHashSize - 1;
			for (int j = 0; j < fieldHash.Length; j++)
			{
				DocFieldProcessorPerField fp0 = fieldHash[j];
				while (fp0 != null)
				{
					int hashPos2 = fp0.fieldInfo.name.GetHashCode() & newHashMask;
					DocFieldProcessorPerField nextFP0 = fp0.next;
					fp0.next = newHashArray[hashPos2];
					newHashArray[hashPos2] = fp0;
					fp0 = nextFP0;
				}
			}
			
			fieldHash = newHashArray;
			hashMask = newHashMask;
		}
		
		public override DocumentsWriter.DocWriter ProcessDocument()
		{
			
			consumer.StartDocument();
			fieldsWriter.StartDocument();
			
			Document doc = docState.doc;
			
			System.Diagnostics.Debug.Assert(docFieldProcessor.docWriter.writer.TestPoint("DocumentsWriter.ThreadState.init start"));
			
			fieldCount = 0;
			
			int thisFieldGen = fieldGen++;
			
			System.Collections.Generic.IList<IFieldable> docFields = doc.GetFields();
			int numDocFields = docFields.Count;
			
			// Absorb any new fields first seen in this document.
			// Also absorb any changes to fields we had already
			// seen before (eg suddenly turning on norms or
			// vectors, etc.):
			
			for (int i = 0; i < numDocFields; i++)
			{
				IFieldable field = docFields[i];
				System.String fieldName = field.Name;
				
				// Make sure we have a PerField allocated
				int hashPos = fieldName.GetHashCode() & hashMask;
				DocFieldProcessorPerField fp = fieldHash[hashPos];
				while (fp != null && !fp.fieldInfo.name.Equals(fieldName))
					fp = fp.next;

                if (fp == null)
                {

                    // TODO FI: we need to genericize the "flags" that a
                    // field holds, and, how these flags are merged; it
                    // needs to be more "pluggable" such that if I want
                    // to have a new "thing" my Fields can do, I can
                    // easily add it
                    FieldInfo fi = fieldInfos.Add(fieldName, field.IsIndexed, field.IsTermVectorStored,
                                                  field.IsStorePositionWithTermVector, field.IsStoreOffsetWithTermVector,
                                                  field.OmitNorms, false, field.OmitTermFreqAndPositions);

                    fp = new DocFieldProcessorPerField(this, fi);
                    fp.next = fieldHash[hashPos];
                    fieldHash[hashPos] = fp;
                    totalFieldCount++;

                    if (totalFieldCount >= fieldHash.Length / 2)
                        Rehash();
                }
                else
                {
                    fp.fieldInfo.Update(field.IsIndexed, field.IsTermVectorStored,
                                        field.IsStorePositionWithTermVector, field.IsStoreOffsetWithTermVector,
                                        field.OmitNorms, false, field.OmitTermFreqAndPositions);
                }

			    if (thisFieldGen != fp.lastGen)
				{
					
					// First time we're seeing this field for this doc
					fp.fieldCount = 0;
					
					if (fieldCount == fields.Length)
					{
						int newSize = fields.Length * 2;
						DocFieldProcessorPerField[] newArray = new DocFieldProcessorPerField[newSize];
						Array.Copy(fields, 0, newArray, 0, fieldCount);
						fields = newArray;
					}
					
					fields[fieldCount++] = fp;
					fp.lastGen = thisFieldGen;
				}
				
				if (fp.fieldCount == fp.fields.Length)
				{
					IFieldable[] newArray = new IFieldable[fp.fields.Length * 2];
					Array.Copy(fp.fields, 0, newArray, 0, fp.fieldCount);
					fp.fields = newArray;
				}
				
				fp.fields[fp.fieldCount++] = field;
				if (field.IsStored)
				{
					fieldsWriter.AddField(field, fp.fieldInfo);
				}
			}
			
			// If we are writing vectors then we must visit
			// fields in sorted order so they are written in
			// sorted order.  TODO: we actually only need to
			// sort the subset of fields that have vectors
			// enabled; we could save [small amount of] CPU
			// here.
			QuickSort(fields, 0, fieldCount - 1);
			
			for (int i = 0; i < fieldCount; i++)
				fields[i].consumer.ProcessFields(fields[i].fields, fields[i].fieldCount);

            if (docState.maxTermPrefix != null && docState.infoStream != null)
            {
                docState.infoStream.WriteLine("WARNING: document contains at least one immense term (longer than the max length " + DocumentsWriter.MAX_TERM_LENGTH + "), all of which were skipped.  Please correct the analyzer to not produce such terms.  The prefix of the first immense term is: '" + docState.maxTermPrefix + "...'");
                docState.maxTermPrefix = null;
            }
			
			DocumentsWriter.DocWriter one = fieldsWriter.FinishDocument();
			DocumentsWriter.DocWriter two = consumer.FinishDocument();
			if (one == null)
			{
				return two;
			}
			else if (two == null)
			{
				return one;
			}
			else
			{
				PerDoc both = GetPerDoc();
				both.docID = docState.docID;
				System.Diagnostics.Debug.Assert(one.docID == docState.docID);
				System.Diagnostics.Debug.Assert(two.docID == docState.docID);
				both.one = one;
				both.two = two;
				return both;
			}
		}
		
		internal void  QuickSort(DocFieldProcessorPerField[] array, int lo, int hi)
		{
			if (lo >= hi)
				return ;
			else if (hi == 1 + lo)
			{
				if (String.CompareOrdinal(array[lo].fieldInfo.name, array[hi].fieldInfo.name) > 0)
				{
					DocFieldProcessorPerField tmp = array[lo];
					array[lo] = array[hi];
					array[hi] = tmp;
				}
				return ;
			}
			
			int mid = Number.URShift((lo + hi), 1);
			
			if (String.CompareOrdinal(array[lo].fieldInfo.name, array[mid].fieldInfo.name) > 0)
			{
				DocFieldProcessorPerField tmp = array[lo];
				array[lo] = array[mid];
				array[mid] = tmp;
			}
			
			if (String.CompareOrdinal(array[mid].fieldInfo.name, array[hi].fieldInfo.name) > 0)
			{
				DocFieldProcessorPerField tmp = array[mid];
				array[mid] = array[hi];
				array[hi] = tmp;
				
				if (String.CompareOrdinal(array[lo].fieldInfo.name, array[mid].fieldInfo.name) > 0)
				{
					DocFieldProcessorPerField tmp2 = array[lo];
					array[lo] = array[mid];
					array[mid] = tmp2;
				}
			}
			
			int left = lo + 1;
			int right = hi - 1;
			
			if (left >= right)
				return ;
			
			DocFieldProcessorPerField partition = array[mid];
			
			for (; ; )
			{
				while (String.CompareOrdinal(array[right].fieldInfo.name, partition.fieldInfo.name) > 0)
					--right;
				
				while (left < right && String.CompareOrdinal(array[left].fieldInfo.name, partition.fieldInfo.name) <= 0)
					++left;
				
				if (left < right)
				{
					DocFieldProcessorPerField tmp = array[left];
					array[left] = array[right];
					array[right] = tmp;
					--right;
				}
				else
				{
					break;
				}
			}
			
			QuickSort(array, lo, left);
			QuickSort(array, left + 1, hi);
		}
		
		internal PerDoc[] docFreeList;
		internal int freeCount;
		internal int allocCount;
		
		internal PerDoc GetPerDoc()
		{
			lock (this)
			{
				if (freeCount == 0)
				{
					allocCount++;
					if (allocCount > docFreeList.Length)
					{
						// Grow our free list up front to make sure we have
						// enough space to recycle all outstanding PerDoc
						// instances
						System.Diagnostics.Debug.Assert(allocCount == 1 + docFreeList.Length);
						docFreeList = new PerDoc[ArrayUtil.GetNextSize(allocCount)];
					}
					return new PerDoc(this);
				}
				else
					return docFreeList[--freeCount];
			}
		}
		
		internal void  FreePerDoc(PerDoc perDoc)
		{
			lock (this)
			{
				System.Diagnostics.Debug.Assert(freeCount < docFreeList.Length);
				docFreeList[freeCount++] = perDoc;
			}
		}
		
		internal class PerDoc:DocumentsWriter.DocWriter
		{
			public PerDoc(DocFieldProcessorPerThread enclosingInstance)
			{
				InitBlock(enclosingInstance);
			}
			private void  InitBlock(DocFieldProcessorPerThread enclosingInstance)
			{
				this.enclosingInstance = enclosingInstance;
			}
			private DocFieldProcessorPerThread enclosingInstance;
			public DocFieldProcessorPerThread Enclosing_Instance
			{
				get
				{
					return enclosingInstance;
				}
				
			}
			
			internal DocumentsWriter.DocWriter one;
			internal DocumentsWriter.DocWriter two;
			
			public override long SizeInBytes()
			{
				return one.SizeInBytes() + two.SizeInBytes();
			}
			
			public override void  Finish()
			{
				try
				{
					try
					{
						one.Finish();
					}
					finally
					{
						two.Finish();
					}
				}
				finally
				{
					Enclosing_Instance.FreePerDoc(this);
				}
			}
			
			public override void  Abort()
			{
				try
				{
					try
					{
						one.Abort();
					}
					finally
					{
						two.Abort();
					}
				}
				finally
				{
					Enclosing_Instance.FreePerDoc(this);
				}
			}
		}
	}
}