#include "config.h"
#include "minnaert.h"
#include "texture.h"
#include "functions.h"

__BEGIN_BRDFLIB_SOURCE

// describes the surface to a given stream
//========================================================================
void
CSurfaceMinnaert::Describe(ostream &app, int ind) const
{
	indent(app, ind);
	app << GetName() << ":\n";

	if (_kd) {
		indent(app, ind+1);
		app << "  diffuse  = \t";
		_kd->Describe(app, ind); app << "\n";
	}


	if (_k) {
		indent(app, ind+1);
		app << "  k    = \t";
		_k->Describe(app, ind); app << "\n";
	}

  app << flush;

  return;
}

//========================================================================
bool
CSurfaceMinnaert::GetAlbedo(CValues &albedo,
                          const CHitPointInfo &hitInfo,
                          EDistributionType   distType) const
{
	assert(_kd);
	if (distType & EE_REFLECTED_DIFFUSE) {
	  albedo = kd(hitInfo);
	  return true;
	}
	return false;
}


//========================================================================
//
//         kd * cos(thetaLight)^k * cos(thetaViewer)^(k-1)
// BRDF = -------------------------------------------------
//							    Pi
// note that for k = 1 it is a Lambertian surface
bool
CSurfaceMinnaert::EvaluateBSDF(CValues &result,
			 const CHitPointInfo &hitInfo,
			 const CVector3D     &dirIn,
			 EDistributionType   distType) const
{
	assert(_kd);
	assert(_k);
	if(distType & EE_REFLECTED_DIFFUSE) 
	{
		CVector3D N          =  hitInfo.GetNormal();
		float lightCos       =  fabs(DotProd(dirIn, N));
		float incEyeCos		 =  fabs(-hitInfo.GetDotProd());

		CValues kval;
		hitInfo.GetTextureValue(kval, *_k, CMappingBase::EE_KSI);
		float k  = kval.Get(0);

		result = kd(hitInfo) * M_1_PI * 
			pow(Max(lightCos,(float)WTINY),k) *
			pow(Max(incEyeCos,(float)WTINY),k-1); 
		return true;
	}
	else
		return false;
}


//========================================================================


int
CSurfaceMinnaert::SpawnStochasticRays(CContributionArray  &carray,
							const CHitPointInfo &hitInfo,
                            CSamplingInfo       &samplingInfo,
							EDistributionType	distType) const
{
	int maxRays = samplingInfo.GetMaxSamples();
	assert(_kd);
	assert(_k);

	if ( !(distType & EE_REFLECTED_DIFFUSE) ||
	   (maxRays < 1) )
	return 0; // no rays

	// store the seed to remain the same if possible, depending
	// on the random generator.
	samplingInfo.StoreSeed();

	// build coordinate system
	CVector3D N = hitInfo.GetNormal();
	if (hitInfo.GetDotProd() > 0.0)
	{
		N = -N;
	}

	// spawn the rays using stratification
	int spawned =
	SpawnStochasticRaysStratify(carray, hitInfo, EE_REFLECTED_DIFFUSE,
				samplingInfo, N);

	samplingInfo.IncDimension(2);

	samplingInfo.RecoverSeed();

#ifdef __TRAVERSAL_STATISTICS
	world->_statistics->reflectedRays += spawned;
#endif

	return spawned;
}

