#pragma once

#include "Vector2.h"

#include <algorithm>

#include <Containers/Tags.h>

namespace nCine
{
	inline namespace Primitives
	{
		using Death::Containers::NoInitT;

		/// Rectangle in a two dimensional space
		template<class T>
		class Rect
		{
		public:
			/// Top-left X coordinate as a public property (left with positive width)
			T X;
			/// Top-left Y coordinate as a public property (top with positive height)
			T Y;
			/// Width as a public property
			T W;
			/// Height as a public property
			T H;

			/// Default constructor, all zeros
			constexpr Rect() noexcept
				: X(T(0)), Y(T(0)), W(T(0)), H(T(0)) {}
			explicit Rect(NoInitT) noexcept {}
			/// Constructs a rectangle from top-left point and size
			constexpr Rect(T x, T y, T w, T h) noexcept
				: X(x), Y(y), W(w), H(h) {}
			/// Constructs a rectangle from top-left point and size as two `Vector2`
			constexpr Rect(const Vector2<T>& point, const Vector2<T>& size) noexcept
				: X(point.X), Y(point.Y), W(size.X), H(size.Y) {}

			/// Creates a rectangle from center and size
			static Rect FromCenterSize(T xx, T yy, T ww, T hh);
			/// Creates a rectangle from center and size as two `Vector2`
			static Rect FromCenterSize(const Vector2<T>& center, const Vector2<T>& size);

			/// Creates a rectangle from minimum and maximum coordinates
			static Rect FromMinMax(T minX, T minY, T maxX, T maxY);
			/// Creates a rectangle from minimum and maximum coordinates as two `Vector2`
			static Rect FromMinMax(const Vector2<T>& min, const Vector2<T>& max);

			/// Returns size
			Vector2<T> GetSize() const;
			/// Returns location
			Vector2<T> GetLocation() const;

			/// Calculates center of the rectangle
			Vector2<T> Center() const;
			/// Calculates minimum coordinates of the rectangle
			Vector2<T> Min() const;
			/// Calculates maximum coordinates of the rectangle
			Vector2<T> Max() const;

			/// Sets rectangle top-left point and size
			void Set(T x, T y, T w, T h);
			/// Sets rectangle top-left point and size as two `Vector2`
			void Set(const Vector2<T>& point, const Vector2<T>& size);
			/// Retains rectangle size but moves its center to another position
			void SetCenter(float cx, float cy);
			/// Retains rectangle size but moves its center to another position with a `Vector2`
			void SetCenter(const Vector2<T>& center);
			/// Retains rectangle center but changes its size
			void SetSize(float ww, float hh);
			/// Retains rectangle center but changes its size with a `Vector2`
			void SetSize(const Vector2<T>& size);

			/// Sets rectangle center and size
			void SetCenterSize(T xx, T yy, T ww, T hh);
			/// Sets rectangle center and size as two `Vector2`
			void SetCenterSize(const Vector2<T>& center, const Vector2<T>& size);

			/// Sets rectangle minimum and maximum coordinates
			void SetMinMax(T minX, T minY, T maxX, T maxY);
			/// Sets rectangle minimum and maximum coordinates as two `Vector2`
			void SetMinMax(const Vector2<T>& min, const Vector2<T>& max);

			/// Converts elements of the rectangle to a specified type
			template<class S>
			Rect<S> As() {
				return Rect<S>(static_cast<S>(X), static_cast<S>(Y), static_cast<S>(W), static_cast<S>(H));
			}

			/// Inverts rectangle size and moves (x, y) to a different angle
			void InvertSize();

			/// Returns `true` if the point is inside this rectangle
			bool Contains(T px, T py) const;
			/// Returns `true` if the point vector is inside this rectangle
			bool Contains(const Vector2<T>& p) const;

			/// Returns `true` if the other rectangle is contained inside this one
			bool Contains(const Rect<T>& rect) const;
			/// Returns `true` if this rect does overlap the other rectangle in any way
			bool Overlaps(const Rect<T>& rect) const;

			/// Intersects this rectangle with the other rectangle
			void Intersect(const Rect<T>& rect);

			/// Unions this rectangle with the other rectangle
			void Union(const Rect<T>& rect);

			bool operator==(const Rect& rect) const;
			bool operator!=(const Rect& rect) const;

			/** @{ @name Constants */

			/// Empty rectangle
			static const Rect Empty;

			/** @} */
		};

		/// Rectangle in a two dimensional space of floats
		using Rectf = Rect<float>;
		/// Rectangle in a two dimensional space of 32-bit integers
		using Recti = Rect<int>;

		template<class T>
		inline Rect<T> Rect<T>::FromCenterSize(T xx, T yy, T ww, T hh)
		{
			return Rect(xx - static_cast<T>(ww * 0.5f),
						yy - static_cast<T>(hh * 0.5f),
						ww, hh);
		}

		template<class T>
		inline Rect<T> Rect<T>::FromCenterSize(const Vector2<T>& center, const Vector2<T>& size)
		{
			return Rect(center.X - static_cast<T>(size.X * 0.5f),
						center.Y - static_cast<T>(size.Y * 0.5f),
						size.X, size.Y);
		}

		template<class T>
		inline Rect<T> Rect<T>::FromMinMax(T minX, T minY, T maxX, T maxY)
		{
			return Rect(minX, minY, maxX - minX, maxY - minY);
		}

		template<class T>
		inline Rect<T> Rect<T>::FromMinMax(const Vector2<T>& min, const Vector2<T>& max)
		{
			return Rect(min.X, min.Y, max.X - min.X, max.Y - min.Y);
		}

		template<class T>
		inline Vector2<T> Rect<T>::GetSize() const
		{
			return Vector2<T>(W, H);
		}

		template<class T>
		inline Vector2<T> Rect<T>::GetLocation() const
		{
			return Vector2<T>(X, Y);
		}

		template<class T>
		inline Vector2<T> Rect<T>::Center() const
		{
			return Vector2<T>(X + static_cast<T>(W * 0.5f), Y + static_cast<T>(H * 0.5f));
		}

		template<class T>
		inline Vector2<T> Rect<T>::Min() const
		{
			return Vector2<T>((W > T(0)) ? X : X + W, (H > T(0)) ? Y : Y + H);
		}

		template<class T>
		inline Vector2<T> Rect<T>::Max() const
		{
			return Vector2<T>((W > T(0)) ? X + W : X, (H > T(0)) ? Y + H : Y);
		}

		template<class T>
		inline void Rect<T>::Set(T x, T y, T w, T h)
		{
			X = x;
			Y = y;
			W = w;
			H = h;
		}

		template<class T>
		inline void Rect<T>::Set(const Vector2<T>& point, const Vector2<T>& size)
		{
			X = point.X;
			Y = point.Y;
			W = size.X;
			H = size.Y;
		}

		template<class T>
		inline void Rect<T>::SetCenter(float cx, float cy)
		{
			X = cx - static_cast<T>(W * 0.5f);
			Y = cy - static_cast<T>(H * 0.5f);
		}

		template<class T>
		inline void Rect<T>::SetCenter(const Vector2<T>& center)
		{
			X = center.X - static_cast<T>(W * 0.5f);
			Y = center.Y - static_cast<T>(H * 0.5f);
		}

		template<class T>
		inline void Rect<T>::SetSize(float ww, float hh)
		{
			W = ww;
			H = hh;
		}

		template<class T>
		inline void Rect<T>::SetSize(const Vector2<T>& size)
		{
			W = size.X;
			H = size.Y;
		}

		template<class T>
		inline void Rect<T>::SetCenterSize(T xx, T yy, T ww, T hh)
		{
			X = xx - static_cast<T>(ww * 0.5f);
			Y = yy - static_cast<T>(hh * 0.5f);
			W = ww;
			H = hh;
		}

		template<class T>
		inline void Rect<T>::SetCenterSize(const Vector2<T>& center, const Vector2<T>& size)
		{
			X = center.X - static_cast<T>(size.X * 0.5f);
			Y = center.Y - static_cast<T>(size.Y * 0.5f);
			W = size.X;
			H = size.Y;
		}

		template<class T>
		inline void Rect<T>::SetMinMax(T minX, T minY, T maxX, T maxY)
		{
			X = minX;
			Y = minY;
			W = maxX - minX;
			H = maxY - minY;
		}

		template<class T>
		inline void Rect<T>::SetMinMax(const Vector2<T>& min, const Vector2<T>& max)
		{
			X = min.X;
			Y = min.Y;
			W = max.X - min.X;
			H = max.Y - min.Y;
		}

		template<class T>
		inline void Rect<T>::InvertSize()
		{
			X = X + W;
			Y = Y + H;
			W = -W;
			H = -H;
		}

		template<class T>
		inline bool Rect<T>::Contains(T px, T py) const
		{
			const bool xAxis = (W > T(0)) ? (px >= X && px <= X + W) : (px <= X && px >= X + W);
			const bool yAxis = (H > T(0)) ? (py >= Y && py <= Y + H) : (py <= Y && py >= Y + H);
			return xAxis && yAxis;
		}

		template<class T>
		inline bool Rect<T>::Contains(const Vector2<T>& p) const
		{
			return Contains(p.X, p.Y);
		}

		template<class T>
		inline bool Rect<T>::Contains(const Rect& rect) const
		{
			const bool containsMin = Contains(rect.Min());
			const bool containsMax = Contains(rect.Max());
			return (containsMin && containsMax);
		}

		template<class T>
		inline bool Rect<T>::Overlaps(const Rect& rect) const
		{
			const Vector2<T> rectMin = rect.Min();
			const Vector2<T> rectMax = rect.Max();
			const Vector2<T> thisMin = Min();
			const Vector2<T> thisMax = Max();

			const bool disjoint = (rectMax.X < thisMin.X || rectMin.X > thisMax.X ||
								   rectMax.Y < thisMin.Y || rectMin.Y > thisMax.Y);
			return !disjoint;
		}

		template<class T>
		inline void Rect<T>::Intersect(const Rect& rect)
		{
			const Vector2<T> rectMin = rect.Min();
			const Vector2<T> rectMax = rect.Max();

			Vector2<T> newMin = Min();
			Vector2<T> newMax = Max();
			if (newMin.X < rectMin.X)
				newMin.X = rectMin.X;
			if (newMin.Y < rectMin.Y)
				newMin.Y = rectMin.Y;
			if (rectMax.X < newMax.X)
				newMax.X = rectMax.X;
			if (rectMax.Y < newMax.Y)
				newMax.Y = rectMax.Y;

			if (W < T(0))
				std::swap(newMin.X, newMax.X);
			if (H < T(0))
				std::swap(newMin.Y, newMax.Y);

			SetMinMax(newMin, newMax);
		}

		template<class T>
		inline void Rect<T>::Union(const Rect& rect)
		{
			const Vector2<T> rectMin = rect.Min();
			const Vector2<T> rectMax = rect.Max();

			Vector2<T> newMin = Min();
			Vector2<T> newMax = Max();
			if (rectMin.X < newMin.X)
				newMin.X = rectMin.X;
			if (rectMin.Y < newMin.Y)
				newMin.Y = rectMin.Y;
			if (newMax.X < rectMax.X)
				newMax.X = rectMax.X;
			if (newMax.Y < rectMax.Y)
				newMax.Y = rectMax.Y;

			if (W < T(0))
				std::swap(newMin.X, newMax.X);
			if (H < T(0))
				std::swap(newMin.Y, newMax.Y);

			SetMinMax(newMin, newMax);
		}

		template<class T>
		inline bool Rect<T>::operator==(const Rect& rect) const
		{
			return (X == rect.X && Y == rect.Y &&
					W == rect.W && H == rect.H);
		}

		template<class T>
		inline bool Rect<T>::operator!=(const Rect& rect) const
		{
			return (X != rect.X || Y != rect.Y ||
					W != rect.W || H != rect.H);
		}

		template<class T>
		const Rect<T> Rect<T>::Empty(0, 0, 0, 0);
	}
}
