/*
ScPl - A plotting library for .NET

SequenceAdapter.cs
Copyright (C) 2003-2004
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.

*/

using System;
using System.Collections;
using System.Data;

namespace scpl
{
	public class SequenceAdapter
	{

		private object dataSource_ = null;
		private object valueData_ = null;
		private object abscissaData_ = null;

		private System.Data.DataRowCollection rows_ = null;

		private delegate int CountMethodDelegate();
		private delegate PointD IndexMethodDelegate( int i );
		private delegate Axis SuggestAxisDelegate();

		private CountMethodDelegate CountMethod = null;
		private IndexMethodDelegate IndexMethod = null;
		private SuggestAxisDelegate SuggestXAxisMethod = null;
		private SuggestAxisDelegate SuggestYAxisMethod = null;

		#region ConvertToDouble
		internal static double ConvertToDouble( object o )
		{
			if (o is DateTime)
			{
				return (double)(((DateTime)o).Ticks);
			}

			else if (o is IConvertible)
			{
				return System.Convert.ToDouble(o);
			}

			throw new System.Exception( "Invalid datatype" );
		}
		#endregion

		#region methods for dataSource == IList, valueData == null, abscissaData == null.
		private int count_IList_null_null()
		{
			return ((IList)this.dataSource_).Count;
		}

		private PointD data_IList_null_null( int i )
		{
			return new PointD( (double)i, ConvertToDouble(((IList)this.dataSource_)[i]) );
		}

		private Axis suggestXAxis_IList_null_null()
		{
			return new LinearAxis(0.0,(double)(((IList)this.dataSource_).Count)-1.0);
		}

		private Axis suggestYAxis_IList_null_null()
		{
			object min;
			object max;
			this.ArrayMinMax( (IList)this.dataSource_, out min, out max );

			if (min is DateTime)
			{
				min = (double)(((DateTime)min).Ticks);
				max = (double)(((DateTime)max).Ticks);
			}
			return new LinearAxis( ConvertToDouble(min), ConvertToDouble(max) );
		}

		#endregion
		#region methods for dataSource == null, valueData == IList, abscissaData == IList.
		private int count_null_IList_IList()
		{
			return ((IList)this.valueData_).Count;
		}

		private PointD data_null_IList_IList( int i )
		{
			double x = ConvertToDouble( ((IList)this.abscissaData_)[i] );
			double y = ConvertToDouble( ((IList)this.valueData_)[i] );
			return new PointD( x, y );
		}

		private Axis suggestXAxis_null_IList_IList()
		{
			IList abscissaData = (IList)this.abscissaData_;
			object min;
			object max;
			this.ArrayMinMax( abscissaData, out min, out max );
			if ( min is DateTime )
			{
				return new DateTimeAxis( (DateTime)min, (DateTime)max );
			}
			else
			{
				return new LinearAxis( ConvertToDouble(min), ConvertToDouble(max) );
			}
		}

		private Axis suggestYAxis_null_IList_IList()
		{
			IList valueData = (IList)this.valueData_;
			object min;
			object max;
			this.ArrayMinMax( valueData, out min, out max );
			if ( min is DateTime )
			{
				return new DateTimeAxis( (DateTime)min, (DateTime)max );
			}
			else
			{
				return new LinearAxis( ConvertToDouble(min), ConvertToDouble(max) );
			}
		}

		#endregion
		#region methods for dataSource == null, valueData == IList, abscissaData == StartStep.
		private int count_null_StartStep_IList()
		{
			return ((IList)this.valueData_).Count;
		}

		private PointD data_null_StartStep_IList( int i )
		{
			StartStep ss = (StartStep)this.abscissaData_;
			return new PointD(
				ss.Start + (double)i * ss.Step, 
				System.Convert.ToDouble(((IList)this.valueData_)[i]) 
				);
		}

		private Axis suggestXAxis_null_StartStep_IList()
		{
			StartStep ss = (StartStep)this.abscissaData_;
			return new LinearAxis( ss.Start, ss.Start + (double)(((IList)valueData_).Count-1) * ss.Step );
		}

		private Axis suggestYAxis_null_StartStep_IList()
		{
			object min;
			object max;
			this.ArrayMinMax( (IList)this.valueData_, out min, out max );
			return new LinearAxis( System.Convert.ToDouble(min), System.Convert.ToDouble(max) );
		}

		#endregion
		#region methods for dataSource == null, valueData == IList, abscissaData == null
		private int count_null_null_IList()
		{
			return ((IList)this.valueData_).Count;
		}

