#include "config.h"
#include "surface_microfacet.h"

// GOLEM headers
#include "ward.h"
#include "texture.h"
#include "functions.h"
#include <iostream>

__BEGIN_BRDFLIB_SOURCE

/**********************************************************************
 *
 *             CSlopeMicrofacets methods
 *
 **********************************************************************/

CSlopeMicrofacets::CSlopeMicrofacets()
{
	_anisotropic = false;
}

CSlopeMicrofacets::CSlopeMicrofacets(CTexture* slopeX)
{
	if(slopeX)
	{
	  _slopes.push_back(slopeX);
	}
	_anisotropic = false;
	_weights.push_back(1.0f);
}

CSlopeMicrofacets::CSlopeMicrofacets(CTexture *slopeX, CTexture *slopeY)
{
	if(slopeX && slopeY)
	{
	  _slopes.push_back(slopeX);
	  _slopes.push_back(slopeY);
	  _anisotropic = true;
	  _weights = vector<float>(2, 0.5f);
	}
	else
	{
	  _anisotropic = false;
	}
}

CSlopeMicrofacets::CSlopeMicrofacets(std::vector<CTexture*> slopes)
{
	if((int)slopes.size() > 0)
	{
	  _slopes = slopes;
	  _anisotropic = true;

	  const float weight = 1.0/slopes.size();
	  _weights = vector<float>(slopes.size(), weight);
	}
	else
	{
	  _anisotropic = false;
	}
}

CSlopeMicrofacets::CSlopeMicrofacets(vector<CTexture *> slopes, vector<float> weights)
{
	int slopes_size  = slopes.size();
	int weights_size = weights.size();

	if(slopes_size <= 0 || (slopes_size > 1 && weights_size <= 0)) return;
	if((slopes_size-1) != weights_size)
	{
		WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets() :\n\
Number of weights for microfacets distributions functions\n\
must be equal to the number of these functions minus 1");
	  return;
	}

	if((int)slopes.size() == 2 && (slopes[0] && slopes[1]))
	  _anisotropic = true;
	else
	  _anisotropic = false;

	_slopes = slopes;


	float sum = 0;
	for(int i = 0; i < weights_size; i++)
	{
	  if(weights[i] <= 0 || weights[i] > 1)
	  {
		  WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
Each weight must be a positive number <= 1");
		return;
	  }
	  sum += weights[i];
	}
	if(sum >= 1.0)
	{
	  WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
Sum of all weights must be equal to 1");
	  return;
	}
	float last_weight = 1.0 - sum;
	weights.push_back(last_weight);
	_weights = weights;
}

CSlopeMicrofacets::~CSlopeMicrofacets()
{
	Clear();
}

void CSlopeMicrofacets::Set(CTexture *slopeX)
{
	if(slopeX)
	{
	  _slopes.push_back(slopeX);

	  if((int)_slopes.size() == 2)
		_anisotropic = true;
	  else
		_anisotropic = false;

	  const float weight = 1.0f/_slopes.size();
	  if((int)_weights.size() > 0) _weights.clear();
	  _weights = vector<float>(_slopes.size(),weight);
	}
}

void CSlopeMicrofacets::Set(CTexture *slopeX, int index)
{
	if(slopeX)
	{
	  if((int)_slopes.size() > index)
	  {
		AssignPointer(_slopes[index],slopeX);
	  }
	  else
		_slopes.push_back(slopeX);
	}

	if((int)_slopes.size() >= 2)
	  _anisotropic = true;
	else
	  _anisotropic = false;
}

void CSlopeMicrofacets::Set(CTexture *slopeX, CTexture *slopeY)
{
	Clear();

	if(slopeX && slopeY)
	{
	  _slopes.push_back(slopeX);
	  _slopes.push_back(slopeY);
	  _anisotropic = true;
	  _weights = vector<float>(2, 0.5f);
	}
	else
	{
	  _anisotropic = false;
	}
}

