/******************************************************************************  		
 * Company:         SINED GmbH
 * Author:          Maksim Kulagin, Andriy Kurhanevych
 * Created:         4.10.2004
 * Last modified:   $Modtime: 18.05.07 12:16 $
 * Purpose:         Declaration and implementation of the Rectangle class
 ******************************************************************************/

#ifndef POLYGONLIBRARY_RECTANGLE_H
#define POLYGONLIBRARY_RECTANGLE_H

#include <limits>
#include "../../Collections/Exception.h"

class Point;
namespace PolygonLibrary
{

	/// Represents a rectangle whose sides are parallel to the coordinate axises 
	/// and therefore can represent a bounding box of a geometric object.
	class Rectangle
	{

	// ------------------------------ Interface -------------------------------
	public:
		// --- Constructors --- 
		
		/// Constructs an empty Rectangle containing no point. 
		/// Summation of the empty rectangle with an other rectangle or expanding to include it 
		/// results in the last one. 
		Rectangle() : m_dMinX(DBL_MAX), m_dMinY(DBL_MAX), m_dMaxX(-DBL_MAX), m_dMaxY(-DBL_MAX) {};
		
		/// Constructs a Rectangle with the specified bounds.
		/// @exception Exceptions::ArgumentException if dMinX > dMaxX or dMinY > dMaxY 
		Rectangle(double dMinX, double dMinY, double dMaxX, double dMaxY) : 
			m_dMinX(dMinX), m_dMinY(dMinY), m_dMaxX(dMaxX), m_dMaxY(dMaxY) 
		{
			if(m_dMinX > m_dMaxX || m_dMinY > m_dMaxY)
				THROW_EXCEPTION(Exceptions::ArgumentException, "Invalid bounds: minX > maxX or minY > maxY");
		}
		
		/// Constructs a smallest rectangle containing the specified points.
		Rectangle(const Point& point1, const Point& point2)
		{
			if(point1.X() < point2.X()) {
				m_dMinX = point1.X();
				m_dMaxX = point2.X();
			} else {
				m_dMinX = point2.X();
				m_dMaxX = point1.X();
			}

			if(point1.Y() < point2.Y()) {
				m_dMinY = point1.Y();
				m_dMaxY = point2.Y();
			} else {
				m_dMinY = point2.Y();
				m_dMaxY = point1.Y();
			}
		}
		
		// --- Attributes --- 
		
		/// Returns the x-coordinate of the left side of the rectangle.  
		double MinX() const {return m_dMinX;}
		
		/// Returns the y-coordinate of the bottom side of the rectangle. 
		double MinY() const {return m_dMinY;}
		
		/// Returns the x-coordinate of the right side of the rectangle.
		double MaxX() const {return m_dMaxX;}
		
		/// Returns the y-coordinate of the top side of the rectangle.
		double MaxY() const {return m_dMaxY;}
		
		/// Returns the width of the rectangle.
		double Width() const { return m_dMaxX - m_dMinX; }
		
		/// Returns the height of the rectangle.
		double Height() const { return m_dMaxY - m_dMinY; }
		
		/// Returns the area of the rectangle.
		double Area() const {return Width() * Height();}
		
		/// Indicates whether the rectangle is empty: contains no point, 
		/// and its summation with an other rectangle or expanding to include it results in the last one.  
		bool IsEmpty() { return m_dMinX > m_dMaxX; }
		
		// --- operations ---
		
		/// Returns a Rectangle representing the intersection of the specified rectangles
		static Rectangle Intersection(const Rectangle& rect1, const Rectangle& rect2) 
		{
			Rectangle rResult(false);
			rResult.m_dMinX = (rect1.m_dMinX > rect2.m_dMinX) ? rect1.m_dMinX : rect2.m_dMinX; 
			rResult.m_dMinY = (rect1.m_dMinY > rect2.m_dMinY) ? rect1.m_dMinY : rect2.m_dMinY;
			rResult.m_dMaxX = (rect1.m_dMaxX < rect2.m_dMaxX) ? rect1.m_dMaxX : rect2.m_dMaxX;
			rResult.m_dMaxY = (rect1.m_dMaxY < rect2.m_dMaxY) ? rect1.m_dMaxY : rect2.m_dMaxY;
			if(rResult.m_dMinX > rResult.m_dMaxX || rResult.m_dMinY > rResult.m_dMaxY) {
				return Rectangle(); // empty intersection
			} else {
				return rResult;
			}
		}
		
