/* 
 * 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 System.Linq;
using Lucene.Net.Index;
using Lucene.Net.Support;
using IndexReader = Lucene.Net.Index.IndexReader;
using ToStringUtils = Lucene.Net.Util.ToStringUtils;
using Query = Lucene.Net.Search.Query;

namespace Lucene.Net.Search.Spans
{
	
	/// <summary>Matches spans which are near one another.  One can specify <i>slop</i>, the
	/// maximum number of intervening unmatched positions, as well as whether
	/// matches are required to be in-order. 
	/// </summary>
	[Serializable]
	public class SpanNearQuery : SpanQuery, System.ICloneable
	{
		protected internal System.Collections.Generic.IList<SpanQuery> clauses;
		protected internal int internalSlop;
		protected internal bool inOrder;
		
		protected internal System.String internalField;
		private readonly bool collectPayloads;
		
		/// <summary>Construct a SpanNearQuery.  Matches spans matching a span from each
		/// clause, with up to <c>slop</c> total unmatched positions between
		/// them.  * When <c>inOrder</c> is true, the spans from each clause
		/// must be * ordered as in <c>clauses</c>. 
		/// </summary>
		public SpanNearQuery(SpanQuery[] clauses, int slop, bool inOrder):this(clauses, slop, inOrder, true)
		{
		}
		
		public SpanNearQuery(SpanQuery[] clauses, int slop, bool inOrder, bool collectPayloads)
		{
			
			// copy clauses array into an ArrayList
			this.clauses = new System.Collections.Generic.List<SpanQuery>(clauses.Length);
			for (int i = 0; i < clauses.Length; i++)
			{
				SpanQuery clause = clauses[i];
				if (i == 0)
				{
					// check field
					internalField = clause.Field;
				}
				else if (!clause.Field.Equals(internalField))
				{
					throw new System.ArgumentException("Clauses must have same field.");
				}
				this.clauses.Add(clause);
			}
			this.collectPayloads = collectPayloads;
			this.internalSlop = slop;
			this.inOrder = inOrder;
		}
		
		/// <summary>Return the clauses whose spans are matched. </summary>
		public virtual SpanQuery[] GetClauses()
		{
            // Return a copy
			return clauses.ToArray();
		}

	    /// <summary>Return the maximum number of intervening unmatched positions permitted.</summary>
	    public virtual int Slop
	    {
	        get { return internalSlop; }
	    }

	    /// <summary>Return true if matches are required to be in-order.</summary>
	    public virtual bool IsInOrder
	    {
	        get { return inOrder; }
	    }

	    public override string Field
	    {
	        get { return internalField; }
	    }

	    public override void  ExtractTerms(System.Collections.Generic.ISet<Term> terms)
		{
            foreach (SpanQuery clause in clauses)
            {
                clause.ExtractTerms(terms);
            }
		}

		public override System.String ToString(System.String field)
		{
			System.Text.StringBuilder buffer = new System.Text.StringBuilder();
			buffer.Append("spanNear([");
			System.Collections.Generic.IEnumerator<SpanQuery> i = clauses.GetEnumerator();
			while (i.MoveNext())
			{
				SpanQuery clause = i.Current;
				buffer.Append(clause.ToString(field));
                buffer.Append(", ");
			}
            if (clauses.Count > 0) buffer.Length -= 2;
			buffer.Append("], ");
			buffer.Append(internalSlop);
			buffer.Append(", ");
			buffer.Append(inOrder);
			buffer.Append(")");
			buffer.Append(ToStringUtils.Boost(Boost));
			return buffer.ToString();
		}
		
		public override Spans GetSpans(IndexReader reader)
		{
			if (clauses.Count == 0)
			// optimize 0-clause case
				return new SpanOrQuery(GetClauses()).GetSpans(reader);
			
			if (clauses.Count == 1)
			// optimize 1-clause case
				return clauses[0].GetSpans(reader);
			
			return inOrder?(Spans) new NearSpansOrdered(this, reader, collectPayloads):(Spans) new NearSpansUnordered(this, reader);
		}
		
		public override Query Rewrite(IndexReader reader)
		{
			SpanNearQuery clone = null;
			for (int i = 0; i < clauses.Count; i++)
			{
				SpanQuery c = clauses[i];
				SpanQuery query = (SpanQuery) c.Rewrite(reader);
				if (query != c)
				{
					// clause rewrote: must clone
					if (clone == null)
						clone = (SpanNearQuery) this.Clone();
					clone.clauses[i] = query;
				}
			}
			if (clone != null)
			{
				return clone; // some clauses rewrote
			}
			else
			{
				return this; // no clauses rewrote
			}
		}
		
		public override System.Object Clone()
		{
			int sz = clauses.Count;
			SpanQuery[] newClauses = new SpanQuery[sz];
			
			for (int i = 0; i < sz; i++)
			{
				SpanQuery clause = clauses[i];
				newClauses[i] = (SpanQuery) clause.Clone();
			}
			SpanNearQuery spanNearQuery = new SpanNearQuery(newClauses, internalSlop, inOrder);
			spanNearQuery.Boost = Boost;
			return spanNearQuery;
		}
		
		/// <summary>Returns true iff <c>o</c> is equal to this. </summary>
		public  override bool Equals(System.Object o)
		{
			if (this == o)
				return true;
			if (!(o is SpanNearQuery))
				return false;
			
			SpanNearQuery spanNearQuery = (SpanNearQuery) o;
			
			if (inOrder != spanNearQuery.inOrder)
				return false;
			if (internalSlop != spanNearQuery.internalSlop)
				return false;
			if (clauses.Count != spanNearQuery.clauses.Count)
				return false;
            System.Collections.IEnumerator iter1 = clauses.GetEnumerator();
            System.Collections.IEnumerator iter2 = spanNearQuery.clauses.GetEnumerator();
            while (iter1.MoveNext() && iter2.MoveNext())
            {
                SpanQuery item1 = (SpanQuery)iter1.Current;
                SpanQuery item2 = (SpanQuery)iter2.Current;
                if (!item1.Equals(item2))
                    return false;
            }
			
			return Boost == spanNearQuery.Boost;
		}
		
		public override int GetHashCode()
		{
			long result = 0;
            //mgarski .NET uses the arraylist's location, not contents to calculate the hash
            // need to start with result being the hash of the contents.
            foreach (SpanQuery sq in clauses)
            {
                result += sq.GetHashCode();
            }
			// Mix bits before folding in things like boost, since it could cancel the
			// last element of clauses.  This particular mix also serves to
			// differentiate SpanNearQuery hashcodes from others.
			result ^= ((result << 14) | (Number.URShift(result, 19))); // reversible
			result += System.Convert.ToInt32(Boost);
			result += internalSlop;
			result ^= (inOrder ? (long) 0x99AFD3BD : 0);
			return (int) result;
		}
	}
}