//============================================================================
//ZedGraph Class Library - A Flexible Line Graph/Bar Graph Library in C#
//Copyright ?2006  John Champion
//RollingPointPairList class Copyright ?2006 by Colin Green
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU Lesser General Public
//License as published by the Free Software Foundation; either
//version 2.1 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//Lesser General Public License for more details.
//
//You should have received a copy of the GNU Lesser General Public
//License along with this library; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//=============================================================================
using System;
using System.Text;
using System.Runtime.Serialization;
using System.Security.Permissions;
namespace DrawGraph
{
	/// 
	/// A class that provides a rolling list of  objects.
	/// This is essentially a 
	/// first-in-first-out (FIFO) queue with a fixed capacity which allows 'rolling' 
	/// (or oscilloscope like) graphs to be be animated without having the overhead of an
	/// ever-growing ArrayList.
	/// 
	/// The queue is constructed with a fixed capacity and new points can be enqueued. When the 
	/// capacity is reached the oldest (first in) PointPair is overwritten. However, when 
	/// accessing via , the  objects are
	/// seen in the order in which they were enqeued.
	///
	/// RollingPointPairList supports data editing through the 
	/// interface.
	/// 
	/// Colin Green with mods by John Champion
	///  $Date: 2007/02/18 05:51:54 $ 
	/// 
	[Serializable]
	public class RollingPointPairList : IPointList, ISerializable, IPointListEdit
	{
	#region Fields
		/// 
		/// An array of PointPair objects that acts as the underlying buffer.
		/// 
		protected PointPair[] _mBuffer;
		/// 
		/// The index of the previously enqueued item. -1 if buffer is empty.
		/// 
		protected int _headIdx;
		/// 
		/// The index of the next item to be dequeued. -1 if buffer is empty.
		/// 
		protected int _tailIdx;
	#endregion
	#region Constructors
		/// 
		/// Constructs an empty buffer with the specified capacity.
		/// 
		/// Number of elements in the rolling list.  This number
		/// cannot be changed once the RollingPointPairList is constructed.
		public RollingPointPairList( int capacity )
			: this( capacity, false )
		{
			_mBuffer = new PointPair[capacity];
			_headIdx = _tailIdx = -1;
		}
		/// 
		/// Constructs an empty buffer with the specified capacity.  Pre-allocates space
		/// for all PointPair's in the list if  is true.
		/// 
		/// Number of elements in the rolling list.  This number
		/// cannot be changed once the RollingPointPairList is constructed.
		/// true to pre-allocate all PointPair instances in
		/// the list, false otherwise.  Note that in order to be memory efficient,
		/// the  method should be used to add
		/// data.  Avoid the  method.
		/// 
		/// 
		public RollingPointPairList( int capacity, bool preLoad )
		{
			_mBuffer = new PointPair[capacity];
			_headIdx = _tailIdx = -1;
			if ( preLoad )
				for ( int i = 0; i < capacity; i++ )
					_mBuffer[i] = new PointPair();
		}
		/// 
		/// Constructs a buffer with a copy of the items within the provided
		/// .
		/// The  is set to the length of the provided list.
		/// 
		/// The  to be copied.
		public RollingPointPairList( IPointList rhs )
		{
			_mBuffer = new PointPair[rhs.Count];
			for ( int i = 0; i < rhs.Count; i++ )
			{
				_mBuffer[i] = new PointPair( rhs[i] );
			}
			_headIdx = rhs.Count - 1;
			_tailIdx = 0;
		}
	#endregion
	#region Properties
		/// 
		/// Gets the capacity of the rolling buffer.
		/// 
		public int Capacity
		{
			get { return _mBuffer.Length; }
		}
		/// 
		/// Gets the count of items within the rolling buffer. Note that this may be less than
		/// the capacity.
		/// 
		public int Count
		{
			get
			{
				if ( _headIdx == -1 )
					return 0;
				if ( _headIdx > _tailIdx )
					return ( _headIdx - _tailIdx ) + 1;
				if ( _tailIdx > _headIdx )
					return ( _mBuffer.Length - _tailIdx ) + _headIdx + 1;
				return 1;
			}
		}
		/// 
		/// Gets a bolean that indicates if the buffer is empty.
		/// Alternatively you can test Count==0.
		/// 
		public bool IsEmpty
		{
			get { return _headIdx == -1; }
		}
		/// 
		/// Gets or sets the  at the specified index in the buffer.
		/// 
		/// 
		/// Index must be within the current size of the buffer, e.g., the set
		/// method will not expand the buffer even if  is available
		/// 
		public PointPair this[int index]
		{
			get
			{
				if ( index >= Count || index < 0 )
					throw new ArgumentOutOfRangeException();
				index += _tailIdx;
				if ( index >= _mBuffer.Length )
					index -= _mBuffer.Length;
				return _mBuffer[index];
			}
			set
			{
				if ( index >= Count || index < 0 )
					throw new ArgumentOutOfRangeException();
				index += _tailIdx;
				if ( index >= _mBuffer.Length )
					index -= _mBuffer.Length;
				_mBuffer[index] = value;
			}
		}
	#endregion
	#region Public Methods
		/// 
		/// Implement the  interface in a typesafe manner by just
		/// calling the typed version of 
		/// 
		/// A deep copy of this object
		object ICloneable.Clone()
		{
			return this.Clone();
		}
		/// 
		/// Typesafe, deep-copy clone method.
		/// 
		/// A new, independent copy of this class
		public RollingPointPairList Clone()
		{
			return new RollingPointPairList( this );
		}
		/// 
		/// Clear the buffer of all  objects.
		/// Note that the  remains unchanged.
		/// 
		public void Clear()
		{
			_headIdx = _tailIdx = -1;
		}
		/// 
		/// Calculate that the next index in the buffer that should receive a new data point.
		/// Note that this method actually advances the buffer, so a datapoint should be
		/// added at _mBuffer[_headIdx].
		/// 
		/// The index position of the new head element
		private int GetNextIndex()
		{
			if ( _headIdx == -1 )
			{	// buffer is currently empty.
				_headIdx = _tailIdx = 0;
			}
			else
			{
				// Determine the index to write to.
				if ( ++_headIdx == _mBuffer.Length )
				{	// Wrap around.
					_headIdx = 0;
				}
				if ( _headIdx == _tailIdx )
				{	// Buffer overflow. Increment tailIdx.
					if ( ++_tailIdx == _mBuffer.Length )
					{	// Wrap around.
						_tailIdx = 0;
					}
				}
			}
			return _headIdx;
		}
		/// 
		/// Add a  onto the head of the queue,
		/// overwriting old values if the buffer is full.
		/// 
		/// The  to be added.
		public void Add( PointPair item )
		{
			_mBuffer[ GetNextIndex() ] = item;
		}
		/// 
		/// Add an  object to the head of the queue.
		/// 
		/// A reference to the  object to
		/// be added
		public void Add( IPointList pointList )
		{   // A slightly more efficient approach would be to determine where the new points should placed within
			// the buffer and to then copy them in directly - updating the head and tail indexes appropriately.
			for ( int i = 0; i < pointList.Count; i++ )
				Add( pointList[i] );
		}
		/// 
		/// Remove an old item from the tail of the queue.
		/// 
		/// The removed item. Throws an 
		/// if the buffer was empty. 
		/// Check the buffer's length () or the 
		/// property to avoid exceptions.
		public PointPair Remove()
		{
			if ( _tailIdx == -1 )
			{	// buffer is currently empty.
				throw new InvalidOperationException( "buffer is empty." );
			}
			PointPair o = _mBuffer[_tailIdx];
			if ( _tailIdx == _headIdx )
			{	// The buffer is now empty.
				_headIdx = _tailIdx = -1;
				return o;
			}
			if ( ++_tailIdx == _mBuffer.Length )
			{	// Wrap around.
				_tailIdx = 0;
			}
			return o;
		}
		/// 
		/// Remove the  at the specified index
		/// 
		/// 
		/// All items in the queue that lie after  will
		/// be shifted back by one, and the queue will be one item shorter.
		/// 
		/// The ordinal position of the item to be removed.
		/// Throws an  if index is less than
		/// zero or greater than or equal to 
		/// 
		public void RemoveAt( int index )
		{
			int count = this.Count;
			if ( index >= count || index < 0 )
				throw new ArgumentOutOfRangeException();
			// shift all the items that lie after index back by 1
			for ( int i = index + _tailIdx; i < _tailIdx + count - 1; i++ )
			{
				i = ( i >= _mBuffer.Length ) ? 0 : i;
				int j = i + 1;
				j = ( j >= _mBuffer.Length ) ? 0 : j;
				_mBuffer[i] = _mBuffer[j];
			}
			// Remove the item from the head (it's been duplicated already)
			Pop();
		}
		/// 
		/// Pop an item off the head of the queue.
		/// 
		/// The popped item. Throws an exception if the buffer was empty.
		public PointPair Pop()
		{
			if ( _tailIdx == -1 )
			{	// buffer is currently empty.
				throw new InvalidOperationException( "buffer is empty." );
			}
			PointPair o = _mBuffer[_headIdx];
			if ( _tailIdx == _headIdx )
			{	// The buffer is now empty.
				_headIdx = _tailIdx = -1;
				return o;
			}
			if ( --_headIdx == -1 )
			{	// Wrap around.
				_headIdx = _mBuffer.Length - 1;
			}
			return o;
		}
		/// 
		/// Peek at the  item at the head of the queue.
		/// 
		/// The  item at the head of the queue.
		/// Throws an  if the buffer was empty.
		/// 
		public PointPair Peek()
		{
			if ( _headIdx == -1 )
			{	// buffer is currently empty.
				throw new InvalidOperationException( "buffer is empty." );
			}
			return _mBuffer[_headIdx];
		}
	#endregion
	#region Auxilliary Methods
		/// 
		/// Add a set of values onto the head of the queue,
		/// overwriting old values if the buffer is full.
		/// 
		/// 
		/// This method is much more efficient that the Add(PointPair)
		/// method, since it does not require that a new PointPair instance be provided.
		/// If the buffer already contains a  at the head position,
		/// then the x, y, z, and tag values will be copied into the existing PointPair.
		/// Otherwise, a new PointPair instance must be created.
		/// In this way, each PointPair position in the rolling list will only be allocated one time.
		/// To truly be memory efficient, the , ,
		/// and  methods should be avoided.  Also, the  property
		/// for this method should be null, since it is a reference type.
		/// 
		/// The X value
		/// The Y value
		/// The Z value
		/// The Tag value for the PointPair
		public void Add( double x, double y, double z, object tag )
		{
			// advance the rolling list
			GetNextIndex();
			if ( _mBuffer[_headIdx] == null )
				_mBuffer[_headIdx] = new PointPair( x, y, z, tag );
			else
			{
				_mBuffer[_headIdx].X = x;
				_mBuffer[_headIdx].Y = y;
				_mBuffer[_headIdx].Z = z;
				_mBuffer[_headIdx].Tag = tag;
			}
		}
		/// 
		/// Add a set of values onto the head of the queue,
		/// overwriting old values if the buffer is full.
		/// 
		/// 
		/// This method is much more efficient that the Add(PointPair)
		/// method, since it does not require that a new PointPair instance be provided.
		/// If the buffer already contains a  at the head position,
		/// then the x, y, z, and tag values will be copied into the existing PointPair.
		/// Otherwise, a new PointPair instance must be created.
		/// In this way, each PointPair position in the rolling list will only be allocated one time.
		/// To truly be memory efficient, the , ,
		/// and  methods should be avoided.
		/// 
		/// The X value
		/// The Y value
		public void Add( double x, double y )
		{
			Add( x, y, PointPair.Missing, null );
		}
		/// 
		/// Add a set of values onto the head of the queue,
		/// overwriting old values if the buffer is full.
		/// 
		/// 
		/// This method is much more efficient that the Add(PointPair)
		/// method, since it does not require that a new PointPair instance be provided.
		/// If the buffer already contains a  at the head position,
		/// then the x, y, z, and tag values will be copied into the existing PointPair.
		/// Otherwise, a new PointPair instance must be created.
		/// In this way, each PointPair position in the rolling list will only be allocated one time.
		/// To truly be memory efficient, the , ,
		/// and  methods should be avoided.  Also, the  property
		/// for this method should be null, since it is a reference type.
		/// 
		/// The X value
		/// The Y value
		/// The Tag value for the PointPair
		public void Add( double x, double y, object tag )
		{
			Add( x, y, PointPair.Missing, tag );
		}
		/// 
		/// Add a set of values onto the head of the queue,
		/// overwriting old values if the buffer is full.
		/// 
		/// 
		/// This method is much more efficient that the Add(PointPair)
		/// method, since it does not require that a new PointPair instance be provided.
		/// If the buffer already contains a  at the head position,
		/// then the x, y, z, and tag values will be copied into the existing PointPair.
		/// Otherwise, a new PointPair instance must be created.
		/// In this way, each PointPair position in the rolling list will only be allocated one time.
		/// To truly be memory efficient, the , ,
		/// and  methods should be avoided.
		/// 
		/// The X value
		/// The Y value
		/// The Z value
		public void Add( double x, double y, double z )
		{
			Add( x, y, z, null );
		}
		/// 
		/// Add a set of points to the 
		/// from two arrays of type double.
		/// If either array is null, then a set of ordinal values is automatically
		/// generated in its place (see ).
		/// If the arrays are of different size, then the larger array prevails and the
		/// smaller array is padded with  values.
		/// 
		/// A double[] array of X values
		/// A double[] array of Y values
		public void Add( double[] x, double[] y )
		{
			int len = 0;
			if ( x != null )
				len = x.Length;
			if ( y != null && y.Length > len )
				len = y.Length;
			for ( int i = 0; i < len; i++ )
			{
				PointPair point = new PointPair( 0, 0, 0 );
				if ( x == null )
					point.X = (double)i + 1.0;
				else if ( i < x.Length )
					point.X = x[i];
				else
					point.X = PointPair.Missing;
				if ( y == null )
					point.Y = (double)i + 1.0;
				else if ( i < y.Length )
					point.Y = y[i];
				else
					point.Y = PointPair.Missing;
				Add( point );
			}
		}
		/// 
		/// Add a set of points to the  from
		/// three arrays of type double.
		/// If the X or Y array is null, then a set of ordinal values is automatically
		/// generated in its place (see .
		/// If the  value
		/// is null, then it is set to zero.
		/// If the arrays are of different size, then the larger array prevails and the
		/// smaller array is padded with  values.
		/// 
		/// A double[] array of X values
		/// A double[] array of Y values
		/// A double[] array of Z values
		public void Add( double[] x, double[] y, double[] z )
		{
			int len = 0;
			if ( x != null )
				len = x.Length;
			if ( y != null && y.Length > len )
				len = y.Length;
			if ( z != null && z.Length > len )
				len = z.Length;
			for ( int i = 0; i < len; i++ )
			{
				PointPair point = new PointPair();
				if ( x == null )
					point.X = (double)i + 1.0;
				else if ( i < x.Length )
					point.X = x[i];
				else
					point.X = PointPair.Missing;
				if ( y == null )
					point.Y = (double)i + 1.0;
				else if ( i < y.Length )
					point.Y = y[i];
				else
					point.Y = PointPair.Missing;
				if ( z == null )
					point.Z = (double)i + 1.0;
				else if ( i < z.Length )
					point.Z = z[i];
				else
					point.Z = PointPair.Missing;
				Add( point );
			}
		}
	#endregion
		