void CSlopeMicrofacets::Set(vector<CTexture *> slopes)
{
	if((int)slopes.size() > 0)
	{
	  Clear();
	  if((int)slopes.size() == 2 && (slopes[0] && slopes[1]))
		_anisotropic = true;
	  else
		_anisotropic = false;

	  _slopes = slopes;
	  const float weight = 1.0f/_slopes.size();
	  _weights = vector<float>(_slopes.size(),weight);
	}
}

void CSlopeMicrofacets::SetWeight(float weight,int index)
{
	int weight_size = (int)_weights.size();
	float auxSum = 0;
	for (int i = 0; i < weight_size; i++)
	{
		if (i!=index) 
			auxSum += _weights[i];
	}
	if (weight + auxSum > 1.0)
		weight = 1.0 - auxSum;
	if(weight_size > index)
	  _weights[index] = weight;
	else
	  _weights.push_back(weight);
}

void CSlopeMicrofacets::SetWeights(vector<float> weights)
{
	int slopes_size  = _slopes.size();
	if(slopes_size > 1 && (int)weights.size() <= 0) return;

	if((int)_weights.size() > 0) _weights.clear();
	if(slopes_size == 1)
	  _weights.push_back(1.0f);

	int weights_size = weights.size();

	if((slopes_size-1) != (int)weights.size())
	{
	  WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets() :\n\
	Number of weights for microfacets distributions functions\n\
	must be equal to the number of these functions minus 1");
	  return;
	}
	float sum = 0;
	for(int i = 0; i < weights_size; i++)
	{
	  if(weights[i] <= 0 || weights[i] > 1)
	  {
		WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
	Each weight must be a positive number <= 1");
		return;
	  }
	  sum += weights[i];
	}
	if(sum >= 1.0)
	{
	  WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
	Sum of all weights must be equal to 1");
	  return;
	}
	float last_weight = 1.0 - sum;
	weights.push_back(last_weight);
	_weights = weights;
}

void CSlopeMicrofacets::Set(std::vector<CTexture*> slopes, std::vector<float> weights)
 {
    int slopes_size  = slopes.size();
    int weights_size = weights.size();

    if(slopes_size <= 0 || (slopes_size > 1 && weights_size <= 0)) return;
    if((slopes_size-1) != weights_size)
    {
      WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets() :\n\
Number of weights for microfacets distributions functions\n\
must be equal to the number of these functions minus 1");
      return;
    }

    Clear();

    if((int)slopes.size() == 2 && (slopes[0] && slopes[1]))
      _anisotropic = true;
    else
      _anisotropic = false;

    _slopes = slopes;


    float sum = 0;
    for(int i = 0; i < weights_size; i++)
    {
      if(weights[i] <= 0 || weights[i] > 1)
      {
        WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
Each weight must be a positive number <= 1");
        return;
      }
      sum += weights[i];
    }
    if(sum >= 1.0)
    {
      WARNING_INFO("CSlopeMicrofacets::CSlopeMicrofacets():\n\
Sum of all weights must be equal to 1");
	  return;
    }
    float last_weight = 1.0 - sum;
    weights.push_back(last_weight);
    _weights = weights;
 }

void CSlopeMicrofacets::Clear()
{
	int slopesCnt = (int)_slopes.size();
	if( slopesCnt > 0)  
	{
		for (int i = 0; i < slopesCnt; i++)
		{
			delete _slopes[i];
		}
		_slopes.clear();
	}
	if((int)_weights.size() > 0) _weights.clear();
}

CTexture* CSlopeMicrofacets::GetSlope()
{
	if((int)_slopes.size() > 0)
	  return _slopes[0];
	return NULL;
}

CTexture* CSlopeMicrofacets::GetSlope(float &weight)
{
	if((int)_slopes.size() > 0)
	{
	  if((int)_weights.size() > 0) weight = _weights[0];
	  else weight = 1.0;
	  return _slopes[0];
	}
	return NULL;
}

CTexture* CSlopeMicrofacets::GetSlope(int index)
{
	if((index >= 0) && ((int)_slopes.size() > index))
	  return _slopes[index];
	return NULL;
}

CTexture* CSlopeMicrofacets::GetSlope(int index, float &weight)
{
	if((index >= 0) && ((int)_slopes.size() > index))
	{
	  if((int)_weights.size() > index) weight = _weights[index];
	  else weight = 1.0;
	  return _slopes[index];
	}
	return NULL;
}

