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

namespace Lucene.Net.Index
{
	
	/// <summary>  A Payload is metadata that can be stored together with each occurrence 
	/// of a term. This metadata is stored inline in the posting list of the
	/// specific term.  
	/// <p/>
	/// To store payloads in the index a <see cref="TokenStream"/> has to be used that
	/// produces payload data.
	/// <p/>
    /// Use <see cref="TermPositions.PayloadLength"/> and <see cref="TermPositions.GetPayload(byte[], int)"/>
	/// to retrieve the payloads from the index.<br/>
	/// 
	/// </summary>
	[Serializable]
	public class Payload : System.ICloneable
	{
		/// <summary>the byte array containing the payload data </summary>
		protected internal byte[] data;
		
		/// <summary>the offset within the byte array </summary>
		protected internal int internalOffset;
		
		/// <summary>the length of the payload data </summary>
		protected internal int internalLength;
		
		/// <summary>Creates an empty payload and does not allocate a byte array. </summary>
		public Payload()
		{
			// nothing to do
		}
		
		/// <summary> Creates a new payload with the the given array as data.
		/// A reference to the passed-in array is held, i. e. no 
		/// copy is made.
		/// 
		/// </summary>
		/// <param name="data">the data of this payload
		/// </param>
		public Payload(byte[] data):this(data, 0, data.Length)
		{
		}
		
		/// <summary> Creates a new payload with the the given array as data. 
		/// A reference to the passed-in array is held, i. e. no 
		/// copy is made.
		/// 
		/// </summary>
		/// <param name="data">the data of this payload
		/// </param>
		/// <param name="offset">the offset in the data byte array
		/// </param>
		/// <param name="length">the length of the data
		/// </param>
		public Payload(byte[] data, int offset, int length)
		{
			if (offset < 0 || offset + length > data.Length)
			{
				throw new System.ArgumentException();
			}
			this.data = data;
			this.internalOffset = offset;
			this.internalLength = length;
		}

	    /// <summary> Sets this payloads data. 
		/// A reference to the passed-in array is held, i. e. no 
		/// copy is made.
		/// </summary>
		public virtual void  SetData(byte[] value, int offset, int length)
		{
			this.data = value;
			this.internalOffset = offset;
			this.internalLength = length;
		}

	    /// <summary> Gets or sets a reference to the underlying byte array
	    /// that holds this payloads data.  Data is not copied.
	    /// </summary>
	    public virtual void SetData(byte[] value)
	    {
	        SetData(value, 0, value.Length);
	    }

	    /// <summary> Gets or sets a reference to the underlying byte array
	    /// that holds this payloads data.  Data is not copied.
	    /// </summary>
	    public virtual byte[] GetData()
	    {
	        return this.data;
	    }

	    /// <summary> Returns the offset in the underlying byte array </summary>
	    public virtual int Offset
	    {
	        get { return this.internalOffset; }
	    }

	    /// <summary> Returns the length of the payload data. </summary>
	    public virtual int Length
	    {
	        get { return this.internalLength; }
	    }

	    /// <summary> Returns the byte at the given index.</summary>
		public virtual byte ByteAt(int index)
		{
			if (0 <= index && index < this.internalLength)
			{
				return this.data[this.internalOffset + index];
			}
			throw new System. IndexOutOfRangeException("Index of bound " + index);
		}
		
		/// <summary> Allocates a new byte array, copies the payload data into it and returns it. </summary>
		public virtual byte[] ToByteArray()
		{
			byte[] retArray = new byte[this.internalLength];
			Array.Copy(this.data, this.internalOffset, retArray, 0, this.internalLength);
			return retArray;
		}
		
		/// <summary> Copies the payload data to a byte array.
		/// 
		/// </summary>
		/// <param name="target">the target byte array
		/// </param>
		/// <param name="targetOffset">the offset in the target byte array
		/// </param>
		public virtual void  CopyTo(byte[] target, int targetOffset)
		{
			if (this.internalLength > target.Length + targetOffset)
			{
				throw new System.IndexOutOfRangeException();
			}
			Array.Copy(this.data, this.internalOffset, target, targetOffset, this.internalLength);
		}
		
		/// <summary> Clones this payload by creating a copy of the underlying
		/// byte array.
		/// </summary>
		public virtual System.Object Clone()
		{
			try
			{
				// Start with a shallow copy of data
				Payload clone = (Payload) base.MemberwiseClone();
				// Only copy the part of data that belongs to this Payload
				if (internalOffset == 0 && internalLength == data.Length)
				{
					// It is the whole thing, so just clone it.
					clone.data = new byte[data.Length];
					data.CopyTo(clone.data, 0);
				}
				else
				{
					// Just get the part
					clone.data = this.ToByteArray();
					clone.internalOffset = 0;
				}
				return clone;
			}
			catch (System.Exception e)
			{
				throw new System.SystemException(e.Message, e); // shouldn't happen
			}
		}
		
		public  override bool Equals(System.Object obj)
		{
			if (obj == this)
				return true;
			if (obj is Payload)
			{
				Payload other = (Payload) obj;
				if (internalLength == other.internalLength)
				{
					for (int i = 0; i < internalLength; i++)
						if (data[internalOffset + i] != other.data[other.internalOffset + i])
							return false;
					return true;
				}
				else
					return false;
			}
			else
				return false;
		}
		
		public override int GetHashCode()
		{
			return ArrayUtil.HashCode(data, internalOffset, internalOffset + internalLength);
		}
	}
}