//
//  MathHelpers.h
//  CRKit
//
//  Created by Yaroslav Glushchenko on 7/23/09.
//  Copyright 2009 Corner-A. All rights reserved.
//

#define CRFloatComparisonEpsilon 0.00001

//////////////////////////////////////////////////////////////////////
// Math
//////////////////////////////////////////////////////////////////////
#pragma mark Vectors

NS_INLINE NSPoint CRRectCenter(const NSRect& rect)
{
	return NSMakePoint(NSMidX(rect), NSMidY(rect));
}

NS_INLINE NSPoint operator - (const NSPoint& p0)
{
	return NSMakePoint(-p0.x, -p0.y);
}

NS_INLINE NSPoint operator + (const NSPoint& p0, const NSPoint& p1)
{
	return NSMakePoint(p0.x + p1.x, p0.y + p1.y);
}

NS_INLINE NSPoint operator += (NSPoint& p0, const NSPoint& p1)
{
	p0.x += p1.x;
	p0.y += p1.y;
	return p0;
}

NS_INLINE NSPoint operator - (const NSPoint& p0, const NSPoint& p1)
{
	return p0 + (-p1);
}

NS_INLINE NSPoint operator -= (NSPoint& p0, const NSPoint& p1)
{
	return p0 += (-p1);
}

NS_INLINE NSPoint operator * (const NSPoint& p0, CGFloat t)
{
	return NSMakePoint(p0.x * t, p0.y * t);
}

NS_INLINE NSPoint operator * (const NSPoint& p0, const NSPoint& p1)
{
	return NSMakePoint(p0.x * p1.x, p0.y * p1.y);
}

NS_INLINE NSPoint operator * (float t, const NSPoint& p0)
{
	return operator * (p0, t);
}

NS_INLINE NSPoint operator / (const NSPoint& p0, CGFloat t)
{
	return operator * (p0, 1.0f / t);
}

NS_INLINE NSPoint operator / (const NSPoint& p0, const NSPoint& p1)
{
	return NSMakePoint(p0.x / p1.x, p0.y / p1.y);
}

NS_INLINE NSPoint operator / (float t, const NSPoint& p0)
{
	return operator / (NSMakePoint(t, t), p0);
}

NS_INLINE NSRect operator * (const NSRect& rect, CGFloat v)
{
	return NSMakeRect(rect.origin.x * v, rect.origin.y * v, rect.size.width * v, rect.size.height * v);
}

NS_INLINE NSRect operator + (const NSRect& rect, const NSPoint& v)
{
	return NSOffsetRect(rect, v.x, v.y);
}

NS_INLINE NSRect operator - (const NSRect& rect, const NSPoint& v)
{
	return NSOffsetRect(rect, -v.x, -v.y);
}

NS_INLINE NSRect operator * (const NSRect& rect, const NSPoint& v)
{
	return NSMakeRect(rect.origin.x * v.x, rect.origin.y * v.y, rect.size.width * v.x, rect.size.height * v.y);
}

NS_INLINE NSRect operator / (const NSRect& rect, const NSPoint& v)
{
	return NSMakeRect(rect.origin.x / v.x, rect.origin.y / v.y, rect.size.width / v.x, rect.size.height / v.y);
}

NS_INLINE NSRect operator * (const NSRect& rect, const NSSize& s)
{
	return NSMakeRect(rect.origin.x * s.width, rect.origin.y * s.height, rect.size.width * s.width, rect.size.height * s.height);
}

NS_INLINE NSRect operator / (const NSRect& rect, const NSSize& s)
{
	return NSMakeRect(rect.origin.x / s.width, rect.origin.y / s.height, rect.size.width / s.width, rect.size.height / s.height);
}

NS_INLINE bool CREqualFloats(CGFloat a, CGFloat b, CGFloat epsilon = CRFloatComparisonEpsilon)
{
	return ABS(a - b) <= epsilon;
}

NS_INLINE CGFloat CRLength(NSPoint vector)
{
	return sqrtf(vector.x * vector.x + vector.y * vector.y);
}

NS_INLINE NSPoint CRNormalize(NSPoint vector)
{
	CGFloat l = CRLength(vector);
	return l? vector / l : NSZeroPoint;
}

//////////////////////////////////////////////////////////////////////
#pragma mark Auxilliary

NS_INLINE float frand()
{
	return (float)rand() / RAND_MAX;
}

NS_INLINE float frandm(float max)
{
	return max * frand();
}

NS_INLINE float frandi(float min, float max)
{
	return min + frandm(max - min);
}

template <typename T>
NS_INLINE T clamp(T f, T min, T max)
{
	if (f < min)
		f = min;
	if (f > max)
		f = max;
	return f;
}

template <typename T>
NS_INLINE T lerp(T p0, T p1, float t)
{
	return p0 + (p1 - p0) * t;
}

template <typename T>
NS_INLINE T lerp_quadratic(T p0, T p1, T p2, float t)
{
	return (1-t)*(1-t)*p0 + 2*t*(1-t)*p1 + t*t*p2;
}

template <typename T>
NS_INLINE T lerp_cubic(T p0, T p1, T p2, T p3, float t)
{
	float inv_t = 1 - t;
	return (inv_t*inv_t*inv_t)*p0 + (3*t*inv_t*inv_t)*p1 + (3*t*t*inv_t)*p2 + (t*t*t)*p3;
}

