/*
 * Copyright (c) 2015 Tricoire Sebastien 3dsman@free.fr
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software
 * in a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 *
 */

#pragma once

#include <math.h>
#include <vector>
#include <algorithm>
#include "Archive.h"
#include <GL/gl.h>
#include "qcolor.h"

struct vector_2d
{
	float x, y;

	vector_2d( float _x,  float _y)
	{
		x = _x;
		y = _y;
	}

	vector_2d() : x(0), y(0)
	{
	}

	void persist(Pakal::Archive* archive)
	{
		archive->value("x", x);
		archive->value("y", y);
	}

	inline vector_2d operator *(const vector_2d& pt) const
	{
		return {pt.x*x, pt.y*y};
	}
	inline vector_2d operator *( const float val)
	{
		return {val*x, val*y};
	}
	inline vector_2d operator /(const vector_2d& pt) const
	{
		return {x/pt.x, y/pt.y};
	}
	inline vector_2d operator /( const float val)
	{
		return {x/val, y/val};
	}
	inline vector_2d operator +(const vector_2d pt) const
	{
		return {pt.x+x, pt.y+y};
	}
	inline vector_2d operator +(const float val) const
	{
		return {val+x, val+y};
	}
	inline void operator +=(const vector_2d pt)
	{
		x+=pt.x;
		y+=pt.y;
	}
	inline vector_2d operator -(const vector_2d pt) const
	{
		return {x-pt.x, y-pt.y};
	}
	inline vector_2d operator -(const float val) const
	{
		return {x-val, y-val};
	}
	inline vector_2d operator -() const
	{
		return {-x, -y};
	}
	inline void operator -=(const vector_2d pt)
	{
		x-=pt.x;
		y-=pt.y;
	}
	inline bool operator ==(const vector_2d pt) const
	{
		return x == pt.x && y == pt.y;
	}
	double dot(const vector_2d pt) const
	{
		return x*pt.x+y*pt.y;
	}
	inline bool operator !=(const vector_2d pt) const
	{
		return x!=pt.x || y!=pt.y;
	}

	/** \brief normalise the vector (his length become 1)
	 */
	void normalize()
	{
		if (len()==0)
		{
			return;
		}
		float length = 1/len();
		x *= length;
		y *= length;
	}


	/** \brief turn the vector 90° to the right
	 */
	void turnRight()
	{
		float tmpx = x;
		x = -y;
		y = tmpx;
	}

	void turnLeft()
	{
		float tmpx = x;
		x = y;
		y = -tmpx;
	}

	float len()
	{
		return sqrt(x*x+y*y);
	}
};

class BoundingBox
{
	private:
		/** \brief The bounding box values. */
		vector_2d min;
		vector_2d max;

	public:
		bool init;

		BoundingBox() : init(false)
		{
		}
		BoundingBox(float minX, float minY, float maxX, float maxY);
		~BoundingBox(){}
		const vector_2d& getMin();
		const vector_2d& getMax();
		const vector_2d getCenter();
		BoundingBox operator+ (const BoundingBox& box);
		BoundingBox operator+ (const vector_2d& point);
		void operator+= (const BoundingBox& box);
		void operator+= (const vector_2d& point);
};

class OE_color
{
	public:
		GLfloat rgba[4];
		OE_color()
		{
			rgba[0] = rgba[1] = rgba[2] = 0.0f;
			rgba[3] = 1.0f;
		}
		OE_color(float r, float g, float b, float a = 1.0)
		{
			rgba[0] = r;
			rgba[1] = g;
			rgba[2] = b;
			rgba[3] = a;
		}

        operator QColor() const
        {
            return QColor(rgba[0]*255,rgba[1]*255,rgba[2]*255,rgba[3]*255);
        }

		inline OE_color operator *( const OE_color color)
		{
			return OE_color(rgba[0]*color.rgba[0], rgba[1]*color.rgba[1], rgba[2]*color.rgba[2], rgba[3]*color.rgba[3]);
		}

		void persist(Pakal::Archive* archive)
		{
			archive->value("r", rgba[0]);
			archive->value("g", rgba[1]);
			archive->value("b", rgba[2]);
			archive->value("a", rgba[3]);
		}
};

