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

void CWardTextured::InitTexture()
{
	_tex_image = new CVector3D[TEX_WIDTH * TEX_HEIGHT];
	int chessX, chessY;
	// make some hardwired texture - e.g. chessboard (10x10 squares)
	for (int i = 0; i < TEX_WIDTH; i++)
	{
		for (int j = 0; j < TEX_HEIGHT; j++)
		{
			chessX = i / 50;
			chessY = j / 50;
			if ((chessX + chessY)%2 == 0)
			{
				_tex_image[j*TEX_WIDTH + i].SetValue(0.1,0.1,0.1);
			}
			else
			{
				_tex_image[j*TEX_WIDTH + i].SetValue(0.9,0.9,0.9);
			}
		}
	}
}

int
CWardTextured::SpawnStochasticRays(CContributionArray  &carray,
		                    const CHitPointInfo &hitInfo,
                                    CSamplingInfo       &samplingInfo,
				    EDistributionType distType) const
{
  int maxRays = samplingInfo.GetMaxSamples();
  assert(maxRays > 0);

  CVector2D texCoords = hitInfo.GetTexCoords();

  //!!!!!!!!!!!!!!!!!!!!!!!! K.P.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  // It should be changed for Halton sequence. But for simple random
  // numbers it is in this way.
  diffuse_rnd.clear();
  specular_rnd.clear();
  //!!!!!!!!!!!!!!!!!!!!!!!! K.P.!!!!!!!!!!!!!!!!!!!!!!!!!!

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

  // diffuse component
  CValues kd_local(CValues::EE_Weigth3);
  // specular component
  CValues ks_local(CValues::EE_Weigth3);
  CVector2D slopeXY(0,0), slopeXY_sq(0,0);

  // compute diffuse and specular
  float akd = 0.0f, aks = 0.0f, tmp_ratio = 0;

  if (_diffuse && (distType & EE_REFLECTED_DIFFUSE)) {
    int idx_x = (int)(texCoords.xx * TEX_WIDTH);
	int idx_y = (int)(texCoords.yy * TEX_HEIGHT);
	CVector3D mapping = _tex_image[idx_y * TEX_WIDTH + idx_x];
	kd_local.Set(0,mapping[0]);kd_local.Set(1,mapping[1]);kd_local.Set(2,mapping[2]);
    akd = kd_local.Luminance();
  }

  if (_specular && (distType & EE_REFLECTED_SPECULAR)) {
    hitInfo.GetTextureValue(ks_local, *_specular, CMappingBase::EE_KS);
    aks = ks_local.Luminance();//*(n+2.0)/(2.0*M_PI))*

    if(!_slope)
    {
	  WARNING_INFO("CWardIsotropic::SpawnStochasticRays() :\n\
Slope structure should be set to compute specular part of Ward model.");
      return 0;
    }

    CTexture *slope = _slope->GetSlope();
    if(!slope)
    {
	  WARNING_INFO("CWardIsotropic::SpawnStochasticRays() :\nGlossy term requires structure 'slope' which consists of 'slopeX' term.");
      return 0;
    }
    CValues slopeXc(0,0,0);
    hitInfo.GetTextureValue(slopeXc, *slope, CMappingBase::EE_KSI);
    float slopeXf = slopeXc.Get(0);
    (slopeXf < WTINY) ? slopeXf = WTINY : slopeXf = slopeXf;

    slopeXY.xx    = slopeXf;
    slopeXY_sq.xx = slopeXf * slopeXf;
    tmp_ratio = aks/(slopeXY_sq.xx * 4.0 * M_PI);
  }

  float albedo_whole = akd + aks;

  if(albedo_whole < 1e-05) {
	WARNING_INFO("CWardIsotropic::SpawnStochasticRays() :\nAlbedo of this surface is < 1e-05. It is a black body!");
    return 0;
  }
  if( albedo_whole >= 1.0 ) {
	WARNING_INFO("CWardIsotropic::SpawnStochasticRays():\nAlbedo > 1.0\n\
To keep the energy conservation requirement the total albedo\n\
(albedo_specularity + albedo_diffuse) should be less than 1.\n\
(If it is equal to 1, termination of a random walk with\nRussian Roulette will be impossible)");
  }

  int numDiffuse = 0;
  int numSpecular = 0;

  int count = maxRays;
  while(count--)
  {
     float rnd_num = samplingInfo.Sample(0);


     // This RND number is from the User's program. Actually, it should be
     // fit into the range 0..albedo_whole if some event besides absorbtion
     // was choosen. Therefore, we shoul make RND fall into the [0..1)
     if(count >= maxRays-1)
     {
       if(rnd_num <= albedo_whole)
         rnd_num /= albedo_whole;  // make first RND fall into the [0..1)
     }
     assert(rnd_num <= 1.0);

     float alpha1 = akd/albedo_whole;
     float alpha2 = aks/albedo_whole;

     if((rnd_num <= (alpha1) && alpha1 >= 1e-6) || (tmp_ratio <= 1e-06)) {
       // Reuse random number for generation of one component of the random
       // direction. Scale it to fall into the range [0..1)
       diffuse_rnd.push_back(rnd_num/alpha1);
       // Choose another component of direction randomly
       diffuse_rnd.push_back(samplingInfo.Sample(1));
       //cout << "here\n";
       numDiffuse++;
     }
     else {
       // Reuse random number for generation of one component of the random
       // direction. Scale it to fall into the range [0..1)
       specular_rnd.push_back((rnd_num-alpha1)/alpha2);
       // Choose another component of direction randomly
       specular_rnd.push_back(samplingInfo.Sample(1));
       numSpecular++;
     }
     samplingInfo.IncDimension(2);
     samplingInfo.RecoverSeed();
  }

  int numRays = 0;

  if (numDiffuse) {
    numRays += SpawnStochasticDiffuseRays(kd_local, ks_local, slopeXY, slopeXY_sq,
    					  carray, hitInfo, samplingInfo,
					  numDiffuse);
  }

  // The number of rays for diffuse interaction
  //int indexDiffuse = carray.GetUsedItems();

  if (numSpecular) {
    numRays += SpawnStochasticSpecularRays(ks_local, slopeXY, slopeXY_sq, kd_local,
					   carray, hitInfo, samplingInfo,
					   numSpecular);
  }

  int indexMax = carray.GetUsedItems();
  assert(indexMax > 0);
  samplingInfo.IncDimension(2);

  // recover the seed for the next dimension to be sampled
  samplingInfo.RecoverSeed();
  assert(indexMax == maxRays);

  return numRays;
}