		private PointD data_null_null_IList( int i )
		{
			StartStep ss = (StartStep)this.abscissaData_;
			return new PointD(
				(double)i, 
				System.Convert.ToDouble(((IList)this.valueData_)[i]) 
				);
		}

		private Axis suggestXAxis_null_null_IList()
		{
			StartStep ss = (StartStep)this.abscissaData_;
			return new LinearAxis( 0.0, (double)(((IList)this.valueData_).Count-1) );
		}

		private Axis suggestYAxis_null_null_IList()
		{
			object min;
			object max;
			this.ArrayMinMax( (IList)this.valueData_, out min, out max );
			return new LinearAxis( System.Convert.ToDouble(min), System.Convert.ToDouble(max) );
		}

		#endregion
		#region methods for dataSource == null, valueData == null, abscissaData == null.
		private int count_null_null_null()
		{
			return 0;
		}

		private PointD data_null_null_null( int i )
		{
			throw new System.Exception( "SequenceAdapter: Attempting to access null data" );
		}

		private Axis suggestXAxis_null_null_null()
		{
			return new LinearAxis( 0.0, 1.0 );
		}

		private Axis suggestYAxis_null_null_null()
		{
			return new LinearAxis( 0.0, 1.0 );
		}
		#endregion
		#region methods for dataSource == Rows, valueData == string, abscissaData == string/null.
		private int count_Rows_string_string()
		{
			return this.rows_.Count;
		}

		private PointD data_Rows_string_string( int i )
		{
			double x = (double)i;
			if ( abscissaData_ != null )
			{
				x = ConvertToDouble(((DataRow)rows_[i])[(string)abscissaData_]);
			}

			double y = ConvertToDouble(((DataRow)rows_[i])[(string)valueData_]);

			return new PointD( x, y );
		}

		private Axis suggestXAxis_Rows_string_string()
		{
			if (abscissaData_ == null)
			{
				return new LinearAxis( 0.0, (double)(this.rows_.Count-1) );
			}
			else
			{
				object min;
				object max;
				RowArrayMinMax( this.rows_, out min, out max, (string)abscissaData_ );
				if ( min is DateTime )
				{
					return new DateTimeAxis( (DateTime)min, (DateTime)max );
				}
				else
				{
					return new LinearAxis( ConvertToDouble(min), ConvertToDouble(max) );
				}
			}
		}

		private Axis suggestYAxis_Rows_string_string()
		{
			object min;
			object max;
			RowArrayMinMax( this.rows_, out min, out max, (string)valueData_ );
			if ( min is DateTime )
			{
				return new DateTimeAxis( (DateTime)min, (DateTime)max );
			}
			else
			{
				return new LinearAxis( ConvertToDouble(min), ConvertToDouble(max) );
			}
		}
		#endregion

		#region InitDelegates
		private void InitDelegates(
			CountMethodDelegate countMethod,
			IndexMethodDelegate indexMethod,
			SuggestAxisDelegate suggestXAxisMethod,
			SuggestAxisDelegate suggestYAxisMethod )
		{
			this.CountMethod = countMethod;
			this.IndexMethod = indexMethod;
			this.SuggestXAxisMethod = suggestXAxisMethod;
			this.SuggestYAxisMethod = suggestYAxisMethod;
		}
		#endregion