template <typename T>
NS_INLINE T lerp_cardinal(T p0, T p1, T p2, T p3, float c, float t)
{
	float h00 = 2*t*t*t - 3*t*t + 1;
	float h10 = t*(t*t - 2*t + 1);
	float h01 = t*t*(3 - 2*t);
	float h11 = t*t*(t - 1);
	
	T m0 = (1 - c) * (p2 - p0) * 0.5f;
	T m1 = (1 - c) * (p3 - p1) * 0.5f;
	
	return h00*p1 + h10*m0 + h01*p2 + h11*m1;
}

template <typename T>
NS_INLINE T lerp_catmullrom(T p0, T p1, T p2, T p3, float t)
{
	return lerp_cardinal(p0, p1, p2, p3, 0, t);
	//return 0.5f * ((2 * p1) + (-p0 + p2) * t + (2*p0 - 5*p1 + 4*p2 - p3) * t*t + (-p0 + 3*p1- 3*p2 + p3) * t*t*t);
}

//////////////////////////////////////////////////////////////////////
#pragma mark Approximate equality

NS_INLINE bool CREqualPoints(const NSPoint& p1, const NSPoint& p2, CGFloat epsilon = CRFloatComparisonEpsilon)
{
	return CREqualFloats(p1.x, p2.x, epsilon) && CREqualFloats(p1.y, p2.y, epsilon);
}

NS_INLINE bool operator == (const NSPoint& p1, const NSPoint& p2)
{
	return CREqualPoints(p1, p2);
}

NS_INLINE bool CREqualSizes(const NSSize& s1, const NSSize& s2, CGFloat epsilon = CRFloatComparisonEpsilon)
{
	return CREqualFloats(s1.width, s2.width, epsilon) && CREqualFloats(s1.height, s2.height, epsilon);
}

NS_INLINE bool operator == (const NSSize& s1, const NSSize& s2)
{
	return CREqualSizes(s1, s2);
}

//////////////////////////////////////////////////////////////////////
#pragma mark Tasks

// ax + by = c, computes params from 2 points
NS_INLINE void CRLineParametersFromPoints(NSPoint p1, NSPoint p2, double&a, double& b, double& c)
{
	a = p1.y - p2.y;
	b = p2.x - p1.x;
	c = a * p1.x + b * p1.y;
}

// compute intersection point of the two lines (each defined by two points)
NS_INLINE NSPoint CRLinesIntersection(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4, NSPoint parallel)
{
	// The algo:
	// ax + by + c = 0 is simple 'equation of the line', we have two of them, so we have:
	// a1*x + b1*y = c1
	// a2*x + b2*y = c2
	double a1, b1, c1;
	CRLineParametersFromPoints(p1, p2, a1, b1, c1);
	
	double a2, b2, c2;
	CRLineParametersFromPoints(p3, p4, a2, b2, c2);
	
	double x(0.0), y(0.0);
	double det = a1 * b2 - a2 * b1;
	// there is intersection
    if (!CREqualFloats(det, 0.0))
	{
        x = (b2 * c1 - b1 * c2) / det;
        y = (a1 * c2 - a2 * c1) / det;
    }
	// parallel
	else
	{
		x = parallel.x;
		y = parallel.y;
	}
	
	return NSMakePoint(x, y);
}

NS_INLINE CGFloat CRGetPointProjectionToSegment(NSPoint p, NSPoint a, NSPoint b, NSPoint& result)
{
	NSPoint v = b - a;
	CGFloat d = v.x*v.x + v.y*v.y;
	if (d == 0)
	{
		result = a;
		return FLT_MAX;
	}
	
	CGFloat t = (p.x*v.x + p.y*v.y - (a.x*v.x + a.y*v.y)) / d;
	result = a + v * t;
	return t;
}

//////////////////////////////////////////////////////////////////////
#pragma mark Flexible rect
struct FlexibleRect
{
	NSPoint points[4];
	
public:
	FlexibleRect()
	{
		memset(points, 0, sizeof(points));
	}
	
	FlexibleRect(const NSRect& rect)
	{
		points[0] = NSMakePoint(NSMinX(rect), NSMinY(rect));
		points[1] = NSMakePoint(NSMinX(rect), NSMaxY(rect));
		points[2] = NSMakePoint(NSMaxX(rect), NSMaxY(rect));
		points[3] = NSMakePoint(NSMaxX(rect), NSMinY(rect));
	}
	
	void transform(NSAffineTransform* transform)
	{
		for (int i = 0; i < 4; ++i)
			points[i] = [transform transformPoint:points[i]];
	}
	
	NSRect boundingRect()
	{
		NSPoint minPoint = { +INT_MAX, +INT_MAX };
		NSPoint maxPoint = { -INT_MAX, -INT_MAX };
		for (int i = 0; i < 4; ++i)
		{
			minPoint.x = MIN(minPoint.x, points[i].x);
			minPoint.y = MIN(minPoint.y, points[i].y);
			
			maxPoint.x = MAX(maxPoint.x, points[i].x);
			maxPoint.y = MAX(maxPoint.y, points[i].y);
		}
		
		return NSMakeRect(minPoint.x, minPoint.y, (maxPoint.x - minPoint.x), (maxPoint.y - minPoint.y));
	}
	
	CGFloat width()
	{
		NSPoint vec = points[1] - points[0];
		return sqrtf(vec.x * vec.x + vec.y * vec.y);
	}
	
	CGFloat height()
	{
		NSPoint vec = points[3] - points[0];
		return sqrtf(vec.x * vec.x + vec.y * vec.y);
	}
	
	static NSRect boundingRectFor(const NSRect& rect, NSAffineTransform* transform)
	{
		FlexibleRect fr(rect);
		fr.transform(transform);
		return fr.boundingRect();
	}
};