#ifndef __GEOMETRY_HPP__
#define __GEOMETRY_HPP__

#include <cmath>
#include <algorithm>
#include <cstdint>
#include <ostd/Types.hpp>

namespace ostd
{
	class IOutputHandler;
	template<class T>
	class Point
	{
		public:
			T x;
			T y;

		public:
			inline Point(void) : x(0), y(0) {}
			inline Point(T xx, T yy) : x(xx), y(yy) {}
			template <class T2> inline Point(Point<T2> copy)
			{
				x = (T2)(copy.x);
				y = (T2)(copy.y);
			}
	};

	typedef Point<float> FPoint;
	typedef Point<double> DPoint;
	typedef Point<uint32_t> UIPoint;
	typedef Point<uint64_t> UI64Point;
	typedef Point<float> UI16Point;
	typedef Point<uint8_t> UI8Point;
	typedef Point<int32_t> IPoint;
	typedef Point<int64_t> I64Point;
	typedef Point<int16_t> I16Point;
	typedef Point<int8_t> I8Point;

	struct Vec2
	{
		//======================== Data ========================
		float x;
		float y;
		//======================================================


		//==================== Construction ====================
		inline Vec2(float xx = 0, float yy = 0) 			: x(xx), y(yy) {  }
		inline Vec2(const Vec2& v2) 						{ set(v2); }
		inline Vec2& set(const Vec2& v2) 					{ x = v2.x; y = v2.y; return *this; }
		inline Vec2& set(float xx, float yy) 				{ x = xx; y = yy; return *this; }
		//======================================================


		//================== Static Functions ==================
		inline static Vec2 fromAngle(float angle)			{ return Vec2(std::cos(angle), std::sin(angle)); }
		inline static float angleBetween(const Vec2& v1, const Vec2& v2) { return std::acos(v1.dot(v2) / (v1.mag() * v2.mag())); }
		//======================================================


		//===================== Conversion =====================
		inline Vec2 toIsometric(void) 			const		{ return Vec2(x - y, (x + y) / 2.0f); }
		inline Vec2 toCartesian(void) 			const 		{ return Vec2((2 * y + x) / 2.0f, (2 * y - x) / 2.0f); }
		String toString(void)					const;
		//======================================================


		//===================== Operations =====================
		inline float mag(void) 					const 		{ return std::sqrt((x * x) + (y * y)); 					}

		inline Vec2 add(Vec2 v2) 				const 		{ return {		x + v2.x, 			y + v2.y 		}; 	}
		inline Vec2 add(float x2, float y2) 	const 		{ return {		x + x2, 			y + y2 			}; 	}
		inline Vec2 sub(Vec2 v2) 				const 		{ return {		x - v2.x, 			y - v2.y 		}; 	}
		inline Vec2 sub(float x2, float y2) 	const 		{ return {		x - x2, 			y - y2 			}; 	}
		inline Vec2 mul(float scalar) 			const 		{ return {		x * scalar, 		y * scalar 		}; 	}
		inline Vec2 div(float scalar) 			const 		{ return {		x / scalar, 		y / scalar 		}; 	}

		inline Vec2 normalize(void)				const		{ float m = _zp(mag()); return { x / m, 	y / m 	};	}
		inline float dist(Vec2 v2)				const		{ return std::sqrt((float)((v2.x - x) * (v2.x - x)) + ((v2.y - y) * (v2.y - y))); }
		inline float heading(void)				const		{ return std::atan2(y, x); }
		inline Vec2 rotate(float angle)			const		{ return Vec2(*this).rotate(angle); }
		inline float dot(const Vec2& v2)		const		{ return (x * v2.x) + (y * v2.y); }
		inline float cross(const Vec2& v2)		const		{ return (x * v2.y) - (v2.x * y); }
		//======================================================


		//===================== Modifiers ======================
		inline Vec2& addm(const Vec2& v2) 			 		{ 		x += v2.x; 			y += v2.y; 	 	return *this;	}
		inline Vec2& addm(const float& x2, const float& y2) { 		x += x2; 			y += y2; 		return *this; 	}
		inline Vec2& subm(const Vec2& v2) 					{ 		x -= v2.x; 			y -= v2.y; 	 	return *this;	}
		inline Vec2& subm(const float& x2, const float& y2) { 		x -= x2; 			y -= y2; 		return *this; 	}
		inline Vec2& mulm(const float& scalar) 				{ 		x *= scalar; 		y *= scalar; 	return *this; 	}
		inline Vec2& divm(const float& scalar) 				{ 		x /= scalar; 		y /= scalar; 	return *this; 	}