		#region Constructor
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="dataSource">the plot DataSource</param>
		/// <param name="valueData">the plot ValueData</param>
		/// <param name="abscissaData">the plot AbscissaData</param>
		public SequenceAdapter( object dataSource, string dataMember, object valueData, object abscissaData )
		{
			this.dataSource_ = dataSource;
			this.valueData_ = valueData;
			this.abscissaData_ = abscissaData;


			// DataSource == null. 
			// -------------------

			if ((dataSource == null) && ((valueData is IList) && (abscissaData is IList)))
			{
				this.InitDelegates( 
					new CountMethodDelegate(this.count_null_IList_IList),
					new IndexMethodDelegate(this.data_null_IList_IList),
					new SuggestAxisDelegate(this.suggestXAxis_null_IList_IList),
					new SuggestAxisDelegate(this.suggestYAxis_null_IList_IList) 
					);
			}

			else if ((dataSource == null) && ((valueData is IList) && (abscissaData is StartStep)))
			{
				this.InitDelegates( 
					new CountMethodDelegate(this.count_null_StartStep_IList),
					new IndexMethodDelegate(this.data_null_StartStep_IList),
					new SuggestAxisDelegate(this.suggestXAxis_null_StartStep_IList),
					new SuggestAxisDelegate(this.suggestYAxis_null_StartStep_IList) 
					);
			}

			else if ((dataSource == null) && ((valueData is IList) && (abscissaData == null)))
			{
				this.InitDelegates( 
					new CountMethodDelegate(this.count_null_null_IList),
					new IndexMethodDelegate(this.data_null_null_IList),
					new SuggestAxisDelegate(this.suggestXAxis_null_null_IList),
					new SuggestAxisDelegate(this.suggestYAxis_null_null_IList) 
					);
			}

			else if ((dataSource == null) && ((valueData == null) && (abscissaData == null)))
			{
				this.InitDelegates( 
					new CountMethodDelegate(this.count_null_null_null),
					new IndexMethodDelegate(this.data_null_null_null),
					new SuggestAxisDelegate(this.suggestXAxis_null_null_null),
					new SuggestAxisDelegate(this.suggestYAxis_null_null_null) 
					);
			}

				// DataSource != null.

			else if ((dataSource is System.Collections.IList) && ((valueData == null) && (abscissaData == null)))
			{
				this.InitDelegates( 
					new CountMethodDelegate(this.count_IList_null_null),
					new IndexMethodDelegate(this.data_IList_null_null),
					new SuggestAxisDelegate(this.suggestXAxis_IList_null_null),
					new SuggestAxisDelegate(this.suggestYAxis_IList_null_null) 
					);
			}

			else if ((dataSource is System.ComponentModel.IListSource))
			{
				if ( dataSource is System.Data.DataSet )
				{
					//bool b = ((System.Data.DataSet)dataSource).GetList();
					
					if(dataMember != null)
					{
						// TODO error check
						this.rows_ = ((DataTable)((DataSet)dataSource).Tables[dataMember]).Rows;
					}
					else
					{
						// TODO error check
						this.rows_ = ((DataTable)((DataSet)dataSource).Tables[0]).Rows;
					}
				}

				else if (dataSource is System.Data.DataTable )
				{
					IList l = ((System.ComponentModel.IListSource)dataSource).GetList();
					this.rows_ = ((DataTable)dataSource).Rows;
				}

				else
				{
					throw new System.Exception ( "not implemented yet" );
				}

				this.InitDelegates( 
					new CountMethodDelegate(this.count_Rows_string_string),
					new IndexMethodDelegate(this.data_Rows_string_string),
					new SuggestAxisDelegate(this.suggestXAxis_Rows_string_string),
					new SuggestAxisDelegate(this.suggestYAxis_Rows_string_string) 
					);
			}

			else
			{
				throw new System.Exception( "SequenceAdapter: DataSource / ValueData / AbscissaData combination invalid" );
			}

		}
		#endregion

		#region Count
		public int Count
		{
			get
			{
				return this.CountMethod();
			}
		}
		#endregion
		#region this[int i]
		public PointD this[int i] 
		{
			get
			{
				return this.IndexMethod(i);
			}
		}
		#endregion
		#region SuggestXAxis
		public Axis SuggestXAxis()
		{
			return this.SuggestXAxisMethod();
		}
		#endregion
		#region SuggestYAxis
		public Axis SuggestYAxis()
		{
			
			Axis a = this.SuggestYAxisMethod();

			double range = a.WorldMax - a.WorldMin;
			
			if ( range > 0.0F )
			{
				range *= 0.08F;
			}
			else
			{
				range = 0.01F;
			}

			a.WorldMax += range;
			a.WorldMin -= range;

			return a;
		}
		#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>
		private bool ArrayMinMax( IList 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.Count == 0 )
			{
				min = null;
				max = null;
				return false;
			}

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

		}
		#endregion

		#region RowArrayMinMax
		/// <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>
		internal static bool RowArrayMinMax( DataRowCollection rows, 
			out object min, out object max, string columnName )
		{
			// 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 ( rows == null || rows.Count == 0 )
			{
				min = null;
				max = null;
				return false;
			}

			min = (rows[0])[columnName];
			max = (rows[0])[columnName];

			foreach ( DataRow r in rows ) 
			{
				IComparable e = (IComparable)r[columnName];

				if (e.CompareTo(min) < 0)
				{
					min = e;
				}
			
				if (e.CompareTo(max) > 0) 
				{
					max = e;
				}
			}
			return true;

		}
		#endregion

	}
}