//========================================================================
//
int
CSurfaceMinnaert::SpawnStochasticRaysStratify(CContributionArray  &carray,
			    const CHitPointInfo &hitInfo,
			    EDistributionType   distType,
			    CSamplingInfo       &samplingInfo,
			    const CVector3D &N) const
{
    ERayType rayType = REFLECTED_RAY;


	// build the coordinate system
	CVector3D U = ArbitraryNormal(N);     // U is perpendicular to N
	CVector3D V = CrossProd(N,U);         // orthonormal base with N and U

	int maxRays = samplingInfo.GetMaxSamples();

	CValues kd_local( kd(hitInfo) );

	for(int i = 0; i < maxRays ; i++) {
	CContributionItem *contrib = carray.AllocateNext();

	if (i)
	  // increase the seed not to generate the same random sample again
	  samplingInfo.IncSeed();

	// Sets the probability for generated direction
	CVector2D vv(samplingInfo.Sample(0), samplingInfo.Sample(1));

	// this is for Lambertian surface
	// contrib->prob = RND::hemisph_cos(dirN, vv);
	// for Minnaert surface it is : 
	
	CVector3D dirN;
	// instead of Lambertian cos^-1(sqrt(r)), where r is rand number in (0,1)
	// for Minnaert, where 
	//         kd * cos(thetaLight)^k * cos(thetaViewer)^(k-1)
	// BRDF = -------------------------------------------------
	//							    Pi
	// it is cos^-1((k+1)sqrt(r)), where (k+1)sqrt is (k+1)-th sqrt 
	// == cos^-1(pow(r,1/(k+1)))
	// for k == 1 it is cos^-1(pow(r,1/2)) = cos^-1(sqrt(r)) and it is
	// Lambertian surface
	assert(_k);
	CValues kval;
	hitInfo.GetTextureValue(kval, *_k, CMappingBase::EE_KSI);
	float k  = kval.Get(0);

	float tmp = powf(1.0 - vv.xx,1.f/(k+1));
	float tmp2 = 2.0*M_PI*vv.yy;
	dirN.SetValue( cos(tmp2) * tmp, sin(tmp2) * tmp, sqrt(vv.xx) );
	contrib->prob = sqrt(vv.xx) / M_PI;

	// transform from local coordinates
	CVector3D dirRes = dirN.x * U + dirN.y * V + dirN.z * N ;
	// set the reflected ray, direction is normalized here
	contrib->ray.Init(hitInfo.GetPoint(), dirRes, rayType);
	// set the flags concerning ray type
	contrib->SetFlags(CContributionItem::EE_ReflectedRay);
	// type of interaction
	contrib->interaction = distType;
	// diffuseness of the bounce
	contrib->diffuseness = 1.0;
	// Evaluate the value of BSDF for given input and generated
	// output direction
	/* weight = BSDF * cos(Light, N) / PDF =
			  = pd / M_PI * cos(Light, N) / (cos(Light, N)/M_PI) = pd    */
	contrib->weigth = kd_local ;
	} // for i

	return maxRays;
}



int 
CSurfaceMinnaert::GetNumParameters() const
{
	return 2; // kd, k
}
//========================================================================

CValues
CSurfaceMinnaert::GetParameter(int parameterIndex) const
{
	CValues c(0,0,0);
	switch (parameterIndex)
	{
		case 0 :
			_kd->GetValue(c,CHitPointInfo::constMapInfo);
			break;
		case 1 :
			_k->GetValue(c,CHitPointInfo::constMapInfo);
	}
	return c;
}
//========================================================================

CValues
CSurfaceMinnaert::GetParameterConstraints(int parameterIndex) const
{
	CValues c(CValues::EE_WeigthN);
	switch (parameterIndex)
	{
		case 0 :
			_setBasicConstraints(EE_ColorRGB,c);
			break;
		case 1 :
			c.SetRepresentation(CValues::EE_Weigth3);
			c.Set(0,0); // min k 
			c.Set(1,10); // max k 
			c.Set(2,0.5); // default
			break;
	}
	return c;
}
//========================================================================

const char* 
CSurfaceMinnaert::GetParameterDescription(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 :
		return BrdfStrings::KAlbedoDesc;
	case 1 :
		return BrdfStrings::KLimbDarkDesc;
	default :
		return BrdfStrings::KInvalidParameter;
	}
}
//========================================================================

CSurface::EParameterUIType
CSurfaceMinnaert::GetParameterSyntax(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 : // diffuse 
		return EE_ColorRGB;
	case 1 : // limb darkening parameter k
		return EE_Float;
	default :
		return EE_Unknown;
	}
}
//========================================================================

const char*
CSurfaceMinnaert::GetParameterTitle(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 :
		return BrdfStrings::KAlbedoTitle;
	case 1 :
		return BrdfStrings::KLimbDarkTitle;
	default :
		return BrdfStrings::KInvalidParameter;
	}
}
//========================================================================

void
CSurfaceMinnaert::SetParameter(int parameterIndex, const CValues& value)
{

	switch (parameterIndex)
	{
	case 0 :
		{
			CConstantTexture* newTex = new CConstantTexture(value);
			SetKd(newTex);
			break;
		}
	case 1 :
		{
			CConstantTexture* newTex = new CConstantTexture(value);
			SetK(newTex);
			break;
		}
	}
}
//========================================================================

const char* 
CSurfaceMinnaert::GetHelp() const
{
	return BrdfStrings::KMinnaertDesc;
}

__END_BRDFLIB_SOURCE