/* PhBBilinearInterpolation.C: PhBBilinearInterpolation nodes (public source) */

#include <math.h>
#include "PhBBilinearInterpolation.H"

namespace xrml {
void PhBBilinearInterpolation::render(void)
{
  if(texture) texture->render();
  if(value == NULL)
    value = new float[texture->nrChannels()];
  if(filteredValue == NULL)
    filteredValue = new float[texture->nrChannels()];
}

float *PhBBilinearInterpolation::values(double u, double v, int *nrChannels)
{
  int nrChan = 0;

  if(texture)
  {
    // weightsbasis
    float wx = (u * texture->width()) - floor(u * texture->width());
    float wy = (v * texture->height()) - floor(v * texture->height());
    
    // next pixel (horizontal and vertical)
    float u2 = u + (1. / texture->width());
    float v2 = v + (1. / texture->height());
    
    // get values
    float *val1 = texture->values(u2, v2, &nrChan);
    float *val2 = texture->values(u , v2, NULL);
    float *val3 = texture->values(u2, v , NULL);
    float *val4 = texture->values(u , v , NULL);
    
    for(int i=0; i < nrChan; i++)
      value[i] = (wy*(wx*val1[i] + (1 - wx)*val2[i]) + (1 - wy)*(wx*val3[i] + (1-wx)*val4[i]));
  }

  if(nrChannels) *nrChannels = nrChan;
  return value;
}


// Filtered values
// TODO good mipmap support, transfer to a separate class???

// utils
inline double sqr(double a) { return (a*a); }
#ifndef MAX
#define MAX(a, b)               ((a) > (b) ? (a) : (b))
#endif


float *PhBBilinearInterpolation::filteredValues(const Vec2& uv, const Vec2& da, 
						 const Vec2& db, int *nrchannels)
{
  // -- TODO:
  //mipmap->FilteredValue(uv, da, db, filteredValuesBuffer);
  //return filteredValuesBuffer;

  // -- Still hardcoded...
  return boxFilteredValues(uv, da, db, nrchannels);
  // return gaussianFilteredValues(uv, da, db, nrchannels);
}

float *PhBBilinearInterpolation::boxFilteredValues(const Vec2& uv, const Vec2& da, 
						    const Vec2& db, int *nrchannels)
{
  // size
  long w = width();
  long h = height();

  // number of channels
  long channels = nrChannels();
  if (nrchannels) *nrchannels = channels;

  // Determine the number of samples over da and db
  Vec2 currenta = uv;

  long a_pixels = (long)(2.*sqrt(sqr(da.u * w) + sqr(da.v * h))) + 1;
  Vec2 adelta;

  if((a_pixels > w) && (a_pixels > h))
    a_pixels = MAX(w, h);

  if(a_pixels <= 1) 
  {
    a_pixels = 1;
    adelta = Vec2(0.,0.);
  }
  else
  {
    currenta -= da;
    adelta = da / (0.5 * a_pixels);
  }


  long b_pixels = (long)(2.*sqrt(sqr(db.u * w) + sqr(db.v * h))) + 1;
  Vec2 bdelta;

  if((b_pixels > w) && (b_pixels > h))
    b_pixels = MAX(w, h);

  if(b_pixels <= 1) 
  {
    b_pixels = 1;
    bdelta = Vec2(0.,0.);
  }
  else
  {
    currenta -= db;
    bdelta = db / (0.5 * b_pixels);
  }

#ifdef FILTER_DEBUG
  double l_da = 2* (da.norm());  // -da -> da
  double l_db = 2* (db.norm());  // -db -> db
  cerr << "da " << da << "  db " << db << "\n";
  cerr << "l_da " << l_da << "  l_db " << l_db << "\n";
  cerr << "a_pixels " << a_pixels << "  b_pixels " << b_pixels << "\n";
#endif

  // Init
  int i;
  for(i =0; i < channels; i++)
    filteredValue[i] = 0;

  // Iterate and average weights
  for(i = 0; i < a_pixels; i++)
  {
    Vec2 current = currenta;

    for(int j = 0; j < b_pixels; j++)
    {
      float *tmpVal = values(current.u, current.v);
      
      for(int ch = 0; ch < channels; ch++)
      {
	filteredValue[ch] += tmpVal[ch];
      }

      current += bdelta;
    } // for j
    
    currenta += adelta;
  } // for i

  for(i =0; i < channels; i++)
    filteredValue[i] /= (float)(a_pixels * b_pixels);
  
  return filteredValue;
}



float *PhBBilinearInterpolation::gaussianFilteredValues(const Vec2& uv, const Vec2& da, 
					       const Vec2& db, int *nrchannels)
{
  // da and db form the axes of the elliptic gaussian
  // their length corresponds to the standard deviation

  // size
  long w = width();
  long h = height();

  // number of channels
  long channels = nrChannels();
  if (nrchannels) *nrchannels = channels;

  // Determine the number of samples over da and db

  Vec2 currenta = uv;

  long a_pixels = (long)(2.*sqrt(sqr(da.u * w) + sqr(da.v * h))) + 1;

  if((a_pixels > w) && (a_pixels > h))
    a_pixels = MAX(w, h);

  Vec2 adelta;

  if(a_pixels <= 1) 
  {
    a_pixels = 1;
    adelta = Vec2(0.,0.);
  }
  else
  {
    currenta -= da;
    adelta = da / (0.5 * a_pixels);
  }


  long b_pixels = (long)(2.*sqrt(sqr(db.u * w) + sqr(db.v * h))) + 1;

  if((b_pixels > w) && (b_pixels > h))
    b_pixels = MAX(w, h);

  Vec2 bdelta;

  if(b_pixels <= 1) 
  {
    b_pixels = 1;
    bdelta = Vec2(0.,0.);
  }
  else
  {
    currenta -= db;
    bdelta = db / (0.5 * b_pixels);
  }

#ifdef FILTER_DEBUG
  double l_da = 2* (da.norm());  // -da -> da
  double l_db = 2* (db.norm());  // -db -> db
  cerr << "da " << da << "  db " << db << "\n";
  cerr << "l_da " << l_da << "  l_db " << l_db << "\n";
  cerr << "a_pixels " << a_pixels << "  b_pixels " << b_pixels << "\n";

  //float adelta_n = adelta.norm();
  //float bdelta_n = bdelta.norm();
  //float da_n = l_da / 2.0;
  //float db_n = l_db / 2.0;
#endif

  // Init
  int i;
  for(i =0; i < channels; i++)
    filteredValue[i] = 0;

  // Iterate and average weights

  for(i = 0; i < a_pixels; i++)
  {
    Vec2 current = currenta;

    for(int j = 0; j < b_pixels; j++)
    {
      float *tmpVal = values(current.u, current.v);

      // Compute gaussian weight, filter over 2 sigma area

      float ua = 8*(i / (float)a_pixels - .5);  // ua == 0 in center, -+2 at borders
      float ub = 8*(j / (float)b_pixels - .5);  // ub == 0 in center, -+2 at borders

      float weight = (1. / (2 * M_PI)) * exp(-0.5 * ( ua*ua + ub*ub ));

      weight = weight * 64. / (a_pixels * b_pixels);

      for(int ch = 0; ch < channels; ch++)
      {
	filteredValue[ch] += weight*tmpVal[ch];
      }

      current += bdelta;
    }
    
    currenta += adelta;
  }

  // Gaussian filter is normalised, so just return the sum
  return filteredValue;
}


}  // namespace xrml