inline std::vector<vector_2d> normals(const std::vector<vector_2d> segments)
{
	vector_2d tmpNormal;
	vector_2d normal;
	std::vector<vector_2d> out;
	for(unsigned i=0; i<segments.size()-1; i++)
	{
		normal = segments.at(i+1)-segments.at(i);
		normal.turnRight();
		if (i==0)
		{
			tmpNormal = normal;
		}
		tmpNormal =tmpNormal + normal;
		tmpNormal.normalize();
		out.push_back(tmpNormal);

		tmpNormal = normal;
	}
	tmpNormal.normalize();
	out.push_back(tmpNormal);
	return out;
}

inline std::vector<vector_2d> subd(const vector_2d pt1, const vector_2d pt2, float maxlen)
{
	std::vector<vector_2d> out;
	vector_2d tmpvec = pt2 - pt1;
	float len = tmpvec.len();
	if (!len)
	{
		return out;
	}
	if (len<maxlen)
	{
		out.push_back(pt2);
		return out;
	}

	int nbsub = ceilf(len / maxlen);
	len /= (float)nbsub;
	tmpvec.normalize();
	tmpvec = tmpvec*len;
	vector_2d tmppos = pt1;
	for (; nbsub>0; nbsub--)
	{
		tmppos = tmppos + tmpvec;
		out.push_back( tmppos);
	}
	return out;
}

/*inline vector_2d operator* ( const vector_2d vect, const float val)
{
	return {val*vect.x,val*vect.y};
}*/
inline vector_2d operator* (const float val, const vector_2d vect)
{
	return {val*vect.x, val*vect.y};
}
/*inline vector_2d operator- ( const vector_2d vect1, const vector_2d vect2)
{
	return {vect1.x-vect2.x,vect1.y-vect2.y};
}*/
inline vector_2d operator- (const float val, const vector_2d vect)
{
	return {val-vect.x, val-vect.y};
}
inline vector_2d operator- (const vector_2d vect, const float val)
{
	return {vect.x-val, vect.y-val};
}

struct vector_2dt
{
	vector_2d v;
	float t;

	vector_2dt( vector_2d _v, float _t)
	{
		v = _v;
		t = _t;
	}

	vector_2dt() {}

	operator vector_2d() const
	{
		return v;
	}

	inline vector_2dt operator* (const float val)
	{
		return {v*val, t*val};
	}

	inline vector_2dt operator/ (const float val)
	{
		return {v/val, t/val};
	}

	inline vector_2dt operator+ (const vector_2dt pt) const
	{
		return {pt.v+v, pt.t+t};
	}

	inline vector_2dt operator- (const vector_2dt pt) const
	{
		return {v-pt.v, t-pt.t};
	}
};

inline std::vector<vector_2d> normals(const std::vector<vector_2dt> segments)
{
	vector_2d tmpNormal;
	vector_2d normal;
	std::vector<vector_2d> out;
	for (unsigned i=0; i<segments.size()-1; i++)
	{
		normal = segments.at(i+1).v-segments.at(i).v;
		normal.turnRight();
		if (i==0)
		{
			tmpNormal = normal;
		}
		tmpNormal = tmpNormal + normal;
		tmpNormal.normalize();
		out.push_back(tmpNormal);

		tmpNormal = normal;
	}
	tmpNormal.normalize();
	out.push_back(tmpNormal);
	return out;
}

struct OE_Matrix
{
	float a, b, c, d, e, f;
	OE_Matrix() : a(1.0), b(0), c(0), d(1.0), e(0), f(0) {}
	OE_Matrix(float a, float b, float c, float d, float e, float f) : a(a), b(b), c(c), d(d), e(e), f(f) {}
	OE_Matrix(float* arr) : a(arr[0]), b(arr[1]), c(arr[2]), d(arr[3]), e(arr[4]), f(arr[5]) {}
	OE_Matrix operator*(const OE_Matrix& m)
	{
		return OE_Matrix(a*m.a+b*c, a*m.b+m.d*b, c*m.a+c*d, m.b*c+m.d*d, e*m.a+c*f+m.e, m.b*e+m.d*f+m.f);
	}
	void apply(float x, float y, float* newx, float* newy)
	{
		if(newx && newy)
		{
			*newx = a*x+c*y+e;
			*newy = b*x+d*y+f;
		}
	}
	void apply(float& x, float& y) const
	{
		float oldx = x;
		float oldy = y;
		x = a*oldx+c*oldy+e;
		y = b*oldx+d*oldy+f;
	}
};

