/*
ScPl - A plotting library for .NET

ArrayAdapter.cs
Copyright (C) 2003
Matt Howlett

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
   
2. Redistributions in binary form must reproduce the following text in 
   the documentation and / or other materials provided with the 
   distribution: 
   
   "This product includes software developed as part of 
   the ScPl plotting library project available from: 
   http://www.netcontrols.org/scpl/" 

------------------------------------------------------------------------

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

$Id: ArrayAdapter.cs,v 1.29 2004/04/24 04:49:48 mhowlett Exp $

*/

using System.Drawing;
using System.Diagnostics;

namespace scpl
{
	/// <summary>
	/// The ArrayAdapter class, providing access to the data points.
	/// </summary>
	public class ArrayAdapter : ISequenceAdapter
	{
		/// <summary>
		/// Returns the number of data points accessable via indexer. 
		/// </summary>
		#region get Count
		public int Count
		{
			get
			{

#if CHECK_ERRORS
				if (this.yValues_ == null)
				{
					throw new System.Exception( "ERROR: ArrayAdapter.Count: yValues_ = null" );
				}
#endif

				return YValues.Length;
			}
		}
		#endregion

		/// <summary>
		/// Indexer for returning the datapoint.
		/// </summary>
		#region PointD this[int i]
		public virtual PointD this[int i]
		{	
			get
			{

#if CHECK_ERRORS
				if (i >= yValues_.Length)
				{
					throw new System.Exception( "ERROR: ArrayAdapter.this[int i]: index out of range" );
				}
#endif

				if ( xValues_ == null )
				{
					double xPos = this.Start + i*this.Step;
					return new PointD( (double)xPos, (double)yValues_[i] );
				}
				else
				{
					return new PointD( (double)xValues_[i], (double)yValues_[i] );
				}
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// The parameterless constructor.
		/// </summary>
		protected ArrayAdapter()
		{
		}
		/// <summary>
		/// ArrayAdapter constructor, taking two float arrays as arguments.
		/// <param name="xs">The float array of abscissa values.</param>
		/// <param name="ys">The float array of ordinate values.</param>
		/// </summary>
		public ArrayAdapter( float[] xs, float[] ys )
		{
			if ( xs.Length != ys.Length )
			{
				throw new System.Exception( "xs and ys not same length." );
			}
			double[] xsd = new double[xs.Length];
			double[] ysd = new double[ys.Length];
			for ( int i=0; i < xs.Length; ++i ) 
			{
				xsd[i] = xs[i];
				ysd[i] = ys[i];
			}
			this.yValues_ = ysd;
			this.xValues_ = xsd;
			this.step_ = null;
			this.start_ = null;
			ArrayMinMax( xValues_, out xValuesMin_, out xValuesMax_);
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking two float arrays as arguments.
		/// Causes the data to be replicated if DoClone is true. 
		/// Otherwise the original data is used. if DoClone is false and the data changes
		/// or goes out of scope before the plot is drawn, results are unpredictable.
		/// <param name="xs">The float array of abscissa values.</param>
		/// <param name="ys">The float array of ordinate values.</param>
		/// <param name="DoClone">The boolean flag used to replicate the data.</param>
		/// </summary>
		public ArrayAdapter( double[] xs, double[] ys, bool DoClone ) : this(xs, ys, DoClone, DoClone)
		{
		}


		/// <summary>
		/// ArrayAdapter constructor, taking two float arrays as arguments.
		/// Causes the data to be replicated if DoClone is true. 
		/// Otherwise the original data is used. if DoClone is false and the data changes
		/// or goes out of scope before the plot is drawn, results are unpredictable.
		/// <param name="xs">The float array of abscissa values.</param>
		/// <param name="ys">The float array of ordinate values.</param>
		/// <param name="DoCloneX">The boolean flag used to replicate the X data.</param>
		/// <param name="DoCloneY">The boolean flag used to replicate the Y data.</param>
		/// </summary>
		public ArrayAdapter( double[] xs, double[] ys, bool DoCloneX, bool DoCloneY )
		{
			if ( xs.Length != ys.Length )
			{
				throw new System.Exception( "xs and ys not same length." );
			}
			if (DoCloneX) 
			{
				this.xValues_ = (double[])xs.Clone();
			} 
			else 
			{
				this.xValues_ = xs;
			}
			if (DoCloneY) 
			{
				this.yValues_ = (double[])ys.Clone();
			} 
			else 
			{
				this.yValues_ = ys;
			}
			this.step_ = null;
			this.start_ = null;
			ArrayMinMax( xValues_, out xValuesMin_, out xValuesMax_);
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking two double arrays as arguments.
		/// <param name="xs">The double array of abscissa values.</param>
		/// <param name="ys">The double array of ordinate values.</param>
		/// </summary>
		public ArrayAdapter( double[] xs, double[] ys )
		{
			if ( xs.Length != ys.Length )
			{
				throw new System.Exception( "xs and ys not same length." );
			}
			this.yValues_ = ys;
			this.xValues_ = xs;
			this.step_ = null;
			this.start_ = null;
			ArrayMinMax( xValues_, out xValuesMin_, out xValuesMax_);
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking one float arrays as argument.
		/// <param name="ys">The float array of ordinate values.</param>
		/// </summary>
		public ArrayAdapter( float[] ys )
		{
			double[] ysd = new double[ys.Length];
			for ( int i = 0; i<ys.Length; ++i )
			{
				ysd[i] = ys[i];
			}
			this.yValues_ = ysd; 
			this.start_ = 0.0;
			this.step_ = 1.0;
			this.xValues_ = null;
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking one double arrays as argument.
		/// <param name="ys">The double array of ordinate values.</param>
		/// </summary>
		public ArrayAdapter( double[] ys )
		{
			this.yValues_ = ys; 
			this.start_ = 0.0;
			this.step_ = 1.0;
			this.xValues_ = null;
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking one float array, and 
		/// the start and step values for the x array.
		/// </summary>
		/// <param name="ys">The float array of ordinate values.</param>
		/// <param name="start">The starting point of the abscissa values.</param>
		/// <param name="step">The step of the abscissa values.</param>
		public ArrayAdapter( float[] ys, float start, float step )
		{
			double[] ysd = new double[ys.Length];
			for ( int i=0; i<ys.Length; ++i ) 
			{
				ysd[i] = ys[i];
			}
			this.yValues_ = ysd;
			this.start_ = (double)start;
			this.step_ = (double)step;
			this.xValues_ = null;
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}

		/// <summary>
		/// ArrayAdapter constructor, taking one double array, and 
		/// the start and step values for the x array.
		/// </summary>
		/// <param name="ys">The double array of ordinate values.</param>
		/// <param name="start">The starting point of the abscissa values.</param>
		/// <param name="step">The step of the abscissa values.</param>
		public ArrayAdapter( double[] ys, double start, double step )
		{
			this.yValues_ = ys;
			this.start_ = start;
			this.step_ = step;
			this.xValues_ = null;
			ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);
		}
		#endregion

		#region Suggest Axes
		/// <summary>
		/// Instances a default abscissa axis.
		/// </summary>
		/// <returns>An instance of the abscissa Axis.</returns>
		public Axis SuggestXAxis()
		{
			// Array data would be nicely drawn using a linear axis, that just includes all data points.
			if ( xValues_ == null )
			{
				if ( yValues_ == null )
				{
					return new LinearAxis( this.Start, 1.0 );
				}
				else
				{
					return new LinearAxis( this.Start, this.Start + this.Step * (yValues_.Length - 1) );
				}
			}
			else
			{
				double range = XValuesMax - XValuesMin;
				
				if ( range > 0.0F )
				{
					range *= 0.08F;
				}
				else 
				{
					range = 0.01F;
				}

				return new LinearAxis( XValuesMin - range, XValuesMax + range);
			}
		}
		
		/// <summary>
		/// Instances a default ordinate axis.
		/// </summary>
		/// <returns>An instance of the ordinate Axis.</returns>
		public Axis SuggestYAxis()
		{
			// Array data would be nicely drawn using a linear axis, that just includes all data points.
			double range = YValuesMax - YValuesMin;
			
			if ( range > 0.0F )
			{
				range *= 0.08F;
			}
			else
			{
				range = 0.01F;
			}

			return new LinearAxis( YValuesMin - range, YValuesMax + range);
		}

		#endregion

		#region ArrayMinMax
		/// <summary>
		/// Returns the minimum and maximum values in an array.
		/// </summary>
		/// <param name="a">The array to search.</param>
		/// <param name="min">The minimum value.</param>
		/// <param name="max">The maximum value.</param>
		/// <returns>true is min max set, false otherwise (a = null or zero length).</returns>
		public bool ArrayMinMax( double[] a, out object min, out object max )
		{
			// double[] is a reference type and can be null, if it is then I reckon the best
			// values for min and max are also null. double is a value type so can't be set
			//	to null. So min an max return object, and we understand that if it is not null
			// it is a boxed double (same trick I use lots elsewhere in the lib). The 
			// wonderful comment I didn't write at the top should explain everything.
			if ( a == null || a.Length == 0 )
			{
				min = null;
				max = null;
				return false;
			}

			min = a[0];
			max = a[0];
			foreach ( double e in a ) 
			{
			
				if (e < (double)min)
				{
					min = e;
				}
			
				if (e > (double)max) 
				{
					max = e;
				}
			}
			return true;

		}
		#endregion

		#region get/set YValues
		/// <summary>
		/// Accessor for the ordinate values array.
		/// </summary>
		public double[] YValues
		{
			get
			{
				return yValues_;
			}
			set
			{
				yValues_ = value;
			}
		}
		#endregion
		#region get/set XValues
		/// <summary>
		/// Accessor for the abscissa values array.
		/// </summary>
		public double[] XValues
		{
			get
			{
				return xValues_;
			}
			set
			{
				xValues_ = value;
			}
		}
		#endregion
		#region get/set Start
		/// <summary>
		/// Accessor for the start abscissa position.
		/// </summary>
		public double Start
		{
			get
			{
				if (start_ != null)
				{
					return (double)start_;
				}
				throw new System.Exception( "start_ not set." );
			}
			set
			{
				start_ = value;
			}
		}

		#endregion
		#region get/set Step
		/// <summary>
		/// Accessor for the abscissa step size. 
		/// </summary>
		public double Step
		{
			get
			{
				if ( step_ != null )
				{
					return (double)step_;
				}
				throw new System.Exception( "step_ not set." );
			}
			set
			{
				step_ = value;
			}
		}
		#endregion

		/// <summary>
		/// The protected array of X values
		/// </summary>
		protected double[] xValues_;
		/// <summary>
		/// The protected array of Y values
		/// </summary>
		protected double[] yValues_;
		/// <summary>
		/// The protected start point.
		/// </summary>
		protected object start_;
		/// <summary>
		/// The protected step.
		/// </summary>
		protected object step_;

		#region get/set XValuesMin, XValuesMax, YValuesMin, YValuesMax
		/// <summary>
		/// The minimal x value.
		/// </summary>
		protected object xValuesMin_ = null;
		/// <summary>
		/// The maximal x value.
		/// </summary>
		protected object xValuesMax_ = null;
		/// <summary>
		/// The minimal y value.
		/// </summary>
		protected object yValuesMin_ = null;
		/// <summary>
		/// The maximal y value.
		/// </summary>
		protected object yValuesMax_ = null;

		/// <summary>
		/// Accessor for the minimum value of the abscissa axis.
		/// </summary>
		public double XValuesMin
		{
			get
			{
				if ( xValues_ == null ) 
				{
					throw new System.Exception( "xValues not set." );
				}
				if ( xValuesMin_ == null ) 
				{
					ArrayMinMax( xValues_, out xValuesMin_, out xValuesMax_);				
				}
				return (double)xValuesMin_;
			}
		}

		/// <summary>
		/// Accessor for the maximum value of the abscissa axis.
		/// </summary>
		public double XValuesMax
		{
			get
			{
				if ( xValues_ == null ) 
				{
					throw new System.Exception( "xValues not set." );
				}
				if ( xValuesMax_ == null ) 
				{
					ArrayMinMax( xValues_, out xValuesMin_, out xValuesMax_);				
				}
				return (double)xValuesMax_;
			}
		}

		/// <summary>
		/// Accessor for the minimum value of the ordinate axis.
		/// </summary>
		public double YValuesMin
		{
			get
			{
				if ( yValues_ == null ) 
				{
					throw new System.Exception( "yValues not set." );
				}
				if ( yValuesMin_ == null ) 
				{
					ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);				
				}
				return (double)yValuesMin_;
			}
		}

		/// <summary>
		/// Accessor for the maximum value of the ordinate axis.
		/// </summary>
		public double YValuesMax
		{
			get
			{
				if ( yValues_ == null ) 
				{
					throw new System.Exception( "yValues not set." );
				}
				if ( yValuesMax_ == null ) 
				{
					ArrayMinMax( yValues_, out yValuesMin_, out yValuesMax_);				
				}
				return (double)yValuesMax_;
			}
		}
		#endregion
		
	} 

} 
