/*
ScPl - A plotting library for .NET

ArrayAdapterDT.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: ArrayAdapterDT.cs,v 1.9 2004/03/25 11:38:35 mhowlett Exp $

*/

using System;

namespace scpl
{
	/// <summary>
	/// Summary description for ArrayAdapterDT.
	/// Probably should derive from ArrayAdapter.
	/// </summary>
	public class ArrayAdapterDT : ISequenceAdapter
	{

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="xs">The DateTime array of abscissa values.</param>
		/// <param name="ys">The double array of ordinate values.</param>
		public ArrayAdapterDT( double[] ys, DateTime[] xs )
		{
			yValues_ = ys;
			xValues_ = xs;
			this.getMinMaxDate();
		}
							
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="ys">The double array or ordinate values.</param>
		/// <param name="start">The start DateTime</param>
		/// <param name="step">The timestep between values.</param>
		public ArrayAdapterDT( double[] ys, DateTime start, TimeSpan step )
		{
			start_ = start;
			step_ = step;

			xValuesMin_ = start;
			long stepTicks = step.Ticks;
			stepTicks *= (long)(ys.Length-1);
			xValuesMax_ = start + new TimeSpan(stepTicks);

			this.yValues_ = ys;
		}

		/// <summary>
		/// Indexer for returning the data point.
		/// </summary>
		public PointD this[int i]
		{
			get
			{
				if ( this.xValues_ == null )
				{
					long stepTicks = ((TimeSpan)step_).Ticks;
					stepTicks *= (long)(i);
					DateTime newDate = (DateTime)start_ + new TimeSpan(stepTicks);
					return new PointD( (double)newDate.Ticks, yValues_[i] );
				}
				else 
				{
					return new PointD( (double)xValues_[i].Ticks, yValues_[i] );
				}
			}
		}

		/// <summary>
		/// Returns number of data points accessable via indexer.
		/// </summary>
		public int Count
		{
			get
			{
				return yValues_.Length;
			}
		}

		/// <summary>
		/// Instances a default abscissa axis.
		/// </summary>
		/// <returns>An instance of the abscissa Axis.</returns>
		public Axis SuggestXAxis()
		{
			DateTimeAxis toRet;

			if (xValuesMin_ == null || xValuesMax_ == null)
			{
				toRet = new DateTimeAxis();
			}

			else 
			{
				toRet = new DateTimeAxis( 
					(double)((DateTime)xValuesMin_).Ticks,
					(double)((DateTime)xValuesMax_).Ticks  );
			}

			return toRet;
		}

		/// <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);
		}

		#region get XValuesMin XValuesMax
		private void getMinMaxDate()
		{
			xValuesMin_ = null;
			xValuesMax_ = null;
			for (int i=0; i<xValues_.Length; ++i)
			{
				if (xValuesMin_ == null)
				{
					xValuesMin_ = xValues_[i];
					xValuesMax_ = xValues_[i];
				}
				else
				{
					if (xValues_[i] < (DateTime)xValuesMin_)
					{
						xValuesMin_ = xValues_[i];
					}
					if (xValues_[i] > (DateTime)xValuesMax_)
					{
						xValuesMax_ = xValues_[i];
					}
				}
			}
		}

		private object xValuesMin_;
		private object xValuesMax_; 

		/// <summary>
		/// 
		/// </summary>
		public double XValuesMin
		{
			get
			{
				if ( xValues_ == null ) 
				{
					throw new System.Exception( "xValues not set." );
				}
				if ( xValuesMin_ == null ) 
				{
					this.getMinMaxDate();				
				}
				return (double)xValuesMin_;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public double XValuesMax
		{
			get
			{
				if ( xValues_ == null ) 
				{
					throw new System.Exception( "xValues not set." );
				}
				if ( xValuesMax_ == null ) 
				{
					this.getMinMaxDate();				
				}
				return (double)xValuesMax_;
			}
		}

		#endregion

		#region get YValuesMin YValuesMax

		/// <summary>
		/// Returns the minimum and maximum values in an array.
		/// Ctr C Ctr V from ArrayAdapter. Naughty Matt.
		/// </summary>
		/// <param name="a">The array to search.</param>
		/// <param name="min">The minimum value.</param>
		/// <param name="max">The maximum value.</param>
		/// <returns>A logical value indicating if the input arrya is null or has 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;

		}

		private object yValuesMin_ = null;
		private object yValuesMax_ = null;

		/// <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

		private double[] yValues_;
		private DateTime[] xValues_;

		private object start_;
		private object step_;

	}
}
