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


namespace Render
{
	//-----------------------------------------------------------------
	// C/D-Tor
	//-----------------------------------------------------------------
	Sphere::Sphere(const Math::mat4& _world2Object, unsigned int _matID, float _radius):
	Primitive(_world2Object, _matID),
	radius(_radius)
	{

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

	}
	//-----------------------------------------------------------------
	bool Sphere::Intersect(const Ray& _ray, Intersection& _intersection) const
	{
		// Transform ray in object space
		Ray localRay;
		Transform::Transform(_ray, WorldToObject, localRay);

//		printf("Sample x:%f y:%f \nPosition:%s\n Diretion:%s\n", _sample.Position.x, _sample.Position.y, Math::ToString(_ray.Position).c_str(), Math::ToString(_ray.Direction).c_str());
//		printf("In Sphere Ray.Position  : %s\n", Math::ToString(_ray.Position).c_str());
//		printf("In Sphere LocalPosition : %s\n", Math::ToString(localPosition).c_str());
//		printf("In Sphere Ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());

		// Compute second degree coefficient : a.x^2 + b.x + c
		float a = Math::dot(localRay.Direction,localRay.Direction); 
		float b = Math::dot(localRay.Direction,localRay.Origin)*2.f;
		float c = Math::dot(localRay.Origin,localRay.Origin)-radius*radius; 
		float delta = b*b - 4*a*c;

		// No complex solution
		if(delta<0) return false;

		// Find roots
		float t0 = (-b+sqrt(delta)) / (2*a);
		float t1 = (-b-sqrt(delta)) / (2*a);	
		if(t0>t1) std::swap(t0,t1);
//		printf("t0:%f  t1:%f\n", t0, t1);

		// Check solution
		float tHit;
		if(t0>_ray.MaxT) return false;
		if(t1<_ray.MinT) return false;
		if(t0>_ray.MinT)
			tHit = t0;
		else if(t1<_ray.MaxT)
			tHit = t1;
		else 
			return false;
//		printf("thit:%f\n",tHit);

		// Compute texture coordinate / parametric value
		float theta, phi;
		Math::vec3 localPosition  = localRay(tHit); 
		Math::vec3 localDirection = Math::normalize(localPosition);
		Transform::EuclidianToPolarY(localDirection, theta, phi);
		float u, v;
		u = theta / Math::Constant::PI;
		v = phi / Math::Constant::TWO_PI;

		const float du = u + 0.1;
		Math::vec3 dpdu;
		Transform::PolarYToEuclidian(du, v, dpdu);
		dpdu = dpdu - localPosition;

//		float dv = v + 0.1;
//		Math::vec3 dpdv;
//		PolarYToEuclidian(u, dv, dpdv);
//		dpdv = dpdv - localPosition;
		
		// Compute intersection attribute in world space
		_intersection.TexCoord.x = u;
		_intersection.TexCoord.y = v;
		_intersection.T 	     = tHit;
		_intersection.Position   = _ray(tHit);
		_intersection.Normal     = Transform::TransformVector(Math::normalize(localRay(tHit)), ObjectToWorld);
		_intersection.Tangent    = Math::normalize(dpdu);
		_intersection.Bitangent  = Math::normalize(Math::cross(_intersection.Normal, _intersection.Tangent));
		_intersection.MaterialID = materialID;
//		printf("In Sphere normal : %s\n", Math::ToString(_intersection.Normal).c_str());
// printf("Sphere t:%f\n",tHit);
		return true;
	}
	//-----------------------------------------------------------------
	bool Sphere::Unoccluded(const VisibilityTester& _tester) const
	{
		// Transform ray in object space
		Ray localRay;
		Transform::Transform(_tester.Path, WorldToObject, localRay);

		// Compute second degree coefficient : a.x^2 + b.x + c
		float a = Math::dot(localRay.Direction,localRay.Direction); 
		float b = Math::dot(localRay.Direction,localRay.Origin)*2.f; 
		float c = Math::dot(localRay.Origin,localRay.Origin)-radius*radius; 
		float delta = b*b - 4*a*c;

		// No complex solution
		if(delta<0) return true;

		// Find roots
		float t0 = (-b+sqrt(delta)) / (2*a);
		float t1 = (-b-sqrt(delta)) / (2*a);	
		if(t0>t1) std::swap(t0,t1);

		// Check solution
		if(t0>_tester.Path.MaxT || t1<_tester.Path.MinT) return true;
		if(t0>_tester.Path.MinT) 				 		 return false;
		else if(t1<_tester.Path.MaxT) 			 		 return false;
		else 							 				 return true;
	}
	//-----------------------------------------------------------------
	void Sphere::Sample(Math::vec3& _point, float& _pdf) const
	{
		_pdf = 0.5f * Math::Constant::INV_TWO_PI;

		const float u0 = Math::Random::Uniform();
		const float u1 = Math::Random::Uniform();

		// Transform left to right hand (and axis z normal to axis y normal
		const Math::vec3 direction = MonteCarlo::UniformSampleSphere(u0,u1).xzy; 
		_point = Transform::TransformPoint((direction * radius), ObjectToWorld);
	}
	//-----------------------------------------------------------------
	float Sphere:: Surface() const
	{
		return 4 * Math::Constant::PI;
	}
}
