/*
ScPl - A plotting library for .NET

AxesConstraint.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: AxesConstraint.cs,v 1.5 2004/06/24 11:10:31 mhowlett Exp $

*/

using System;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace scpl
{
	public abstract class AxesConstraint
	{
		// todo: polish these. 
		// make apply to second axes too.

		#region class XPixelWorldLength
		public class XPixelWorldLength : AxesConstraint
		{
			double pWorldLength_ = 0.0f;
			object holdFixedY_ = null;

			public XPixelWorldLength( double p )
			{
				this.pWorldLength_ = p;
			}
		
			public XPixelWorldLength( double p, PlotSurface2D.YAxisPosition holdFixedY )
			{
				this.pWorldLength_ = p;
				this.holdFixedY_ = holdFixedY;
			}

			public override void ApplyConstraint( 
				ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
				ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 )
			{
				double desiredLength = pXAxis1.Axis.Length / (double)this.pWorldLength_;
				double currentLength = pXAxis1.Length;
				double delta = currentLength-desiredLength;

				int changeLeft = (int)delta/2;
				int changeRight = (int)delta/2;
				if (this.holdFixedY_ != null)
				{
					if ( (PlotSurface2D.YAxisPosition)this.holdFixedY_ == PlotSurface2D.YAxisPosition.Left )
					{
						changeLeft = 0;
						changeRight = (int)delta;
					}
					else
					{
						changeLeft = (int)delta;
						changeRight = 0;
					}
				}

				pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X+changeLeft, pXAxis1.PhysicalMin.Y );
				pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X-changeRight, pXAxis1.PhysicalMax.Y );
				pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X+changeLeft, pXAxis2.PhysicalMin.Y );
				pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X-changeRight, pXAxis2.PhysicalMax.Y );

				pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X+changeLeft, pYAxis1.PhysicalMin.Y );
				pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X+changeLeft, pYAxis1.PhysicalMax.Y );
				pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X-changeRight, pYAxis2.PhysicalMin.Y );
				pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X-changeRight, pYAxis2.PhysicalMax.Y );
		
			}
		}
		#endregion
		#region class YPixelWorldLength
		public class YPixelWorldLength : AxesConstraint
		{
			float pWorldLength_ = 0.0f;
			object holdFixedX_ = null;

			public YPixelWorldLength( float p )
			{
				this.pWorldLength_ = p;
			}
		
			public YPixelWorldLength( float p, PlotSurface2D.XAxisPosition holdFixedX )
			{
				this.pWorldLength_ = p;
				this.holdFixedX_ = holdFixedX;
			}

			public override void ApplyConstraint( 
				ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
				ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 )
			{

				double desiredLength = pYAxis1.Axis.Length / (double)this.pWorldLength_;
				double currentLength = pYAxis1.Length;
				double delta = currentLength-desiredLength;

				int changeBottom = -(int)delta/2;
				int changeTop = -(int)delta/2;
				if (this.holdFixedX_ != null)
				{
					if ( (PlotSurface2D.XAxisPosition)this.holdFixedX_ == PlotSurface2D.XAxisPosition.Bottom )
					{
						changeBottom = 0;
						changeTop = -(int)delta;
					}
					else
					{
						changeBottom = -(int)delta;
						changeTop = 0;
					}
				}

				pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y+changeBottom );
				pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X, pYAxis1.PhysicalMax.Y-changeTop );
				pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X, pYAxis2.PhysicalMin.Y+changeBottom );
				pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X, pYAxis2.PhysicalMax.Y-changeTop );

				pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMin.Y+changeBottom );
				pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X, pXAxis1.PhysicalMax.Y+changeBottom );
				pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X, pXAxis2.PhysicalMin.Y-changeTop );
				pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X, pXAxis2.PhysicalMax.Y-changeTop );

			}
		}
		#endregion
		#region class AxisPosition
		public class AxisPosition : AxesConstraint
		{
			private object xAxisPosition_;
			private object yAxisPosition_;
			private float position_;

			public AxisPosition( PlotSurface2D.XAxisPosition axis, float position )
			{
				position_ = position;
				xAxisPosition_ = axis;
			}

			public AxisPosition( PlotSurface2D.YAxisPosition axis, float position )
			{
				position_ = position;
				yAxisPosition_ = axis;
			}

			public override void ApplyConstraint( 
				ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
				ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 )
			{
				if ( xAxisPosition_ != null )
				{
					if ((PlotSurface2D.XAxisPosition)xAxisPosition_ == PlotSurface2D.XAxisPosition.Bottom)
					{
						pXAxis1.PhysicalMin = new Point( (int)pXAxis1.PhysicalMin.X, (int)(float)this.position_ );
						pXAxis1.PhysicalMax = new Point( (int)pXAxis1.PhysicalMax.X, (int)(float)this.position_ );
			
						pYAxis1.PhysicalMin = new Point( (int)pYAxis1.PhysicalMin.X, (int)(float)this.position_ );
						pYAxis2.PhysicalMin = new Point( (int)pYAxis2.PhysicalMin.X, (int)(float)this.position_ );
					}
					else
					{
						pXAxis2.PhysicalMin = new Point( (int)pXAxis2.PhysicalMin.X, (int)(float)this.position_ );
						pXAxis2.PhysicalMax = new Point( (int)pXAxis2.PhysicalMax.X, (int)(float)this.position_ );

						pYAxis1.PhysicalMax = new Point( (int)pYAxis1.PhysicalMax.X, (int)(float)this.position_ );
						pYAxis2.PhysicalMax = new Point( (int)pYAxis2.PhysicalMax.X, (int)(float)this.position_ );
					}
				}
				else if (yAxisPosition_ != null )
				{
					if ((PlotSurface2D.YAxisPosition)yAxisPosition_ == PlotSurface2D.YAxisPosition.Left)
					{
						pYAxis1.PhysicalMin = new Point( (int)(float)this.position_, (int)pYAxis1.PhysicalMin.Y );
						pYAxis1.PhysicalMax = new Point( (int)(float)this.position_, (int)pYAxis1.PhysicalMax.Y );

						pXAxis1.PhysicalMin = new Point( (int)(float)this.position_, (int)pXAxis1.PhysicalMin.Y );
						pXAxis2.PhysicalMin = new Point( (int)(float)this.position_, (int)pXAxis2.PhysicalMin.Y );
					}
					else
					{
						pYAxis2.PhysicalMin = new Point( (int)(float)this.position_, (int)pYAxis2.PhysicalMin.Y );
						pYAxis2.PhysicalMax = new Point( (int)(float)this.position_, (int)pYAxis2.PhysicalMax.Y );
				
						pXAxis1.PhysicalMax = new Point( (int)(float)this.position_, (int)pXAxis1.PhysicalMax.Y );
						pXAxis2.PhysicalMax = new Point( (int)(float)this.position_, (int)pXAxis2.PhysicalMax.Y );
					}
				}

			}

		}
		#endregion
		#region class AspectRatio
		/// <summary>
		/// Axes Constraint that forces the world width and height pixel lengths
		/// to be at the provided ratio. For example, an aspect ratio of 3:2 or
		/// 1.5 indicates that there should be 1.5 times as many pixels per fixed
		/// world length along the x direction than for the same world length along
		/// the y direction. In other words, the world length of one pixel along 
		/// the x direction is 2/3rds that of the world length of one pixel height
		/// in the y direction.
		/// </summary>
		/// <remarks>
		/// This class will never increase the size of the plot bounding box. It 
		/// will always be made smaller.
		/// </remarks>
		public class AspectRatio : AxesConstraint
		{
			private double a_;
			private object holdFixedX_ = null;
			private object holdFixedY_ = null;

			/// <summary>
			/// Constructor.
			/// </summary>
			/// <param name="a">Aspect Ratio</param>
			public AspectRatio( double a )
			{
				this.a_ = a;
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="a">Aspect Ratio</param>
			/// <param name="holdFixedX">
			/// When adjusting the position of axes, the specified axis will never
			/// be moved.
			/// </param>
			public AspectRatio( double a, PlotSurface2D.XAxisPosition holdFixedX )
			{
				this.a_ = a;
				this.holdFixedX_ = holdFixedX;
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="a">Aspect Ratio</param>
			/// <param name="holdFixedY">
			/// When adjusting the position of axes, the 
			/// specified axis will never be moved.
			/// </param>
			public AspectRatio( double a, PlotSurface2D.YAxisPosition holdFixedY )
			{
				this.a_ = a;
				this.holdFixedY_ = holdFixedY;
			}

			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="a">Aspect Ratio</param>
			/// <param name="holdFixedX">
			/// When adjusting the position of axes, the specified axis will never
			/// be moved.
			/// </param>
			/// <param name="holdFixedY">
			/// When adjusting the position of axes, the specified axis will never
			/// be moved.
			/// </param>
			public AspectRatio( 
				double a,
				PlotSurface2D.XAxisPosition holdFixedX, 
				PlotSurface2D.YAxisPosition holdFixedY )
			{
				this.a_ = a;
				this.holdFixedX_ = holdFixedX;
				this.holdFixedY_ = holdFixedY;
			}
							
			/// <summary>
			/// 
			/// </summary>
			/// <param name="pXAxis1"></param>
			/// <param name="pYAxis1"></param>
			/// <param name="pXAxis2"></param>
			/// <param name="pYAxis2"></param>
			public override void ApplyConstraint( 
				ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
				ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 )
			{
				double xWorldRange = Math.Abs( (pXAxis1.Axis.WorldMax - pXAxis1.Axis.WorldMin) );
				double xPhysicalRange = Math.Abs( (pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X) );
				double xDirPixelSize =  xWorldRange / xPhysicalRange;
			
				double yWorldRange = Math.Abs( (pYAxis1.Axis.WorldMax - pYAxis1.Axis.WorldMin) );
				double yPhysicalRange = Math.Abs( (pYAxis1.PhysicalMax.Y - pYAxis1.PhysicalMin.Y) );
				double yDirPixelSize =  yWorldRange / yPhysicalRange;

				double currentAspectRatio = yDirPixelSize / xDirPixelSize;

				// we want to change the current aspect ratio to be the desired.
				// to do this, we may only add the world pixel lengths.

				if ( this.a_ > currentAspectRatio )
				{
					// want to increase aspect ratio. Therefore, want to add some amount
					// to yDirPixelSize (numerator).

					double toAdd = ( this.a_ - currentAspectRatio ) * xDirPixelSize;
					int newHeight =
						(int)(Math.Abs(pYAxis1.Axis.WorldMax - pYAxis1.Axis.WorldMin) / (yDirPixelSize + toAdd));
					int changeInHeight = (int)yPhysicalRange - newHeight;

					int changeBottom = changeInHeight/2;
					int changeTop = changeInHeight/2;
					if (this.holdFixedX_ != null)
					{
						if ( (PlotSurface2D.XAxisPosition)this.holdFixedX_ == PlotSurface2D.XAxisPosition.Bottom )
						{
							changeBottom = 0;
							changeTop = changeInHeight;
						}
						else
						{
							changeBottom = changeInHeight;
							changeTop = 0;
						}
					}

					pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y-changeBottom );
					pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X, pYAxis1.PhysicalMax.Y+changeTop );
					pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X, pYAxis2.PhysicalMin.Y-changeBottom );
					pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X, pYAxis2.PhysicalMax.Y+changeTop );

					pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMin.Y-changeBottom );
					pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X, pXAxis1.PhysicalMax.Y-changeBottom );
					pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X, pXAxis2.PhysicalMin.Y+changeTop );
					pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X, pXAxis2.PhysicalMax.Y+changeTop );

				}

				else 
				{

					// want to decrease aspect ratio. Therefore, want to add some amount
					// to xDirPixelSize (denominator).

					double toAdd = yDirPixelSize / this.a_ - xDirPixelSize;
					int newWidth = 
						(int)(Math.Abs(pXAxis1.Axis.WorldMax - pXAxis1.Axis.WorldMin) / (xDirPixelSize + toAdd));
					int changeInWidth = (int)xPhysicalRange - newWidth;

					int changeLeft = changeInWidth/2;
					int changeRight = changeInWidth/2;
					if (this.holdFixedY_ != null)
					{
						if ( (PlotSurface2D.YAxisPosition)this.holdFixedY_ == PlotSurface2D.YAxisPosition.Left )
						{
							changeLeft = 0;
							changeRight = changeInWidth;
						}
						else
						{
							changeLeft = changeInWidth;
							changeRight = 0;
						}
					}

					pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X+changeLeft, pXAxis1.PhysicalMin.Y );
					pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X-changeRight, pXAxis1.PhysicalMax.Y );
					pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X+changeLeft, pXAxis2.PhysicalMin.Y );
					pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X-changeRight, pXAxis2.PhysicalMax.Y );

					pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X+changeLeft, pYAxis1.PhysicalMin.Y );
					pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X+changeLeft, pYAxis1.PhysicalMax.Y );
					pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X-changeRight, pYAxis2.PhysicalMin.Y );
					pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X-changeRight, pYAxis2.PhysicalMax.Y );

				}

			}