	#region Serialization
		/// 
		/// Current schema value that defines the version of the serialized file
		/// 
		public const int schema = 10;
		/// 
		/// Constructor for deserializing objects
		/// 
		/// A  instance that defines the serialized data
		/// 
		/// A  instance that contains the serialized data
		/// 
		protected RollingPointPairList( SerializationInfo info, StreamingContext context )
		{
			// The schema value is just a file version parameter.  You can use it to make future versions
			// backwards compatible as new member variables are added to classes
			int sch = info.GetInt32( "schema" );
			_headIdx = info.GetInt32( "headIdx" );
			_tailIdx = info.GetInt32( "tailIdx" );
			_mBuffer = (PointPair[])info.GetValue( "mBuffer", typeof( PointPair[] ) );
		}
		/// 
		/// Populates a  instance with the data needed to serialize the target object
		/// 
		/// A  instance that defines the serialized data
		/// A  instance that contains the serialized data
		[SecurityPermissionAttribute( SecurityAction.Demand, SerializationFormatter = true )]
		public virtual void GetObjectData( SerializationInfo info, StreamingContext context )
		{
			info.AddValue( "schema", schema );
			info.AddValue( "headIdx", _headIdx );
			info.AddValue( "tailIdx", _tailIdx );
			info.AddValue( "mBuffer", _mBuffer );
		}
	#endregion
	}
}