bool
CWardTextured::EvaluateBSDF(CValues &result,
			 const CHitPointInfo &hitInfo,
			 const CVector3D     &dirIn,
			 EDistributionType   distType) const
{
  bool retval = false;
  float d = Magnitude(dirIn);
  CVector3D N          =  hitInfo.GetNormal();
  CVector3D anisAxis   =  hitInfo.GetAnisAxis();
  CVector2D texCoords  =  hitInfo.GetTexCoords();

  assert(fabs(DotProd(N, anisAxis)) < 1e-06);

  float lightCos       =  DotProd(dirIn, N);
  float incEyeCos  =  -(hitInfo.GetDotProd());

  if(lightCos >= -1e-06 && incEyeCos >= -1e-06)
  {
    if(lightCos < 0) lightCos = 0;
    else
    {
      if(lightCos < WTINY) lightCos       = WTINY;
    }

    if(incEyeCos < 0) incEyeCos = 0;
    else
    {
      if(incEyeCos < WTINY) incEyeCos  = WTINY;
    }

    if ( (d < 1.0-1e-06) || (d > 1.0+1e-06) )
      Normalize(dirIn);

    if(_diffuse && (distType & EE_REFLECTED_DIFFUSE)) {
	  int idx_x = (int)(texCoords.xx * TEX_WIDTH);
	  int idx_y = (int)(texCoords.yy * TEX_HEIGHT);
	  CVector3D mapping = _tex_image[idx_y * TEX_WIDTH + idx_x];
	  result.Set(0,mapping[0]);result.Set(1,mapping[1]);result.Set(2,mapping[2]);
      retval = true;
    }

    if((_specular && (distType & EE_REFLECTED_GLOSSY)) && _slope) {
      CTexture *slope = _slope->GetSlope();
      if(slope)
      {
        CValues rho_s, slopeX;
        hitInfo.GetTextureValue(rho_s,  *_specular, CMappingBase::EE_KS);

        hitInfo.GetTextureValue(slopeX, *slope,     CMappingBase::EE_KSI);
        float a2  = (slopeX.Get(0))*(slopeX.Get(0));
        if(a2 < WTINY) a2 = WTINY;

        float a2_pi_4 = 4.0*M_PI*a2;

	if(rho_s.Luminance()/a2_pi_4 > 1e-06)
	{

          CVector3D  H  =  Normalize(dirIn - (hitInfo.GetRay()->GetDir()));
          float cos_HN  =  DotProd(H, N);
          assert(cos_HN >= 0);

          if(cos_HN < WTINY)  cos_HN = WTINY;

	  float tan2_HN =  1.0f/(cos_HN * cos_HN) - 1.0;
          if(tan2_HN < 0) tan2_HN = 0;

	  float ex  = exp( -tan2_HN/a2 );
          float k;
          if ( _max)
            k = ex / (Max(fabs(lightCos),fabs(incEyeCos)) * a2_pi_4);
          else k = ex / ( sqrt(lightCos*incEyeCos) * a2_pi_4);
          result += rho_s * k;
          retval = true;
        }
      }
    }

    if(retval)
    {
      result *= lightCos;
    }
  }
  else
  {
    retval = false;
  }
  return retval;
}