//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------		
#include <Render/Reflection.hpp>
#include <Render/MonteCarlo.hpp>
#include <Math/Random.hpp>
#include <algorithm>
#include <cstdio>
#include <cstring>

namespace Render
{
	//-----------------------------------------------------------------
	// BSDF Methods
	//-----------------------------------------------------------------
	BSDF::BSDF():
	nBxdfs(0)
	{
		memset(bxdfs, 0x0, sizeof(BxDF*) * MAX_BxDF);
	}
	//-----------------------------------------------------------------
	BSDF::~BSDF()
	{

	}
	//-----------------------------------------------------------------
	void BSDF::Add(BxDF* _bxdf)
	{
		assert(_bxdf!=NULL);
		bxdfs[nBxdfs++] = _bxdf;
	}
	//-----------------------------------------------------------------
	int BSDF::NumComponents() const
	{
		return nBxdfs;
	}
	//-----------------------------------------------------------------
	int BSDF::NumComponents(BxDFType _flags) const
	{
		int nComponent=0;
		for(int i=0; i < nBxdfs; ++i)
			if(bxdfs[i]->MatchFlag(_flags)) 
				++nComponent;
		return nComponent;
	}
	//-----------------------------------------------------------------
	Math::vec3 BSDF::Fr(const Math::vec3& _wo, const Math::vec3& _wi, BxDFType _flags) const
	{
		//const Math::vec3 normal(0.,0.,1.);
		// TODO : do check on wo & wi orientation (same hemisphere, above, below
		//		   surface ... ), to avoir light leak

		Math::vec3 fr(0.f);
		for(int i=0;i<nBxdfs;++i)
		{
			if(bxdfs[i]->MatchFlag(_flags))
			{
				assert(bxdfs[i] != 0);
				fr += bxdfs[i]->Fr(_wo,_wi);
			}
		}
		return fr;
	}
	//-----------------------------------------------------------------
	Math::vec3 BSDF::SampleFr(const Math::vec3& _wo, Math::vec3& _wi, float& _pdf, BxDFType _flags) const
	{
		// Get number of BxDF which match with _flags
		int matchingComps = NumComponents(_flags);
		if (matchingComps == 0) 
		{
			_pdf = 0.f;
			_wi  = Math::vec3(0.f);
			return Math::vec3(0.f);
		}

		// Select a random BxDF among whose are matched
		const float u0 = Math::Random::Uniform();
		int which = std::min<double>(floor(u0 * matchingComps),	static_cast<double>(matchingComps-1));
		BxDF *bxdf = NULL;
		int count = which;
		for (int i = 0; i < nBxdfs; ++i)
			if (bxdfs[i]->MatchFlag(_flags))
				if (count-- == 0) 
				{
					bxdf = bxdfs[i];
					break;
				}
		assert(bxdf!=NULL);

		// TODO make all coordinate transformation here (world to local and local to world)

		// Compute BxDF and wi value
		return bxdf->SampleFr(_wo, _wi, _pdf);

		//TODO compute correct PDF !
	/*	if (_pdf == 0.f) 
			return Math::vec3(0.f);

		// Compute overall PDF with all matching _BxDF_s
		if (!(bxdf->type & BSDF_SPECULAR) && matchingComps > 1) 
		{
			for (int i = 0; i < nBxDFs; ++i) 
			{
				if (bxdfs[i] != bxdf && bxdfs[i]->MatchesFlags(flags))
					*pdf += bxdfs[i]->Pdf(wo, wi);
			}
		}

		if (matchingComps > 1) *pdf /= matchingComps;


		// Compute value of BSDF for sampled direction
		if (!(bxdf->type & BSDF_SPECULAR)) 
		{
			f = 0.;
			if (Dot(*wiW, ng) * Dot(woW, ng) > 0)
				// ignore BTDFs
				flags = BxDFType(flags & ~BSDF_TRANSMISSION);
			else
				// ignore BRDFs
				flags = BxDFType(flags & ~BSDF_REFLECTION);
			for (int i = 0; i < nBxDFs; ++i)
				if (bxdfs[i]->MatchesFlags(flags))
					f += bxdfs[i]->f(wo, wi);
		}
		return f;*/
	}


	//-----------------------------------------------------------------
	// BxDF Methods
	//-----------------------------------------------------------------
	BxDF::BxDF(BxDFType _type):
	type(_type)
	{

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

	}
	//-----------------------------------------------------------------
	bool BxDF::MatchFlag(BxDFType _type) const
	{
		return (_type & type) != 0;
	}
	//-----------------------------------------------------------------
	Math::vec3 BxDF::SampleFr(const Math::vec3& _wo, Math::vec3& _wi, float& _pdf) const
	{
//		MonteCarlo::UniformSampleHemisphere(1.f,1.f);
		const float u0 = Math::Random::Uniform();//TODO : debug compilation
		const float u1 = Math::Random::Uniform();
		_wi = MonteCarlo::CosineSampleHemisphere(u0, u1);
		if(_wo.z < 0.)
			_wi.z *= -1.f;
		_pdf = Pdf(_wo, _wi);
		return Fr(_wo, _wi);
	}
	//-----------------------------------------------------------------
	float BxDF::Pdf(const Math::vec3& _wo, const Math::vec3& _wi) const 
	{
		return Reflection::SameHemisphere(_wo, _wi) ? fabsf(_wi.z) * Math::Constant::INV_PI : 0.f;
	}



	//-----------------------------------------------------------------
	// Phong Methods
	//-----------------------------------------------------------------
	Lambertian::Lambertian(const Math::vec3& _rho):
	BxDF(BxDFType(BSDF_DIFFUSE | BSDF_REFLECTION)),
	rhoOverPi(_rho * Math::Constant::INV_PI)
	{

	}
	//-----------------------------------------------------------------
	Lambertian::~Lambertian()
	{
	
	}
	//-----------------------------------------------------------------
	Math::vec3 Lambertian::Fr(const Math::vec3& _wo, const Math::vec3& _wi) const
	{
		return rhoOverPi;
	}


/*	//-----------------------------------------------------------------
	// Phong Methods
	//-----------------------------------------------------------------
	Phong::Phong(float  _rhoDiffuse, float _rhoSpcular, float _exponent):
	BxDF(BxDFType::DIFFUSE & BxDFType::SPECULAR & REFLECTION),
	rhoDiffuse(_rhoDiffuse),
	rhoSpecular(_rhoSpecular),
	exponent(_exponent)
	{

	}
	//-----------------------------------------------------------------
	Phong::~Phong()
	{
	
	}
	//-----------------------------------------------------------------
	Math::vec3 Phong::Fr(const Math::vec3& _wo, const Math::vec3& _wi) const
	{
		Math::vec3 h = (_wo + _wi) * 0.5;
//		Math::vec3 reflected =  Math::reflect(_wi,);
		const Math::vec3 normal(0.,0.,1.); 
		return exp(std::max(0.0, Math::dot(normal, h)),exponent);
	}
	//-----------------------------------------------------------------
	Math::vec3 Phong::SampleFr(const Math::vec3& _wo, Math::vec3& _wi, BxDFType _flags) const
	{

	}*/
	
}