		inline Vec2& normalizem(void)						{ float m = _zp(mag()); x /= m; y /= m; 	return *this;	}
		inline Vec2& setMag(const float& mag)				{ return normalizem().mulm(mag); }
		inline Vec2& setHeading(const float& angle) 		{ float m = mag(); set(m * std::cos(angle), m * std::sin(angle)); return *this; }
		inline Vec2& rotate(const float& angle)				{ return setHeading(heading() + angle); }
		inline Vec2& limit(const float& max)				{ float msq = mag(); msq *= msq; if (msq > (max * max)) divm(std::sqrt(msq)).mulm(max); return *this; }
		//======================================================


		//===================== Operators ======================
		inline bool  operator==(const Vec2& op2 ) 	const	{ return (x == op2.x && y == op2.y); }
		inline bool  operator!=(const Vec2& op2 ) 	const	{ return (x != op2.x && y != op2.y); }
		inline Vec2  operator+ (const Vec2& op2 ) 	const	{ return add(op2); }
		inline Vec2  operator- (const Vec2& op2 ) 	const	{ return sub(op2); }
		inline Vec2  operator+ (const float& op2) 	const	{ return add(op2, op2); }
		inline Vec2  operator- (const float& op2) 	const	{ return sub(op2, op2); }
		inline Vec2  operator* (const float& op2) 	const	{ return mul(op2); }
		inline Vec2  operator/ (const float& op2) 	const	{ return div(op2); }
		inline Vec2& operator= (const Vec2& val ) 			{ return set(val); }
		inline Vec2& operator= (const float& val) 			{ return set(val, val); }
		inline Vec2& operator+=(const Vec2& op2 ) 			{ return addm(op2); }
		inline Vec2& operator-=(const Vec2& op2 ) 			{ return subm(op2); }
		inline Vec2& operator+=(const float& op2) 			{ return addm(op2, op2); }
		inline Vec2& operator-=(const float& op2) 			{ return subm(op2, op2); }
		inline Vec2& operator*=(const float& op2) 			{ return mulm(op2); }
		inline Vec2& operator/=(const float& op2) 			{ return divm(op2); }

		friend std::ostream& operator<<(std::ostream& out, const Vec2& val);
		//======================================================

	private:
		inline float _zp(float n1) const { return (n1 == 0 ? 1 : n1); }	

	};

	inline std::ostream& operator<<(std::ostream& out, const Vec2& val)
	{
		out << val.toString();
		return out;
	}

	struct Vec3
	{
		inline Vec3(float xx = 0, float yy = 0, float zz = 0)
		{
			x = xx;
			y = yy;
			z = zz;
		}
		inline Vec3(const Vec2& xy, float _z = 0.0f) { x = xy.x; y = xy.y; z = _z; }
		inline Vec2 xy(void) const { return Vec2(x, y); }
		inline Vec2 yz(void) const { return Vec2(y, z); }
		inline Vec2 zx(void) const { return Vec2(z, x); }
		String toString(void) const;
		inline bool  operator==(const Vec3& op2 ) 	const	{ return (x == op2.x && y == op2.y && op2.z == z); }
		inline bool  operator!=(const Vec3& op2 ) 	const	{ return (x != op2.x && y != op2.y && op2.z != z); }
		inline Vec3& operator+=(const Vec3& op2 ) 			{ x += op2.x; y += op2.y; z += op2.z; return *this; }

		friend std::ostream& operator<<(std::ostream& out, const Vec2& val);

		float x;
		float y;
		float z;
	};

	inline std::ostream& operator<<(std::ostream& out, const Vec3& val)
	{
		out << val.toString();
		return out;
	}

	struct Vec4
	{
		inline Vec4(float xx = 0, float yy = 0, float zz = 0, float ww = 0)
		{
			x = xx;
			y = yy;
			z = zz;
			w = ww;
		}
		inline Vec4(const Vec2& xy, float _z = 0.0f, float _w = 0.0f) { x = xy.x; y = xy.y; z = _z; w = _w; }
		inline Vec4(const Vec3& xyz, float _w = 0.0f) { x = xyz.x; y = xyz.y; z = xyz.z; w = _w; }
		inline Vec4(const Vec2& xy, const Vec2& zw) { x = xy.x; y = xy.y; z = zw.x; w = zw.y; }
		inline Vec2 xy(void) const { return Vec2(x, y); }
		inline Vec2 yz(void) const { return Vec2(y, z); }
		inline Vec2 zw(void) const { return Vec2(z, w); }
		inline Vec2 wx(void) const { return Vec2(w, x); }