inline float sign (const vector_2d p1, const vector_2d p2, const vector_2d p3)
{
	return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}

inline bool PointInTriangle (const vector_2d pt, const vector_2d v1, const vector_2d v2, const vector_2d v3)
{
	bool b1, b2, b3;

	b1 = sign(pt, v1, v2) < 0.0f;
	b2 = sign(pt, v2, v3) < 0.0f;
	b3 = sign(pt, v3, v1) < 0.0f;

	return b1 == b2 && b2 == b3;
}

//inline vector_2d* intersect(vector_2d p1, vector_2d p2, vector_2d p3, vector_2d p4);
/*
inline bool intersect(vector_2d p1, vector_2d p2, vector_2d p3, vector_2d p4, vector_2d &intersectPt)
{
	// Store the values for fast access and easy
	// equations-to-code conversion
	float x1 = p1.x, x2 = p2.x, x3 = p3.x, x4 = p4.x;
	float y1 = p1.y, y2 = p2.y, y3 = p3.y, y4 = p4.y;

	float minx12 = minf(x1, x2) , minx34 = minf(x3, x4), miny12 = minf(y1, y2) , miny34 = minf(y3, y4);
	float maxx12 = maxf(x1, x2) , maxx34 = maxf(x3, x4), maxy12 = maxf(y1, y2) , maxy34 = maxf(y3, y4);
	//if ( maxx12<minx34 || maxx34<minx12 || maxy12<miny34 || maxy34<miny12)
	//{
	//	return false;
	//}

	double lenx12 = (x1 - x2) , lenx34 = x3 - x4, leny12 = y1 - y2 , leny34 = y3 - y4;

	double d = lenx12 * leny34 - leny12 * lenx34;
	// If d is zero, there is no intersection
	if (d == 0)
	{
		return false;
	}

	// Get the x and y
	double pre = (x1*y2 - y1*x2), post = (x3*y4 - y3*x4);
	double x = ( pre * lenx34 - lenx12 * post ) / d;
	double y = ( pre * leny34 - leny12 * post ) / d;

	// Check if the x and y coordinates are within both lines (use only the longer axis to avoid precision problems on vertical or horizontal segments)
	if (abs(lenx12)>abs(leny12))
	{
		if( x < minx12 || x > maxx12 )
		{
			return false;
		}
	}
	else
	{
		if( y < miny12 || y > maxy12 )
		{
			return false;
		}
	}
	if (abs(lenx34) > abs(leny34))
	{
		if(x < minx34 || x > maxx34 )
		{
			return false;
		}
	}
	else
	{
		if(y < miny34 || y > maxy34)
		{
			return false;
		}
	}
	//if (x < minx12 || x > maxx12 || x < minx34 || x > maxx34)
	//{
	//	return nullptr;
	//}
	//if (y < miny12 || y > maxy12 || y < miny34 || y > maxy34)
	//{
	//	return nullptr;
	//}
	intersectPt.x = x;
	intersectPt.y = y;

	return true;
}
*/

inline bool intersect(vector_2d p1, vector_2d p2, vector_2d p3, vector_2d p4, vector_2d& intersectPt, float& intersectP1P2, float &intersectP3P4, bool extremity = true)
{
	vector_2d CmP = p3 - p1;
	vector_2d r = p2 - p1;
	vector_2d s = p4 - p3;

	float CmPxr = CmP.x * r.y - CmP.y * r.x;
	float CmPxs = CmP.x * s.y - CmP.y * s.x;
	float rxs = r.x * s.y - r.y * s.x;

	if (CmPxr == 0.0f)
	{
		// Lines are collinear, and so intersect if they have any overlap
		return ((p3.x - p1.x < 0.0f) != (p3.x - p2.x < 0.0f)) ||
		       ((p3.y - p1.y < 0.0f) != (p3.y - p2.y < 0.0f));
	}

	if (rxs == 0.0f)
	{
		return false; // Lines are parallel.
	}

	float rxsr = 1.0f / rxs;
	float t = CmPxs * rxsr;
	float u = CmPxr * rxsr;

	if (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f)
	{
		intersectP1P2 = t;
		intersectP3P4 = u;
		intersectPt = r * t + p1;
		return (extremity||(t != 0.0f && t != 1.0f && u != 0.0f && u != 1.0f));
	}

	return false;
}

