//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------		
#include <Render/Triangle.hpp>
#include <Render/MonteCarlo.hpp>
#include <Math/Random.hpp>
#include <algorithm>


namespace Render
{
	//-----------------------------------------------------------------
	// C/D-Tor
	//-----------------------------------------------------------------
	Triangle::Triangle(const Math::mat4& _world2Object, unsigned int _matID, 
					   const Math::vec3 _p0, const Math::vec3 _p1, const Math::vec3 _p2,
					   const Math::vec2 _uv0, const Math::vec2 _uv1, const Math::vec2 _uv2):
	Primitive(_world2Object, _matID),
	p0(_p0), p1(_p1), p2(_p2),
	uv0(_uv0), uv1(_uv1), uv2(_uv2)
	{
		// Transform triangle from object to world space //TODO : remove is add intanced object
		p0 = Transform::TransformPoint(p0,ObjectToWorld);
		p1 = Transform::TransformPoint(p1,ObjectToWorld);
		p2 = Transform::TransformPoint(p2,ObjectToWorld);

	}
	//-----------------------------------------------------------------
	Triangle::~Triangle()
	{

	}
	//-----------------------------------------------------------------
	bool Triangle::Intersect(const Ray& _ray, Intersection& _intersection) const
	{
		// Transform ray in object space
		//Ray localRay;
		//Transform::Transform(_ray, WorldToObject, localRay);
		//printf("p0: %s\n", Math::ToString(p0).c_str());
		//printf("p1: %s\n", Math::ToString(p1).c_str());
		//printf("p2: %s\n", Math::ToString(p2).c_str());

		Math::vec3 e1 = p1 - p0;
		Math::vec3 e2 = p2 - p0;

		Math::vec3 n  = Math::cross(e1,e2);
		Math::vec3 nn = Math::normalize(n);

		Math::vec3 w = _ray.Origin - p0;

		float temp = Math::dot(n,_ray.Direction);
		float dir  = Math::dot(n,w);

		if(dir<0)
			nn = -1.f * nn;

		float tHit= -dir/ temp;
		if(tHit<0)
			return false;

		float b1 = Math::dot(Math::cross(w,e2),_ray.Direction) / temp;
		float b2 = Math::dot(Math::cross(e1,w),_ray.Direction) / temp;

		if(!(b2>=0 && b1 >=0 && b1+b2 <=1))
			return false;

		if (tHit < _ray.MinT || tHit > _ray.MaxT)
			return false;

		/*
		Math::vec3 e1 = p1 - p0;
		Math::vec3 e2 = p2 - p0;
		Math::vec3 s1 = Math::cross(e2,_ray.Direction);
		float divisor = Math::dot(s1,e1);
		printf("divisor:%f\n",divisor);
		if(divisor == 0.) return false;
		float invDivisor = 1. / divisor;

		// Compute first barycentric coordinate
		Math::vec3 d = _ray.Position - p1;
		float b1 = Math::dot(d, s1) * invDivisor;
		//printf("b1%f\n", b1);
		if (b1 < 0. || b1 > 1.)
			return false;


		// Compute second barycentric coordinate
		Math::vec3 s2 = Math::cross(e1, d);
		float b2 = Math::dot(_ray.Direction, s2) * invDivisor;
		//printf("b2:%f",b2);
		if (b2 < 0. || b1 + b2 > 1.)
			return false;


		// Compute _t_ to intersection point
		float tHit = Math::dot(e2, s2) * invDivisor;
		//printf("Hit:%f",tHit);
		if (tHit < _ray.MinT || tHit > _ray.MaxT)
			return false;
		*/

		// Compute object space vector
		const Math::vec3 Q1 = p1 - p0;
		const Math::vec3 Q2 = p2 - p0;

		// Compute tex space vector
		const float ds1 = uv1.s - uv0.s;
		const float dt1 = uv1.t - uv0.t;
		const float ds2 = uv2.s - uv0.s;
		const float dt2 = uv2.t - uv0.t;

		// Compute tangent and bitangent
		// http://www.terathon.com/code/tangent.html
		const float factor  = 1.f / (ds1*dt2 - ds2*dt1);
		const Math::vec3 T = ((dt2*e1) - (dt1*e2)) * factor;
		const Math::vec3 B = ((ds1*e2) - (ds2*e1)) * factor;

		// Compute normal face 
		const Math::vec3 N = Math::normalize(Math::cross(Math::normalize(e1), Math::normalize(e2)));

		// Use Gram-Schmidt normalization
		// T′ = T − (N · T)N
		// B′ = B − (N · B)N − (T′ · B)T′
		Math::vec3 Tprime = T - Math::dot(N,T)*N;
		Math::vec3 Bprime = B - Math::dot(N,B)*N - Math::dot(Tprime,B)*Tprime;

		// Normalize vector
		Tprime = Math::normalize(Tprime);
		Bprime = Math::normalize(Bprime);

		// Compute interpolated (u,v)
		float b0 = 1 - b1 - b2;
		float u  = b0*uv0.s + b1*uv1.s + b2*uv2.s;
		float v  = b0*uv0.t + b1*uv1.t + b2*uv2.t;

		// Compute intersection attribute in world space
		_intersection.TexCoord.x = u;
		_intersection.TexCoord.y = v;
		_intersection.T 	     = tHit;
		_intersection.Position   = _ray(tHit);
		_intersection.Normal     = N;
		_intersection.Tangent    = Tprime;
		_intersection.Bitangent  = Bprime;
		_intersection.MaterialID = materialID;
		// printf("In Triangle normal : %s\n", Math::ToString(_intersection.Normal).c_str());

//printf("Triangle t:%f\n",tHit);
		return true;
	}
	//-----------------------------------------------------------------
	bool Triangle::Unoccluded(const VisibilityTester& _tester) const
	{
		Math::vec3 e1 = p1 - p0;
		Math::vec3 e2 = p2 - p0;

		Math::vec3 n  = Math::cross(e1,e2);
		Math::vec3 nn = Math::normalize(n);

		Math::vec3 w = _tester.Path.Origin - p0;

		float temp = Math::dot(n,_tester.Path.Direction);
		float dir  = Math::dot(n,w);

		if(dir<0)
			nn = -1.f * nn;

		float tHit= -dir/ temp;
		if(tHit<0)
			return true;

		float b1 = Math::dot(Math::cross(w,e2),_tester.Path.Direction) / temp;
		float b2 = Math::dot(Math::cross(e1,w),_tester.Path.Direction) / temp;

		if(!(b2>=0 && b1 >=0 && b1+b2 <=1))
			return true;

		if (tHit < _tester.Path.MinT || tHit > _tester.Path.MaxT)
			return true;

		return false;
	}
	//-----------------------------------------------------------------
	void Triangle::Sample(Math::vec3& _point, float& _pdf) const
	{
		_pdf = 1./Surface();

		// Generate uv position
		const float u0 = Math::Random::Uniform();
		const float u1 = Math::Random::Uniform();
		const Math::vec2 uv = MonteCarlo::UniformSampleTriangle(u0,u1);
		
		// Compute xyz position
		_point = uv.x * p0 + uv.y * p1 + (1.f - uv.x - uv.y) * p2;
	}
	//-----------------------------------------------------------------
	float Triangle::Surface() const
	{
		// Triangle surface  = |e1 ^ e2| / 2;
		const Math::vec3 e1 = p1 - p0;
		const Math::vec3 e2 = p2 - p0;
		return Math::length(Math::cross(e1,e2)) * 0.5f;
	}
}