		float x;
		float y;
		float z;
		float w;
	};

	struct Triangle
	{
		Vec2 vA;
		Vec2 vB;
		Vec2 vC;
		inline Triangle(void) {}
		inline Triangle(Vec2 a, Vec2 b, Vec2 c)
		{
			vA = a;
			vB = b;
			vC = c;
		}
		inline Triangle(float ax, float ay, float bx, float by, float cx, float cy)
		{
			vA.x = ax;
			vA.y = ay;
			vB.x = bx;
			vB.y = by;
			vC.x = cx;
			vC.y = cy;
		}
		bool contains(Vec2 p)
		{
			float d1, d2, d3;
			bool has_neg, has_pos;

			d1 = __sign(p, vA, vB);
			d2 = __sign(p, vB, vC);
			d3 = __sign(p, vC, vA);

			has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
			has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);

			return !(has_neg && has_pos);
		}

	private:
		inline float __sign(Vec2 p1, Vec2 p2, Vec2 p3)
		{
			return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
		}
	};

	class Rectangle
	{
		public:
			inline Rectangle(void) : x(0), y(0), w(0), h(0) {}
			inline Rectangle(float xx, float yy, float ww, float hh) : x(xx), y(yy), w(ww), h(hh) {}
			inline Rectangle(float xw, float yh, bool position = true) { if (position) { x = xw; y = yh; } else { w = xw; h = yh; } }
			inline Rectangle(float xx, float yy, Vec2 size) { x = xx; y = yy; w = size.x; h = size.y; }
			inline Rectangle(Vec2 position, float ww, float hh) { x = position.x; y = position.y; w = ww; h = hh; }
			inline Rectangle(Vec2 position, Vec2 size) { x = position.x; y = position.y; w = size.x; h = size.y; }
			virtual ~Rectangle(void) = default;

			inline virtual float getx(void) const { return x; }
			inline virtual float gety(void) const { return y; }
			inline virtual float getw(void) const { return w; }
			inline virtual float geth(void) const { return h; }
			inline virtual float getCenterX(void) const { return getx() + getw() / 2; }
			inline virtual float getCenterY(void) const { return gety() + geth() / 2; }

			inline virtual void setx(float xx) { x = xx; }
			inline virtual void sety(float yy) { y = yy; }
			inline virtual void setw(float ww) { w = ww; }
			inline virtual void seth(float hh) { h = hh; }

			inline virtual Vec2 getPosition(void) const { return Vec2(getx(), gety()); }
			inline virtual Vec2 getSize(void) const { return Vec2(getw(), geth()); }
			inline virtual Vec2 getCenter(void) const { return Vec2(getx() + getw() / 2, gety() + geth() / 2); }

			inline virtual void setPosition(Vec2 pos) { setx(pos.x); sety(pos.y); }
			inline virtual void setPosition(float xx, float yy) { setx(xx); sety(yy); }
			inline virtual void setSize(Vec2 size) { setw(size.x); seth(size.y); }
			inline virtual void setSize(float ww, float hh) { setw(ww); seth(hh); }
			inline virtual void setBounds(float xx, float yy, float ww, float hh) { setx(xx); sety(yy); setw(ww); seth(hh); }

			inline virtual float addx(float xx) { setx(getx() + xx); return getx(); }
			inline virtual float addy(float yy) { sety(gety() + yy); return gety(); }
			inline virtual Vec2 addPos(float xx, float yy) { return Vec2(addx(xx), addy(yy)); }
			inline virtual Vec2 addPos(Vec2 pos) { return addPos(pos.x, pos.y); }
			inline virtual float addw(float ww) { setw(getw() + ww); return getw(); }
			inline virtual float addh(float hh) { seth(geth() + hh); return geth(); }
			inline virtual Vec2 addSize(float ww, float hh) { return Vec2(addw(ww), addh(hh)); }
			inline virtual Vec2 addSize(Vec2 size) { return addPos(size.x, size.y); }

			inline virtual float subx(float xx) { setx(getx() - xx); return getx(); }
			inline virtual float suby(float yy) { sety(gety() - yy); return gety(); }
			inline virtual Vec2 subPos(float xx, float yy) { return Vec2(subx(xx), suby(yy)); }
			inline virtual Vec2 subPos(Vec2 pos) { return subPos(pos.x, pos.y); }
			inline virtual float subw(float ww) { setw(getw() - ww); return getw(); }
			inline virtual float subh(float hh) { seth(geth() - hh); return geth(); }
			inline virtual Vec2 subSize(float ww, float hh) { return Vec2(subw(ww), subh(hh)); }
			inline virtual Vec2 subSize(Vec2 size) { return subPos(size.x, size.y); }

			inline virtual float mulx(float xx) { setx(getx() * xx); return getx(); }
			inline virtual float muly(float yy) { sety(gety() * yy); return gety(); }
			inline virtual Vec2 mulPos(float xx, float yy) { return Vec2(mulx(xx), muly(yy)); }
			inline virtual Vec2 mulPos(Vec2 pos) { return mulPos(pos.x, pos.y); }
			inline virtual float mulw(float ww) { setw(getw() * ww); return getw(); }
			inline virtual float mulh(float hh) { seth(geth() * hh); return geth(); }
			inline virtual Vec2 mulSize(float ww, float hh) { return Vec2(mulw(ww), mulh(hh)); }
			inline virtual Vec2 mulSize(Vec2 size) { return mulPos(size.x, size.y); }

			inline virtual float divx(float xx) { setx(getx() / xx); return getx(); }
			inline virtual float divy(float yy) { sety(gety() / yy); return gety(); }
			inline virtual Vec2 divPos(float xx, float yy) { return Vec2(divx(xx), divy(yy)); }
			inline virtual Vec2 divPos(Vec2 pos) { return divPos(pos.x, pos.y); }
			inline virtual float divw(float ww) { setw(getw() / ww); return getw(); }
			inline virtual float divh(float hh) { seth(geth() / hh); return geth(); }
			inline virtual Vec2 divSize(float ww, float hh) { return Vec2(divw(ww), divh(hh)); }
			inline virtual Vec2 divSize(Vec2 size) { return divPos(size.x, size.y); }

			inline virtual Vec2 topLeft(void) const { return getPosition(); }
			inline virtual Vec2 topRight(void) const { return Vec2(getx() + getw(), gety()); }
			inline virtual Vec2 bottomLeft(void) const { return Vec2(getx(), gety() + geth()); }
			inline virtual Vec2 bottomRight(void) const { return Vec2(getx() + getw(), gety() + geth()); }

			inline virtual bool intersects(Rectangle rect, bool includeBounds = true) const
			{
				if (includeBounds)
				{
					if (x + w <= rect.x || x >= rect.x + rect.w)
						return false;
					if (y + h <= rect.y || y >= rect.y + rect.h)
						return false;
				}
				else
				{
					if (x + w < rect.x || x > rect.x + rect.w)
						return false;
					if (y + h < rect.y || y > rect.y + rect.h)
						return false;
				}
				return true;
			}
			inline virtual Rectangle getIntersection(Rectangle rect, bool includeBounds = true) const
			{
				if (!intersects(rect, includeBounds))
					return Rectangle();
				float leftX   = std::max(x, rect.x);
				float rightX  = std::min(x + w, rect.x + rect.w);
				float topY    = std::max(y, rect.y);
				float bottomY = std::min(y + h, rect.y + rect.h);
				return { leftX, topY, rightX - leftX, bottomY - topY };
			}
			inline virtual bool contains(Vec2 p, bool includeBounds = false) const
			{
				if (includeBounds)
					return p.x >= x && p.y >= y & p.x <= x + w && p.y <= y + h;
				else
					return p.x > x && p.y > y & p.x < x + w && p.y < y + h;
			}
			inline virtual bool contains(float xx, float yy, bool includeBounds = false) const { return contains({ xx, yy }); }

			inline virtual float getDistance(Vec2 p) const { return sqrt(fabs((p.x - getx()) * (p.x - getx()) + (p.y - gety()) * (p.y - gety()))); }

		public:
			float x;
			float y;
			float w;
			float h;
	};
} // namespace ox

 #endif