		/// Returns the smallest rectangle containing the rectangles. 
		Rectangle operator + (const Rectangle& rect) const
		{
			Rectangle rResult(false);
			rResult.m_dMinX = (rect.m_dMinX < m_dMinX) ? rect.m_dMinX : m_dMinX;
			rResult.m_dMinY = (rect.m_dMinY < m_dMinY) ? rect.m_dMinY : m_dMinY;
			rResult.m_dMaxX = (rect.m_dMaxX > m_dMaxX) ? rect.m_dMaxX : m_dMaxX;
			rResult.m_dMaxY = (rect.m_dMaxY > m_dMaxY) ? rect.m_dMaxY : m_dMaxY;
			return rResult;
		}
		
	
		/// Expands the rectangle minimally to include the specified point.
		void ExpandToInclude(const Point& point)
		{
			if(IsEmpty()) {
				m_dMinX = m_dMaxX = point.X();
				m_dMinY = m_dMaxY = point.Y();
			} 
			else 
			{
				if(point.X() < m_dMinX) {
					m_dMinX = point.X();
				}
				else if(point.X() > m_dMaxX) {
					m_dMaxX = point.X();
				} 
				
				if(point.Y() < m_dMinY) {
					m_dMinY = point.Y();
				}
				else if(point.Y() > m_dMaxY) {
					m_dMaxY = point.Y();
				}
			}
		}
		
		/// Expands the rectangle minimally to include the specified rectangle.
		void ExpandToInclude(const Rectangle& rect)
		{
			if(rect.m_dMinX < m_dMinX) {
				m_dMinX = rect.m_dMinX;
			}
			if(rect.m_dMinY < m_dMinY) {
				m_dMinY = rect.m_dMinY;
			}
			if(rect.m_dMaxX > m_dMaxX) {
				m_dMaxX = rect.m_dMaxX;
			}
			if(rect.m_dMaxY > m_dMaxY) {
				m_dMaxY = rect.m_dMaxY;
			}
		}
		
		/// Moves each bound of this rectangle by the specified value in the direction from the rectangle inside.
		/// If the specified expansion is negative then the rectangle is reduced and becomes empty if
		/// the expansion modulus is greater then half of the rectangle width or height.
		void ExpandBy(double dExpansion)
		{
			m_dMinX -= dExpansion;
			m_dMinY -= dExpansion;
			m_dMaxX += dExpansion;
			m_dMaxY += dExpansion;
			if(m_dMinX > m_dMaxX || m_dMinY > m_dMaxY) {
				*this = Rectangle(); // empty rectangle
			}
		}

		/// Determines whether the rectangle contains the specified point.
		bool Contains(const Point& point) const 
		{
			return (point.X() >= m_dMinX && point.X() <= m_dMaxX && point.Y() >= m_dMinY && point.Y() <= m_dMaxY);
		}

		/// Determines whether the rectangle contains the specified rectangle.
		bool Contains(const Rectangle& rect) const 
		{
			return (rect.m_dMinX >= m_dMinX && rect.m_dMaxX <= m_dMaxX && rect.m_dMinY >= m_dMinY && rect.m_dMaxY <= m_dMaxY);
		}
		
		/// Determines whether the rectangle overlaps the specified rectangle (the intersection area > 0).
		bool Overlaps(const Rectangle& rect) const
		{
			return !(rect.m_dMinX >= m_dMaxX || rect.m_dMaxX <= m_dMinX ||
				rect.m_dMinY >= m_dMaxY || rect.m_dMaxY <= m_dMinY);
		}

		/// Determines whether the rectangle intersects the specified rectangle (the rectangles have common points).
		bool Intersects(const Rectangle& rect) const
		{
			return !(rect.m_dMinX > m_dMaxX || rect.m_dMaxX < m_dMinX ||
				rect.m_dMinY > m_dMaxY || rect.m_dMaxY < m_dMinY);
		}
		
		/// Determines whether the rectangle intersects the specified rectangle 
		/// considering the specified tolerance distance.
		bool Intersects(const Rectangle& rect, double dTolerance) const
		{
			return !(rect.m_dMinX > m_dMaxX + dTolerance || rect.m_dMaxX < m_dMinX - dTolerance ||
				rect.m_dMinY > m_dMaxY + dTolerance || rect.m_dMaxY < m_dMinY - dTolerance);
		}
	
	// --- Implementation ---  
	private:
		// constructor that does not initialize fields
		Rectangle(bool) {};
		
		double m_dMinX;
		double m_dMinY;
		double m_dMaxX;
		double m_dMaxY;
	};

}
#endif