// ===================================================================
// $Id$
//
// neumann.h
//    Implementation of Neumann metallic BRDF surface.
//
// Class: CSurfaceNeumann
//
// Initial coding by Jiri Nekola within diploma thesis project, November 2011

// Reference:
//  1. Neumann L., Neumann A., Kalos L.S. "Reflectance models with fast importance 
//  sampling", Computer Graphics Forum, Volume 18, Issue 4, pp. 249–265, 1999


#ifndef __SURFACE_NEUMANN_H__
#define __SURFACE_NEUMANN_H__

#include "configh.h"
#include "surface.h"

__BEGIN_BRDFLIB_HEADER

// Forward-declare classes
class CHitPointInfo;
class CTexture;

// ------------------------------------------------------------------------------
/// Neumann metallic BRDF class implemented according to the original paper [1]
// ------------------------------------------------------------------------------
class CSurfaceNeumann :
  public CSurface
{
public:
	/**
		 Constructs the Neumann surface.
		 @param rho_d (in)  The diffuse reflectance texture.
		 @param rho_s (in)  The specular reflectance texture - must be Fresnel type.
		 @param shine (in)  Shininess of the material
	*/
	CSurfaceNeumann(CTexture *rho_d, CTexture *rho_s, CTexture *shine):
	  CSurface(EE_NONE)
	{
		if(rho_d != NULL)
		{
			_diffuse  = rho_d;
			_setSupportedType(EE_REFLECTED_DIFFUSE);
		}

		if(rho_s != NULL)
		{
			if (!rho_s->IsFresnel())
			{
				WARNING_INFO("CSurfaceNeumann::CSurfaceNeumann() :\n\
Specular texture must be of CFresnelTexture type.");
			}
			_specular  = rho_s;
			_setSupportedType(EE_REFLECTED_SPECULAR);
		}

		if(shine)
		{
			_shininess = shine;
		}
		else
		{
			WARNING_INFO("CSurfaceNeumann::CSurfaceNeumann() :\n\
this surface needs shininess texture!");
			return;
		}
	}

	CSurfaceNeumann():
		CSurface(EE_NONE),
		_diffuse(0),
		_specular(0),
		_shininess(0)
	{
	}

	virtual ~CSurfaceNeumann() 
	{
		if (_diffuse)
		{
			delete _diffuse;
		}
		if (_specular)
		{
			delete _specular;
		}
		if (_shininess)
		{
			delete _shininess;
		}
	}

	/**
	 Returns the value of diffuse coefficient at given point.
	 (kd is albedo)
	*/
	inline CValues kd(const CHitPointInfo &hitInfo) const
	{
		CValues ret(CValues::EE_Weigth3);
		hitInfo.GetTextureValue(ret, *_diffuse, CMappingBase::EE_KD);
		return ret;
	}

	inline void SetKd(CTexture* kd)
	{
		AssignPointer(_diffuse,kd); 
	}

	inline void SetKs(CTexture* ks)
	{
		AssignPointer(_specular,ks);
	}

	inline void SetShininess(CTexture* shine)
	{
		AssignPointer(_shininess,shine);
	}

	virtual void Describe(ostream &app, int ind) const;

	/**
		Deep copy this object.
	*/
	virtual CSurface *Dup() const
	{
		CValues value;
		_diffuse->GetValue(value,CHitPointInfo::constMapInfo);
		CConstantTexture* diffuse = new CConstantTexture(value);
		_specular->GetValue(value,CHitPointInfo::constMapInfo);
		CConstantTexture* specular = new CConstantTexture(value);
		_shininess->GetValue(value,CHitPointInfo::constMapInfo);
		CConstantTexture* shine = new CConstantTexture(value);

		return new CSurfaceNeumann(diffuse,specular,shine);
	}

	virtual bool
	GetAlbedoMax(CValues &albedo,
		const CHitPointInfo &hitInfo,
		EDistributionType   distType = EE_ALL) const
	{
		bool res = false;
		albedo.Set(0);
		// TODO : implement
		if (distType & EE_REFLECTED_DIFFUSE) {
			albedo += kd(hitInfo);
			res = true;
		}
		if (distType & EE_REFLECTED_SPECULAR) {
			CValues ret(CValues::EE_Weigth3);
			hitInfo.GetTextureValue(ret, *_diffuse, CMappingBase::EE_KD);
			albedo += ret; 
			res = true;
		}
		return res;
	}

	virtual bool GetDefaultColor(CValues &defaultColor,
			       const CHitPointInfo &hitInfo) const	   
	{
		defaultColor.Set(0.0);
		return true;
	}

	virtual char* GetName() const
	{return "CSurfaceNeumann";}

	virtual bool
	EvaluateBSDF(CValues             &result,
	       const CHitPointInfo &hitInfo,
	       const CVector3D     &dirIn,
	       EDistributionType   distType = EE_ALL) const;

	virtual bool
	GetAlbedo(CValues &albedo,
	       const CHitPointInfo &hitInfo,
	       EDistributionType   distType=EE_ALL) const;

	virtual int
	SpawnDeterministicRays(CContributionArray  &carray,
		         const CHitPointInfo &hitInfo,
       		         EDistributionType   distType = EE_ALL) const
	{
		return 0;
	}

	virtual float GetAverageDiffuseness(void) const  {
		// ??
		return 0.0;
	}

	virtual float GetDiffuseness(const CHitPointInfo &,
			       EDistributionType distType = EE_ALL) const
	{
		// ??
		return 0.0;
	}

	virtual int
	SpawnStochasticRays(CContributionArray  &carray,
		      const CHitPointInfo &hitInfo,
		      CSamplingInfo       &samplingInfo,
		      EDistributionType    distType = EE_ALL) const;

	virtual inline EType GetType() const { return  EE_BSDFMicrofacetRefl; }

	virtual const char* GetParameterTitle(int parameterIndex) const;

	virtual const char* GetParameterDescription(int parameterIndex) const;

	virtual EParameterUIType GetParameterSyntax(int parameterIndex) const;

	virtual void SetParameter(int parameterIndex, 
							const CValues& value);

	virtual CValues GetParameter(int parameterIndex) const;

	virtual CValues GetParameterConstraints(int parameterIndex) const;

	virtual int GetNumParameters() const;

	virtual const char* GetHelp() const;

protected:
	CTexture			*_diffuse;	// Diffuse color of the object
	CTexture			*_specular;	// Specular color of the object
	CTexture			*_shininess;// shininess of the material

	/**
		Auxiliary method for evaluating Cmax constant. See ref[1] for more info.
	*/
	inline float EvaluateCmax(float shininess) const;
	/**
		Auxiliary method for evaluating R, based on a given random number. See ref[1] for more info.
	*/
	inline float EvaluateR(float shininess, float rndNum) const;

	/**
		Auxiliary method for ray generation within local coordinate system.
		For more info, see ref[1], Page 6, for GenerateDirectionWithBasicBRDF()
		method.

		@param R(in) A radius for circle generation.
		@param V(in) Viewer direction. Should be in local coordinate system with normal (0,1,0)
		@param N(in) Normal of the surface. Should be in local coordinate system, therefore (0,1,0)
		@param L(out) Generated direction L in local coordinate system with normal (0,1,0)

		@return Albedo = weight of such generated direction.
	*/
	float GenerateLocalDirWithBasicBRDF(float		R, 
								  const CVector3D&	V, 
								  const CVector3D&	N, 
								  CVector3D&		L) const;

	virtual int SpawnStochasticSpecularRays(const CValues       &kd_local,
											const CValues       &s_local,
											CContributionArray  &carray,
											const CHitPointInfo &hitInfo,
											CSamplingInfo       &samplingInfo,
											int                 maxRays) const;

	virtual int SpawnStochasticDiffuseRays(const CValues        &kd_local,
											const CValues        &s_local,
											CContributionArray   &carray,
											const CHitPointInfo  &hitInfo,
											CSamplingInfo        &samplingInfo,
											int                  maxRays) const;
protected:
	// the auxiliary storage of random variable
	typedef vector<double> rnd_array;
	//static double aux_rnd;
	static rnd_array diffuse_rnd;
	static rnd_array specular_rnd;
};

__END_BRDFLIB_HEADER

#endif // __SURFACE_NEUMANN_H__