using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common;
using System.Windows.Markup;
using System.Globalization;
using System.ComponentModel;

namespace Microsoft.Research.DynamicDataDisplay
{
	/// <summary>
	/// Describes a rectangle in viewport or data coordinates.
	/// </summary>
	[Serializable]
	[ValueSerializer(typeof(DataRectSerializer))]
	[TypeConverter(typeof(DataRectConverter))]
	public struct DataRect : IEquatable<DataRect>, IFormattable
	{
		#region Ctors

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="rect">Source rect.</param>
		public DataRect(Rect rect)
		{
			xMin = rect.X;
			yMin = rect.Y;
			width = rect.Width;
			height = rect.Height;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="size">The size.</param>
		public DataRect(Size size)
		{
			if (size.IsEmpty)
			{
				this = emptyRect;
			}
			else
			{
				xMin = yMin = 0.0;
				width = size.Width;
				height = size.Height;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="location">The location.</param>
		/// <param name="size">The size.</param>
		public DataRect(Point location, Size size)
		{
			if (size.IsEmpty)
			{
				this = emptyRect;
			}
			else
			{
				xMin = location.X;
				yMin = location.Y;
				width = size.Width;
				height = size.Height;
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="point1">The point1.</param>
		/// <param name="point2">The point2.</param>
		public DataRect(Point point1, Point point2)
		{
			xMin = Math.Min(point1.X, point2.X);
			yMin = Math.Min(point1.Y, point2.Y);
			width = Math.Max((double)(Math.Max(point1.X, point2.X) - xMin), 0);
			height = Math.Max((double)(Math.Max(point1.Y, point2.Y) - yMin), 0);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="point">The point.</param>
		/// <param name="vector">The vector.</param>
		public DataRect(Point point, Vector vector) : this(point, point + vector) { }

		/// <summary>
		/// Initializes a new instance of the <see cref="DataRect"/> struct.
		/// </summary>
		/// <param name="xMin">The minimal x.</param>
		/// <param name="yMin">The minimal y.</param>
		/// <param name="width">The width.</param>
		/// <param name="height">The height.</param>
		public DataRect(double xMin, double yMin, double width, double height)
		{
			if ((width < 0) || (height < 0))
				throw new ArgumentException(Strings.Exceptions.WidthAndHeightCannotBeNegative);

			this.xMin = xMin;
			this.yMin = yMin;
			this.width = width;
			this.height = height;
		}

		#endregion

		#region Static

		/// <summary>
		/// Creates the DataRect from minimal and maximal 'x' and 'y' coordinates.
		/// </summary>
		/// <param name="xMin">The x min.</param>
		/// <param name="yMin">The y min.</param>
		/// <param name="xMax">The x max.</param>
		/// <param name="yMax">The y max.</param>
		/// <returns></returns>
		public static DataRect Create(double xMin, double yMin, double xMax, double yMax)
		{
			DataRect rect = new DataRect(xMin, yMin, xMax - xMin, yMax - yMin);
			return rect;
		}

		public static DataRect FromPoints(double x1, double y1, double x2, double y2)
		{
			return new DataRect(new Point(x1, y1), new Point(x2, y2));
		}

		public static DataRect FromCenterSize(Point center, double width, double height)
		{
			DataRect rect = new DataRect(center.X - width / 2, center.Y - height / 2, width, height);
			return rect;
		}

		public static DataRect FromCenterSize(Point center, Size size)
		{
			return FromCenterSize(center, size.Width, size.Height);
		}

		public static DataRect Intersect(DataRect rect1, DataRect rect2)
		{
			rect1.Intersect(rect2);
			return rect1;
		}

		public static implicit operator DataRect(Rect rect)
		{
			return new DataRect(rect);
		}

		#endregion

		public Rect ToRect()
		{
			return new Rect(xMin, yMin, width, height);
		}

		public void Intersect(DataRect rect)
		{
			if (!IntersectsWith(rect))
			{
				this = DataRect.Empty;
				return;
			}

			DataRect res = new DataRect();

			double x = Math.Max(this.XMin, rect.XMin);
			double y = Math.Max(this.YMin, rect.YMin);
			res.width = Math.Max((double)(Math.Min(this.XMax, rect.XMax) - x), 0.0);
			res.height = Math.Max((double)(Math.Min(this.YMax, rect.YMax) - y), 0.0);
			res.xMin = x;
			res.yMin = y;

			this = res;
		}

		public bool IntersectsWith(DataRect rect)
		{
			if (IsEmpty || rect.IsEmpty)
				return false;

			return ((((rect.XMin <= this.XMax) && (rect.XMax >= this.XMin)) && (rect.YMax >= this.YMin)) && (rect.YMin <= this.YMax));
		}

		private double xMin;
		private double yMin;
		private double width;
		private double height;

		/// <summary>
		/// Gets a value indicating whether this instance is empty.
		/// </summary>
		/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
		public bool IsEmpty
		{
			get { return width < 0; }
		}

		/// <summary>
		/// Gets the bottom.
		/// </summary>
		/// <value>The bottom.</value>
		public double YMin
		{
			get { return yMin; }
			set
			{
				if (this.IsEmpty)
					throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

				yMin = value;
			}
		}

		/// <summary>
		/// Gets the maximal y value.
		/// </summary>
		/// <value>The top.</value>
		public double YMax
		{
			get
			{
				if (IsEmpty)
					return Double.PositiveInfinity;

				return yMin + height;
			}
		}

		/// <summary>
		/// Gets the minimal x value.
		/// </summary>
		/// <value>The left.</value>
		public double XMin
		{
			get { return xMin; }
			set
			{
				if (this.IsEmpty)
					throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

				xMin = value;
			}
		}

		/// <summary>
		/// Gets the maximal x value.
		/// </summary>
		/// <value>The right.</value>
		public double XMax
		{
			get
			{
				if (IsEmpty)
					return Double.PositiveInfinity;

				return xMin + width;
			}
		}

		/// <summary>
		/// Gets or sets the location.
		/// </summary>
		/// <value>The location.</value>
		public Point Location
		{
			get { return new Point(xMin, yMin); }
			set
			{
				if (IsEmpty)
					throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

				xMin = value.X;
				yMin = value.Y;
			}
		}

		public Point XMaxYMax
		{
			get { return new Point(XMax, YMax); }
		}

		public Point XMinYMin
		{
			get { return new Point(xMin, yMin); }
		}

		/// <summary>
		/// Gets or sets the size.
		/// </summary>
		/// <value>The size.</value>
		public Size Size
		{
			get
			{
				if (IsEmpty)
					return Size.Empty;

				return new Size(width, height);
			}
			set
			{
				if (value.IsEmpty)
				{
					this = emptyRect;
				}
				else
				{
					if (IsEmpty)
						throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

					width = value.Width;
					height = value.Height;
				}
			}
		}

		public double Width
		{
			get { return width; }
			set
			{
				if (this.IsEmpty)
					throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
				if (value < 0)
					throw new ArgumentOutOfRangeException(Strings.Exceptions.DataRectSizeCannotBeNegative);

				width = value;
			}
		}

		public double Height
		{
			get { return height; }
			set
			{
				if (this.IsEmpty)
					throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
				if (value < 0)
					throw new ArgumentOutOfRangeException(Strings.Exceptions.DataRectSizeCannotBeNegative);

				height = value;
			}
		}

		private static readonly DataRect emptyRect = CreateEmptyRect();

		public static DataRect Empty
		{
			get { return DataRect.emptyRect; }
		}

		private static DataRect CreateEmptyRect()
		{
			DataRect rect = new DataRect();
			rect.xMin = Double.PositiveInfinity;
			rect.yMin = Double.PositiveInfinity;
			rect.width = Double.NegativeInfinity;
			rect.height = Double.NegativeInfinity;
			return rect;
		}

		private static readonly DataRect infinite = new DataRect(Double.MinValue / 2, Double.MinValue / 2, Double.MaxValue, Double.MaxValue);
		public static DataRect Infinite
		{
			get { return infinite; }
		}

		#region Object overrides

		/// <summary>
		/// Indicates whether this instance and a specified object are equal.
		/// </summary>
		/// <param name="obj">Another object to compare to.</param>
		/// <returns>
		/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
		/// </returns>
		public override bool Equals(object obj)
		{
			if (obj == null)
				return false;

			if (!(obj is DataRect))
				return false;

			DataRect other = (DataRect)obj;

			return Equals(other);
		}

		/// <summary>
		/// Returns the hash code for this instance.
		/// </summary>
		/// <returns>
		/// A 32-bit signed integer that is the hash code for this instance.
		/// </returns>
		public override int GetHashCode()
		{
			if (IsEmpty)
				return 0;

			return xMin.GetHashCode() ^
					width.GetHashCode() ^
					yMin.GetHashCode() ^
					height.GetHashCode();
		}

		/// <summary>
		/// Returns the fully qualified type name of this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="T:System.String"/> containing a fully qualified type name.
		/// </returns>
		public override string ToString()
		{
			if (IsEmpty)
				return "Empty";

			return String.Format("({0};{1}) -> {2}*{3}", xMin, yMin, width, height);
		}

		/// <summary>
		/// Implements the operator ==.
		/// </summary>
		/// <param name="rect1">The rect1.</param>
		/// <param name="rect2">The rect2.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator ==(DataRect rect1, DataRect rect2)
		{
			return rect1.Equals(rect2);
		}

		/// <summary>
		/// Implements the operator !=.
		/// </summary>
		/// <param name="rect1">The rect1.</param>
		/// <param name="rect2">The rect2.</param>
		/// <returns>The result of the operator.</returns>
		public static bool operator !=(DataRect rect1, DataRect rect2)
		{
			return !rect1.Equals(rect2);
		}

		public static bool EqualEps(DataRect rect1, DataRect rect2, double eps)
		{
			double width = Math.Min(rect1.Width, rect2.Width);
			double height = Math.Min(rect1.Height, rect2.Height);
			return Math.Abs(rect1.XMin - rect2.XMin) < width * eps &&
				   Math.Abs(rect1.XMax - rect2.XMax) < width * eps &&
				   Math.Abs(rect1.YMin - rect2.YMin) < height * eps &&
				   Math.Abs(rect1.YMax - rect2.YMax) < height * eps;
		}

		#endregion

		#region IEquatable<DataRect> Members

		/// <summary>
		/// Indicates whether the current object is equal to another object of the same type.
		/// </summary>
		/// <param name="other">An object to compare with this object.</param>
		/// <returns>
		/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
		/// </returns>
		public bool Equals(DataRect other)
		{
			if (this.IsEmpty)
				return other.IsEmpty;

			return xMin == other.xMin &&
					width == other.width &&
					yMin == other.yMin &&
					height == other.height;
		}

		#endregion

		/// <summary>
		/// Determines whether this DataRect contains point with specified coordinates.
		/// </summary>
		/// <param name="x">The x coordinate of point.</param>
		/// <param name="y">The y coordinate of point.</param>
		/// <returns>
		/// 	<c>true</c> if contains point with specified coordinates; otherwise, <c>false</c>.
		/// </returns>
		public bool Contains(double x, double y)
		{
			if (this.IsEmpty)
				return false;

			return x >= xMin &&
				x <= XMax &&
				y >= yMin &&
				y <= YMax;
		}

		public bool Contains(Point point)
		{
			return Contains(point.X, point.Y);
		}

		public bool Contains(DataRect rect)
		{
			if (this.IsEmpty || rect.IsEmpty)
				return false;

			return
				this.xMin <= rect.xMin &&
				this.yMin <= rect.yMin &&
				this.XMax >= rect.XMax &&
				this.YMax >= rect.YMax;
		}

		public void Offset(Vector offsetVector)
		{
			if (this.IsEmpty)
				throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

			this.xMin += offsetVector.X;
			this.yMin += offsetVector.Y;
		}

		public void Offset(double offsetX, double offsetY)
		{
			if (this.IsEmpty)
				throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);

			this.xMin += offsetX;
			this.yMin += offsetY;
		}

		public static DataRect Offset(DataRect rect, double offsetX, double offsetY)
		{
			rect.Offset(offsetX, offsetY);
			return rect;
		}

		internal void UnionFinite(DataRect rect)
		{
			if (!rect.IsEmpty)
			{
				if (rect.xMin.IsInfinite())
					rect.xMin = 0;
				if (rect.yMin.IsInfinite())
					rect.yMin = 0;
				if (rect.width.IsInfinite())
					rect.width = 0;
				if (rect.height.IsInfinite())
					rect.height = 0;
			}

			Union(rect);
		}

		public void Union(DataRect rect)
		{
			if (IsEmpty)
			{
				this = rect;
				return;
			}
			else if (!rect.IsEmpty)
			{
				double minX = Math.Min(xMin, rect.xMin);
				double minY = Math.Min(yMin, rect.yMin);

				if (rect.width == Double.PositiveInfinity || this.width == Double.PositiveInfinity)
				{
					this.width = Double.PositiveInfinity;
				}
				else
				{
					double maxX = Math.Max(XMax, rect.XMax);
					this.width = Math.Max(maxX - minX, 0.0);
				}

				if (rect.height == Double.PositiveInfinity || this.height == Double.PositiveInfinity)
				{
					this.height = Double.PositiveInfinity;
				}
				else
				{
					double maxY = Math.Max(YMax, rect.YMax);
					this.height = Math.Max(maxY - minY, 0.0);
				}

				this.xMin = minX;
				this.yMin = minY;
			}
		}

		public void Union(Point point)
		{
			this.Union(new DataRect(point, point));
		}

		public static DataRect Union(DataRect rect, Point point)
		{
			rect.Union(point);

			return rect;
		}

		public static DataRect Union(DataRect rect1, DataRect rect2)
		{
			rect1.Union(rect2);

			return rect1;
		}

		internal string ConvertToString(string format, IFormatProvider provider)
		{
			if (IsEmpty)
				return "Empty";

			char listSeparator = TokenizerHelper.GetNumericListSeparator(provider);
			return String.Format(provider, "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}{0}{4:" + format + "}", listSeparator, xMin, yMin, width, height);
		}

		/// <summary>
		/// Parses the specified string as a DataRect.
		/// </summary>
		/// <remarks>
		/// There are three possible string patterns, that are recognized as string representation of DataRect:
		/// 1) Literal string "Empty" - represents an DataRect.Empty rect;
		/// 2) String in format "d,d,d,d", where d is a floating-point number with '.' as decimal separator - is considered as a string 
		/// of "XMin,YMin,Width,Height";
		/// 3) String in format "d,d d,d", where d is a floating-point number with '.' as decimal separator - is considered as a string
		/// of "XMin,YMin XMax,YMax".
		/// </remarks>
		/// <param name="source">The source.</param>
		/// <returns>DataRect, parsed from the given input string.</returns>
		public static DataRect Parse(string source)
		{
			DataRect rect;
			IFormatProvider cultureInfo = CultureInfo.GetCultureInfo("en-us");

			if (source == "Empty")
			{
				rect = DataRect.Empty;
			}
			else
			{
				// format X,Y,Width,Height
				string[] values = source.Split(',');
				if (values.Length == 4)
				{
					rect = new DataRect(
						Convert.ToDouble(values[0], cultureInfo),
						Convert.ToDouble(values[1], cultureInfo),
						Convert.ToDouble(values[2], cultureInfo),
						Convert.ToDouble(values[3], cultureInfo)
						);
				}
				else
				{
					// format XMin, YMin - XMax, YMax
					values = source.Split(new Char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
					rect = DataRect.Create(
						Convert.ToDouble(values[0], cultureInfo),
						Convert.ToDouble(values[1], cultureInfo),
						Convert.ToDouble(values[2], cultureInfo),
						Convert.ToDouble(values[3], cultureInfo)
						);
				}
			}

			return rect;
		}

		#region IFormattable Members

		string IFormattable.ToString(string format, IFormatProvider formatProvider)
		{
			return ConvertToString(format, formatProvider);
		}

		#endregion
	}
}