CTexture* CSlopeMicrofacets::GetSlopeX()
{
	if((int)_slopes.size() > 0)
	  return _slopes[0];
	return NULL;
}

CTexture* CSlopeMicrofacets::GetSlopeY()
{
	if((int)_slopes.size() > 1)
	  return _slopes[1];
	return NULL;
}

void CSlopeMicrofacets::AddSlope(CTexture* slope, float weight)
{
	if(slope)
	{
	  _slopes.push_back(slope);
	  _weights.push_back(weight);
	}
	if (GetSlopesSize() > 1)
	{
		_anisotropic = true;
	}
}

void CSlopeMicrofacets::Describe(std::ostream &app, int ind)
{
	int size = _slopes.size();
	if(size > 0)
	{
	  if(_anisotropic)
	  {
		if((int)_slopes.size() > 1)
		{
		  app << "The Microfacet structure is anisotropic:\n";
		  indent(app, ind+2);
	  _slopes[0]->Describe(app, ind); app << "\n";
		  indent(app, ind+2);
	  _slopes[1]->Describe(app, ind); app << "\n";
		}
		else
		{
	  if((int)_slopes.size() > 0)
	  {
			app << "The Microfacet structure is isotropic:\n";
			indent(app, ind+2);
		_slopes[0]->Describe(app, ind); app << "\n";
		  }
		}
	  }
	  else
	  {
		app << "The Microfacet structure has " << _slopes.size();
		if((int)_slopes.size() == 1) app << " slope:\n";
	else                         app << " slopes:\n";

		for(int i = 0; i < size; i++)
		{
	  indent(app, ind+2);
	  (_slopes[i])->Describe(app, ind);
	  app << ", \twith weight = " << _weights[i] <<  "\n";
	   }
	  }
	}
	else
	{
	  app << "The Microfacet structure is empty!\n";
	}
}



/**********************************************************************
 *
 *             CSurfaceMicrofacet methods
 *
 **********************************************************************/

CSurfaceMicrofacet::CSurfaceMicrofacet() :
	CSurface(EE_NONE),
    _diffuse(0),
    _specular(0),
    _slope(0)
{}

CSurfaceMicrofacet::~CSurfaceMicrofacet()
{
	if (_slope)
	{
		delete _slope;
	}
	if (_specular)
	{
		delete _specular;
	}
	if (_specular)
	{
		delete _diffuse;
	}
}



bool
CSurfaceMicrofacet::GetAlbedoMax(CValues &albedo,
              const CHitPointInfo &hitInfo,
	      EDistributionType   distType) const
{
  CValues c(CValues::EE_Weigth3); // result - zero by default
  CValues d(CValues::EE_Weigth3);
  bool retval = false;

  if ((distType & EE_REFLECTED_DIFFUSE) && _diffuse) {
    hitInfo.GetTextureValue(d, *_diffuse, CMappingBase::EE_KD);
    c += d;
    retval = true;
  }

  if ((distType & _microfacetReflectionType()) && _specular) {
    hitInfo.GetTextureValue(d, *_specular, CMappingBase::EE_KS);
    c += d;
    retval = true;
  }

//#define _DEBUG_PHYSICAL_PLAUSIBILITY
#ifdef _DEBUG_PHYSICAL_PLAUSIBILITY
  // test if the albedo is smaller than 1.0 -- physically plausible
  if (c.Max() > 1.0) {
    if (_diffuse) {
      hitInfo.GetTextureValue(d, *_diffuse, CMappingBase::EE_KD);
      DEBUG << "diffuse = " << d << "\n";
    }
    if (_specular) {
      hitInfo.GetTextureValue(d, *_specular, CMappingBase::EE_KS);
      DEBUG << "specular = " << d << "\n";
    }
  }
#endif // _DEBUG_PHYSICAL_PLAUSIBILITY

  if (retval) {
    albedo = c;
    return true;
  }

  // unable to compute the value for some reason
#ifdef _DEBUG_PHYSICAL_PLAUSIBILITY
  if ((!_diffuse) && (!_specular)) {
    WARNING << "This is the black body - albedo cannot be computed" << endl;
  }
#endif // _DEBUG

  return false;
}
//========================================================================