/*
			public override void ApplyConstraint( 
				ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
				ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 )
			{
				double xWorldRange = Math.Abs( (pXAxis1.Axis.WorldMax - pXAxis1.Axis.WorldMin) );
				double xPhysicalRange = Math.Abs( (pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X) );
				double xDirPixelSize =  xWorldRange / xPhysicalRange;
			
				double yWorldRange = Math.Abs( (pYAxis1.Axis.WorldMax - pYAxis1.Axis.WorldMin) );
				double yPhysicalRange = Math.Abs( (pYAxis1.PhysicalMax.Y - pYAxis1.PhysicalMin.Y) );
				double yDirPixelSize =  yWorldRange / yPhysicalRange;

				// y Pixel Size bigger than x, so need to make x pixel size bigger (shorten axis)
				// SQUASH IN X DIRECTION
				if (yDirPixelSize > xDirPixelSize)
				{

					double thisMuchBigger = yDirPixelSize / xDirPixelSize;
					double newWidth = (pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X) / thisMuchBigger;
					double amountToModify = ((pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X) - newWidth) / 2.0;

					if (pXAxis1.PhysicalMin.X > pXAxis1.PhysicalMax.X)
					{
						amountToModify *= -1.0;
					}

					pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X+(float)amountToModify, pXAxis1.PhysicalMin.Y );
					pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X-(float)amountToModify, pXAxis1.PhysicalMax.Y );
					pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X+(float)amountToModify, pXAxis2.PhysicalMin.Y );
					pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X-(float)amountToModify, pXAxis2.PhysicalMax.Y );

					pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X+(float)amountToModify, pYAxis1.PhysicalMin.Y );
					pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X+(float)amountToModify, pYAxis1.PhysicalMax.Y );
					pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X-(float)amountToModify, pYAxis2.PhysicalMin.Y );
					pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X-(float)amountToModify, pYAxis2.PhysicalMax.Y );

				}

				// SQUASH IN Y DIRECTION
				else
				{

					double thisMuchBigger = xDirPixelSize / yDirPixelSize;
					double newHeight = (pYAxis1.PhysicalMax.Y - pYAxis1.PhysicalMin.Y) / thisMuchBigger;
					double amountToModify = ((pYAxis1.PhysicalMax.Y - pYAxis1.PhysicalMin.Y) - newHeight) / 2.0;

					if (pYAxis1.PhysicalMin.Y < pYAxis1.PhysicalMax.Y)
					{
						amountToModify *= -1.0;
					}

					pYAxis1.PhysicalMin = new PointF( pYAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y+(float)amountToModify );
					pYAxis1.PhysicalMax = new PointF( pYAxis1.PhysicalMax.X, pYAxis1.PhysicalMax.Y-(float)amountToModify );
					pYAxis2.PhysicalMin = new PointF( pYAxis2.PhysicalMin.X, pYAxis2.PhysicalMin.Y+(float)amountToModify );
					pYAxis2.PhysicalMax = new PointF( pYAxis2.PhysicalMax.X, pYAxis2.PhysicalMax.Y-(float)amountToModify );

					pXAxis1.PhysicalMin = new PointF( pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMin.Y+(float)amountToModify );
					pXAxis1.PhysicalMax = new PointF( pXAxis1.PhysicalMax.X, pXAxis1.PhysicalMax.Y+(float)amountToModify );
					pXAxis2.PhysicalMin = new PointF( pXAxis2.PhysicalMin.X, pXAxis2.PhysicalMin.Y-(float)amountToModify );
					pXAxis2.PhysicalMax = new PointF( pXAxis2.PhysicalMax.X, pXAxis2.PhysicalMax.Y-(float)amountToModify );

				}

			}
			*/
		}
		
		#endregion

		public abstract void ApplyConstraint( 
			ref PhysicalAxis pXAxis1, ref PhysicalAxis pYAxis1, 
			ref PhysicalAxis pXAxis2, ref PhysicalAxis pYAxis2 );
	}
}
