//============================================================================
//PointPairList Class
//Copyright ?2004  Jerry Vos
//
//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.Drawing;
using System.Collections.Generic;
namespace DrawGraph
{
	/// 
	/// A collection class containing a list of  objects
	/// that define the set of points to be displayed on the curve.
	/// 
	/// 
	/// 
	/// 
	///  Jerry Vos based on code by John Champion
	/// modified by John Champion
	///  $Revision: 3.36 $ $Date: 2007/02/18 05:51:54 $ 
	[Serializable]
	public class PointPairList : List, IPointList, IPointListEdit
	{
	#region Fields
		/// Private field to maintain the sort status of this
		/// .  Use the public property
		///  to access this value.
		/// 
		protected bool _sorted = true;
	#endregion
	#region Properties
/*		/// 
		/// Indexer to access the specified  object by
		/// its ordinal position in the list.
		/// 
		/// The ordinal position (zero-based) of the
		///  object to be accessed.
		/// A  object reference.
		public PointPair this[ int index ]  
		{
			get { return (PointPair) List[index]; }
			set { List[index] = value; }
		}
*/
		/// 
		/// true if the list is currently sorted.
		/// 
		/// 
		public bool Sorted
		{
			get { return _sorted; }
		}
	#endregion
	#region Constructors
		/// 
		/// Default constructor for the collection class
		/// 
		public PointPairList()
		{
			_sorted = false;
		}
		/// 
		/// Constructor to initialize the PointPairList from two arrays of
		/// type double.
		/// 
		public PointPairList( double[] x, double[] y )
		{
			Add( x, y );
			_sorted = false;
		}
		/// 
		/// Constructor to initialize the PointPairList from an IPointList
		/// 
		public PointPairList( IPointList list )
		{
			int count = list.Count;
			for ( int i = 0; i < count; i++ )
				Add( list[i] );
			_sorted = false;
		}
		/// 
		/// Constructor to initialize the PointPairList from three arrays of
		/// type double.
		/// 
		public PointPairList( double[] x, double[] y, double[] baseVal )
		{
			Add( x, y, baseVal );
			
			_sorted = false;
		}
		/// 
		/// The Copy Constructor
		/// 
		/// The PointPairList from which to copy
		public PointPairList( PointPairList rhs )
		{
			Add( rhs );
			_sorted = false;
		}
		/// 
		/// 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 PointPairList Clone()
		{
			return new PointPairList( this );
		}
	#endregion
	#region Methods
		/// 
		/// Add a  object to the collection at the end of the list.
		/// 
		/// The  object to
		/// be added
		/// The zero-based ordinal index where the point was added in the list.
		public new void Add( PointPair point )
		{
			_sorted = false;
			base.Add( new PointPair( point ) );
		}
		/// 
		/// Add a  object to the collection at the end of the list.
		/// 
		/// A reference to the  object to
		/// be added
		/// The zero-based ordinal index where the last point was added in the list,
		/// or -1 if no points were added.
		public void Add( PointPairList pointList )
		{
			foreach ( PointPair point in pointList )
			Add( point );
				
			_sorted = false;
		}
		/// 
		/// Add a set of points to the PointPairList 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
		/// The zero-based ordinal index where the last point was added in the list,
		/// or -1 if no points were added.
		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
		/// 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 
		/// 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 or lower-dependent axis values
		/// The zero-based ordinal index where the last point was added in the list,
		/// or -1 if no points were added.
		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
		/// Add a single point to the  from values of type double.
		/// 
		/// The X value
		/// The Y value
		/// The zero-based ordinal index where the point was added in the list.
		public void Add( double x, double y )
		{
			_sorted = false;
			PointPair	point = new PointPair( x, y );
			base.Add( point );
		}
		/// 
		/// Add a single point to the  from values of type double.
		/// 
		/// The X value
		/// The Y value
		/// The Tag value for the PointPair
		/// The zero-based ordinal index where the point was added in the list.
		public void Add( double x, double y, string tag )
		{
			_sorted = false;
			PointPair	point = new PointPair( x, y, tag );
			base.Add( point );
		}
		/// 
		/// Add a single point to the  from values of type double.
		/// 
		/// The X value
		/// The Y value
		/// The Z or lower dependent axis value
		/// The zero-based ordinal index where the point was added
		/// in the list.
		public void Add( double x, double y, double z )
		{
			_sorted = false;
			PointPair point = new PointPair( x, y, z );
			base.Add( point );
		}
		/// 
		/// Add a single point to the  from values of type double.
		/// 
		/// The X value
		/// The Y value
		/// The Z or lower dependent axis value
		/// The Tag value for the PointPair
		/// The zero-based ordinal index where the point was added
		/// in the list.
		public void Add( double x, double y, double z, string tag )
		{
			_sorted = false;
			PointPair point = new PointPair( x, y, z, tag );
			base.Add( point );
		}
		/// 
		/// Add a  object to the collection at the specified,
		/// zero-based, index location.
		/// 
		/// 
		/// The zero-based ordinal index where the point is to be added in the list.
		/// 
		/// 
		/// The  object to be added.
		/// 
		public new void Insert( int index, PointPair point )
		{
			_sorted = false;
			base.Insert( index, point );
		}
		/// 
		/// Add a single point (from values of type double ) to the  at the specified,
		/// zero-based, index location.
		/// 
		/// 
		/// The zero-based ordinal index where the point is to be added in the list.
		/// 
		/// The X value
		/// The Y value
		public void Insert( int index, double x, double y )
		{
			_sorted = false;
			base.Insert( index, new PointPair( x, y ) );
		}
		/// 
		/// Add a single point (from values of type double ) to the  at the specified,
		/// zero-based, index location.
		/// 
		/// 
		/// The zero-based ordinal index where the point is to be added in the list.
		/// 
		/// The X value
		/// The Y value
		/// The Z or lower dependent axis value
		public void Insert( int index, double x, double y, double z )
		{
			_sorted = false;
			Insert( index, new PointPair( x, y, z ) );
		}
		/*
				/// 
				/// Remove the specified  object from the collection based
				/// the point values (must match exactly).
				/// 
				/// 
				/// A  that is to be removed by value.
				/// 
				/// 
				public void Remove( PointPair pt )
				{
					List.Remove( pt );
				}
				/// 
				/// Return the zero-based position index of the specified
				///  in the collection.
				/// 
				/// The  object that is to be found.
				/// 
				/// The zero-based index of the specified , or -1 if the 
				/// is not in the list
				/// 
				public int IndexOf( PointPair pt )
				{
					return List.IndexOf( pt );
				}
		*/
		/// 
		/// Return the zero-based position index of the
		///  with the specified label .
		/// 
		/// The  object must be of type 
		/// for this method to find it.
		/// The  label that is in the
		///  attribute of the item to be found.
		/// 
		/// The zero-based index of the specified ,
		/// or -1 if the  is not in the list
		public int IndexOfTag( string label )
		{
			int iPt = 0;
			foreach ( PointPair p in this )
			{
				if ( p.Tag is string && String.Compare( (string) p.Tag, label, true ) == 0 )
					return iPt;
				iPt++;
			}
			return -1;
		}
		/// 
		/// Compare two  objects to see if they are equal.
		/// 
		/// Equality is based on equal count of  items, and
		/// each individual  must be equal (as per the
		///  method.
		/// The  to be compared with for equality.
		/// true if the  objects are equal, false otherwise.
		public override bool Equals( object obj )
		{
			PointPairList rhs = obj as PointPairList;
			if( this.Count != rhs.Count )
				return false;
			for( int i=0; i
		/// Return the HashCode from the base class.
		/// 
		/// 
		public override int GetHashCode()
		{
			return base.GetHashCode ();
		}
		/// 
		/// Sorts the list according to the point x values. Will not sort the 
		/// list if the list is already sorted.
		/// 
		/// If the list was sorted before sort was called
		public new bool Sort()
		{
			// if it is already sorted we don't have to sort again
			if ( _sorted )
				return true;
			Sort( new PointPair.PointPairComparer( SortType.XValues ) );
			return false;
		}
		
		/// 
		/// Sorts the list according to the point values . Will not sort the 
		/// list if the list is already sorted.
		/// 
		///   The 
		///used to determine whether the X or Y values will be used to sort
		///the list
		/// If the list was sorted before sort was called
		public bool Sort( SortType type)
		{
			// if it is already sorted we don't have to sort again
			if ( _sorted )
				return true;
				
			this.Sort( new PointPair.PointPairComparer( type ) );
			
			return false;
		}
		
		/// 
		/// Set the X values for this  from the specified
		/// array of double values.
		/// 
		/// 
		/// If  has more values than
		/// this list, then the extra values will be ignored.  If 
		/// has less values, then the corresponding  values
		/// will not be changed.  That is, if the  has 20 values
		/// and  has 15 values, then the first 15 values of the
		///  will be changed, and the last 5 values will not be
		/// changed.
		/// 
		/// An array of double values that will replace the existing X
		/// values in the .
		public void SetX( double[] x )
		{
			for ( int i=0; i
		/// Set the Y values for this  from the specified
		/// array of double values.
		/// 
		/// 
		/// If  has more values than
		/// this list, then the extra values will be ignored.  If 
		/// has less values, then the corresponding  values
		/// will not be changed.  That is, if the  has 20 values
		/// and  has 15 values, then the first 15 values of the
		///  will be changed, and the last 5 values will not be
		/// changed.
		/// 
		/// An array of double values that will replace the existing Y
		/// values in the .
		public void SetY( double[] y )
		{
			for ( int i=0; i
		/// Set the Z values for this  from the specified
		/// array of double values.
		/// 
		/// 
		/// If  has more values than
		/// this list, then the extra values will be ignored.  If 
		/// has less values, then the corresponding  values
		/// will not be changed.  That is, if the  has 20 values
		/// and  has 15 values, then the first 15 values of the
		///  will be changed, and the last 5 values will not be
		/// changed.
		/// 
		/// An array of double values that will replace the existing Z
		/// values in the .
		public void SetZ( double[] z )
		{
			for ( int i=0; i
		/// Add the Y values from the specified  object to this
		/// .  If  has more values than
		/// this list, then the extra values will be ignored.  If 
		/// has less values, the missing values are assumed to be zero.
		/// 
		/// A reference to the  object to
		/// be summed into the this .
		public void SumY( PointPairList sumList )
		{
			for ( int i=0; i
		/// Add the X values from the specified  object to this
		/// .  If  has more values than
		/// this list, then the extra values will be ignored.  If 
		/// has less values, the missing values are assumed to be zero.
		/// 
		/// A reference to the  object to
		/// be summed into the this .
		public void SumX( PointPairList sumList )
		{
			for ( int i=0; i
		/// Linearly interpolate the data to find an arbitraty Y value that corresponds to the specified X value.
		/// 
		/// 
		/// This method uses linear interpolation with a binary search algorithm.  It therefore
		/// requires that the x data be monotonically increasing.  Missing values are not allowed.  This
		/// method will extrapolate outside the range of the PointPairList if necessary.
		/// 
		/// The target X value on which to interpolate
		/// The Y value that corresponds to the  value.
		public double InterpolateX( double xTarget )
		{
			int lo, mid, hi;
			if ( this.Count < 2 )
				throw new Exception( "Error: Not enough points in curve to interpolate" );
			if ( xTarget <= this[0].X )
			{
				lo = 0;
				hi = 1;
			}
			else if ( xTarget >= this[this.Count-1].X )
			{
				lo = this.Count - 2;
				hi = this.Count - 1;
			}
			else
			{
				// if x is within the bounds of the x table, then do a binary search
				// in the x table to find table entries that bound the x value
				lo = 0;
				hi = this.Count - 1;
			    
				// limit to 1000 loops to avoid an infinite loop problem
				int j;
				for ( j=0; j<1000 && hi > lo + 1; j++ )
				{
					mid = ( hi + lo ) / 2;
					if ( xTarget > this[mid].X )
						lo = mid;
					else
						hi = mid;
				}
				if ( j >= 1000 )
					throw new Exception( "Error: Infinite loop in interpolation" );
			}
			return ( xTarget - this[lo].X ) / ( this[hi].X - this[lo].X ) *
					( this[hi].Y - this[lo].Y ) + this[lo].Y;
		}
		/// 
		/// Use Cardinal Splines to Interpolate the data to find an arbitraty Y value that corresponds to
		/// the specified X value.
		/// 
		/// 
		/// This method uses cardinal spline interpolation with a binary search algorithm.  It therefore
		/// requires that the x data be monotonically increasing.  Missing values are not allowed.  This
		/// method will not extrapolate outside the range of the PointPairList (it returns
		///  if extrapolation would be required).  WARNING: Cardinal
		/// spline interpolation can generate curves with non-unique X values for higher tension
		/// settings.  That is, there may be multiple X values for the same Y value.  This routine
		/// follows the path of the spline curve until it reaches the FIRST OCCURRENCE of the
		/// target X value.  It does not check to see if other solutions are possible.
		/// 
		/// The target X value on which to interpolate
		/// The tension setting that controls the curvature of the spline fit.
		/// Typical values are between 0 and 1, where 0 is a linear fit, and 1 is lots of "roundness".
		/// Values greater than 1 may give odd results.
		/// 
		/// The Y value that corresponds to the  value.
		public double SplineInterpolateX( double xTarget, double tension )
		{
			// Scale the tension value to be compatible with the GDI+ values
			tension /= 3.0;
			int lo, mid, hi;
			if ( this.Count < 2 )
				throw new Exception( "Error: Not enough points in curve to interpolate" );
			// Extrapolation not allowed
			if ( xTarget <= this[0].X || xTarget >= this[this.Count-1].X )
				return PointPair.Missing;
			else
			{
				// if x is within the bounds of the x table, then do a binary search
				// in the x table to find table entries that bound the x value
				lo = 0;
				hi = this.Count - 1;
			    
				// limit to 1000 loops to avoid an infinite loop problem
				int j;
				for ( j=0; j<1000 && hi > lo + 1; j++ )
				{
					mid = ( hi + lo ) / 2;
					if ( xTarget > this[mid].X )
						lo = mid;
					else
						hi = mid;
				}
				if ( j >= 1000 )
					throw new Exception( "Error: Infinite loop in interpolation" );
			}
			// At this point, we know the two bounding points around our point of interest
			// We need the four points that surround our point
			double X0, X1, X2, X3;
			double Y0, Y1, Y2, Y3;
			double B0, B1, B2, B3;
			X1 = this[lo].X;
			X2 = this[hi].X;
			Y1 = this[lo].Y;
			Y2 = this[hi].Y;
			// if we are at either the beginning of the table or the end, then make up a before
			// and/or after point to fill in the four points
			if ( lo == 0 )
			{
				X0 = X1 - ( X2 - X1 )/3;
				Y0 = Y1 - ( Y2 - Y1 )/3;
			}
			else
			{
				X0 = this[lo-1].X;
				Y0 = this[lo-1].Y;
			}
			if ( hi == this.Count - 1 )
			{
				X3 = X2 + ( X2 - X1 )/3;
				Y3 = Y2 + ( Y2 - Y1 )/3;
			}
			else
			{
				X3 = this[hi+1].X;
				Y3 = this[hi+1].Y;
			}
			double	newX, newY,
						lastX = X1,
						lastY = Y1;
			// Do 100 steps to find the result
			for ( double t=0.01; t<=1; t+=0.01 )
			{
				B0 = (1 - t) * (1 - t) * (1 - t);
				B1 = 3.0 * t * (1 - t) * (1 - t);
				B2 = 3.0 * t * t * (1 - t);
				B3 = t * t * t;
				newX = X1 * B0 + (X1 + (X2 - X0) * tension) * B1 +
						(X2 - (X3 - X1) * tension) * B2 + X2 * B3;
				newY = Y1 * B0 + (Y1 + (Y2 - Y0) * tension) * B1 +
						(Y2 - (Y3 - Y1) * tension) * B2 + Y2 * B3;
				// We are looking for the first X that exceeds the target
				if ( newX >= xTarget )
				{
					// We now have two bounding X values around our target
					// use linear interpolation to minimize the discretization
					// error.
					return ( xTarget - lastX ) / ( newX - lastX ) *
							( newY - lastY ) + lastY;
				}
				lastX = newX;
				lastY = newY;
			}
			// This should never happen
			return Y2;
		}
		/// 
		/// Linearly interpolate the data to find an arbitraty X value that corresponds to the specified Y value.
		/// 
		/// 
		/// This method uses linear interpolation with a binary search algorithm.  It therefore
		/// requires that the Y data be monotonically increasing.  Missing values are not allowed.  This
		/// method will extrapolate outside the range of the PointPairList if necessary.
		/// 
		/// The target Y value on which to interpolate
		/// The X value that corresponds to the  value.
		public double InterpolateY( double yTarget )
		{
			int lo, mid, hi;
			if ( this.Count < 2 )
				throw new Exception( "Error: Not enough points in curve to interpolate" );
			if ( yTarget <= this[0].Y )
			{
				lo = 0;
				hi = 1;
			}
			else if ( yTarget >= this[this.Count-1].Y )
			{
				lo = this.Count - 2;
				hi = this.Count - 1;
			}
			else
			{
				// if y is within the bounds of the y table, then do a binary search
				// in the y table to find table entries that bound the y value
				lo = 0;
				hi = this.Count - 1;
			    
				// limit to 1000 loops to avoid an infinite loop problem
				int j;
				for ( j=0; j<1000 && hi > lo + 1; j++ )
				{
					mid = ( hi + lo ) / 2;
					if ( yTarget > this[mid].Y )
						lo = mid;
					else
						hi = mid;
				}
				if ( j >= 1000 )
					throw new Exception( "Error: Infinite loop in interpolation" );
			}
			return ( yTarget - this[lo].Y ) / ( this[hi].Y - this[lo].Y ) *
					( this[hi].X - this[lo].X ) + this[lo].X;
		}
		/// 
		/// Use linear regression to form a least squares fit of an existing
		///  instance.
		/// 
		/// The output  will cover the
		/// same X range of data as the original dataset.
		/// 
		/// An  instance containing
		/// the data to be regressed.
		/// The number of desired points to be included
		/// in the resultant .
		/// 
		/// A new  containing the resultant
		/// data fit.
		/// 
		public PointPairList LinearRegression( IPointList points, int pointCount )
		{
			double minX = double.MaxValue;
			double maxX = double.MinValue;
			for ( int i=0; i maxX ? pt.X : maxX;
				}
			}
			return LinearRegression( points, pointCount, minX, maxX );
		}
		/// 
		/// Use linear regression to form a least squares fit of an existing
		///  instance.
		/// 
		/// An  instance containing
		/// the data to be regressed.
		/// The number of desired points to be included
		/// in the resultant .
		/// 
		/// The minimum X value of the resultant
		/// .
		/// The maximum X value of the resultant
		/// .
		/// A new  containing the resultant
		/// data fit.
		/// 
		///  Brian Chappell - lazarusds
		///          modified by John Champion
		public PointPairList LinearRegression( IPointList points, int pointCount,
			double minX, double maxX )
		{
			double x = 0, y = 0, xx = 0, xy = 0, count = 0;
			for ( int i = 0; i < points.Count; i++ )
			{
				PointPair pt = points[i];
				if ( !pt.IsInvalid )
				{
					x += points[i].X;
					y += points[i].Y;
					xx += points[i].X * points[i].X;
					xy += points[i].X * points[i].Y;
					count++;
				}
			}
			if ( count < 2 || maxX - minX < 1e-20 )
				return null;
			double slope = ( count * xy - x * y ) / ( count * xx - x * x );
			double intercept = ( y - slope * x ) / count;
			PointPairList newPoints = new PointPairList();
			double stepSize = ( maxX - minX ) / pointCount;
			double value = minX;
			for ( int i = 0; i < pointCount; i++ )
			{
				newPoints.Add( new PointPair( value, value * slope + intercept ) );
				value += stepSize;
			}
			return newPoints;
		} 
	#endregion
	}
}