const char* 
CSurfaceMicrofacet::GetHelp() const
{
	return BrdfStrings::KMicrofacetDesc;
}
//========================================================================

int 
CSurfaceMicrofacet::GetNumParameters() const
{
	return 3; // diffuse,specular,slope(0) of weight 1
}
//========================================================================

CValues
CSurfaceMicrofacet::GetParameter(int parameterIndex) const
{
	CValues c(0,0,0);
	switch (parameterIndex)
	{
	case 0 : //diffuse
		_diffuse->GetValue(c,CHitPointInfo::constMapInfo);
		break;
	case 1 : //specular
		_specular->GetValue(c,CHitPointInfo::constMapInfo);
		break;
	case 2 : //slopes[0] with weight 1
		_slope->GetSlopeX()->GetValue(c,CHitPointInfo::constMapInfo);
		break;
	default :
		break;
	}
	return c;
}
//========================================================================

CValues
CSurfaceMicrofacet::GetParameterConstraints(int parameterIndex) const
{
	CValues c(CValues::EE_WeigthN);
	switch (parameterIndex)
	{
	case 0 : //diffuse
	case 1 : //specular
		_setBasicConstraints(EE_ColorRGB,c);
		break;
	case 2 : //slopes[0] with weight 1
		_setBasicConstraints(EE_Weighted_XYZ,c);
		break;
	default :
		break;
	}
	return c;
}
//========================================================================

const char* 
CSurfaceMicrofacet::GetParameterDescription(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 :
		return BrdfStrings::KDiffuseDesc;
	case 1 :
		return BrdfStrings::KSpecularDesc;
	case 2 :
		return BrdfStrings::KSlopeDesc;
	default :
		return BrdfStrings::KInvalidParameter;
	}
}
//========================================================================

CSurface::EParameterUIType
CSurfaceMicrofacet::GetParameterSyntax(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 : //diffuse
		return EE_ColorRGB;
	case 1 : //specular
		return EE_ColorRGB;
	case 2 : //slope[0]
		return EE_Weighted_XYZ;
	default :
		return EE_Unknown;
	}
}
//========================================================================

const char*
CSurfaceMicrofacet::GetParameterTitle(int parameterIndex) const
{
	switch (parameterIndex)
	{
	case 0 :
		return BrdfStrings::KDiffuseTitle;
	case 1 :
		return BrdfStrings::KSpecularTitle;
	case 2 :
		return BrdfStrings::KSlopeTitle;
	default :
		return BrdfStrings::KInvalidParameter;
	}
}
//========================================================================

void
CSurfaceMicrofacet::SetParameter(int parameterIndex, const CValues& value)
{
	CConstantTexture* newTex = new CConstantTexture(value);
	switch (parameterIndex)
	{
	case 0 :
		SetDiffuse(newTex);
		break;
	case 1 :
		SetSpecular(newTex);
		break;
	case 2 :
		CSlopeMicrofacets* newSlopeMF = new CSlopeMicrofacets(newTex);
		SetSlope(newSlopeMF);
		break;
	}
}
//========================================================================


CSurface *
CSurfaceMicrofacet::Dup() const
{
  CSurfaceMicrofacet *ret = CreateNewObject();

  if (_diffuse)
    ret->SetDiffuse(_diffuse);
  if (_specular)
    ret->_specular = _specular;
  if (_slope)
    ret->_slope = _slope;

  ret->_supportedType = _supportedType;
  return ret;
}
//========================================================================


bool
CSurfaceMicrofacet::GetDefaultColor(CValues &defaultColor,
			       const CHitPointInfo &hitInfo) const
{
  // let us consider _diffuse color to be the default one
  if (_diffuse) {
    hitInfo.GetTextureValue(defaultColor, *_diffuse, CMappingBase::EE_KD);
    return false;
  }

  defaultColor.Set(0.0);
  return true;
}
//========================================================================

__END_BRDFLIB_SOURCE