#include "assert.h"
#include <math.h>
#include "planecache.h"
#include "globalstate.h"
#include "globals.h"
#include "common.h"

// # define NO_EYE_RAY_CACHE
// # define NO_PLANE_CACHE

void PlaneCache::newViewpoint (Camera *cam)
{
  //
  //                             ____________________
  //                            /        ^ yhat     /
  //                           /        /          /
  //    o------------------------------>----------> xhat
  //   eye        zhat       /                   /
  //                        /___________________/ near plane
  //

  // zhat:  vector from eye to view plane, normal to view plane
  Vec4 viewvec = cam->D();
  real dist = cam->Dist();
  _zhat = viewvec * dist;

  int newsize = 0;
  if ((_width != cam->x() || (_height != cam->y()) || (_fovx != cam->FovX()) || (_fovy != cam->FovY())))
      {
        //if view frusta has changed size, note it, b/c it means
        //we have to recompute our eye ray coefficients.
        _width = cam->x();
        _height = cam->y();
        _fovx = cam->FovX();
        _fovy = cam->FovY();
        newsize = 1;
      }

  real aspectRatio = _width/_height;
  
  // produce Right, perpendicular to viewdir, world-space up vector
  Vec4 Right;
  Vec4FastCross3 ( Right, cam->D(), cam->U() ); Right.MakeUnit();

  // view plane yhat is Right, cross D, scaled
  Vec4FastCross3 ( _yhat, Right, cam->D() );
  _yhat *= (dist * tanf(0.5 * _fovx / aspectRatio) /* * 0.5 */);  // SJT put 0.5 inside tan()

  // if vp width < vp height, width constrains viewing frustum
  if (aspectRatio < 1.) 
    _yhat /= aspectRatio;
  // xhat:  vector perpendicular to both zhat and yhat
  Vec4FastCross3 (_xhat, viewvec, _yhat);
  _xhat *= aspectRatio;

  _eye = cam->R();

  //precompute the close clipping plane - parallel to film plane, but
  //on the eye point.
  plane_from_point_normal (_eye, viewvec, _filmplane);

  //clear the cache
  clear();

#ifndef NO_EYE_RAY_CACHE
  if (newsize)  //cam changed size? need new eyerays!
    {
      makeEyeRayAndDPCache ();
    }
#endif

}

void PlaneCache::makeEyeRayAndDPCache ()
{
  assert (_eye_ray_cache);
  assert (_dot_product_cache);
  Vec4 *coeff = _eye_ray_cache;
  float *dot = _dot_product_cache;

  Vec4 znorm = _zhat; //zhat normalized.
  znorm.FastMakeUnit3();
  for (int y = 0 ; y < _biggestdim; y++)
      {
        for (int x =0; x < _biggestdim; x++)
          {
            coeff->Set (((2. * x + 1 +1.) / _width  - 1.),
                             ((2. * y + 1+ 1.) / _height - 1.), 1.);
            Vec4 pre = coeff->x() * _xhat + coeff->y() * _yhat + coeff->z() * _zhat;

            (*coeff) /= pre.Length();

	    pre.FastMakeUnit3();
	    *dot = pre.Dot3 (znorm);
	    
	    dot++;
            coeff++;
            }
      }
}

void PlaneCache::realCalcPlane (int dim, int where, Plane &plane)
{
  Vec4 vB, vT;

  //shoot a couple rays that are in that plane
  if (dim == X)
    {
      ComputeEyeRayDirection(where, int(_height - 1), vB);
      ComputeEyeRayDirection(where, 0, vT); 
    }
  else
    {
      ComputeEyeRayDirection(0 ,where, vB);
      ComputeEyeRayDirection(int(_width - 1), where, vT);
    }

  //cross'em to get the normal to the plane. Given a point in the plane (_eye)
  //that's all we need!
  //the X planes point to the right (+1 is right)
  //the Y planes point up.  (+1 is up)
  plane_from_point_normal (_eye, vT.Cross3(vB), plane );
      
}

void PlaneCache::clear(void)
{
#ifdef ONE_THREAD
  struct PlaneStruct *current;
  struct PlaneStruct *next;

  current = _list_head;
  while (current)
    {
      //make sure _list_tail is as we'd expect
      if (current->next == NULL) assert (current == _list_tail);

      current->calced = 0;
      next = current->next;
      current->next = NULL;
      current = next;
    }
#else
  //clear it the old-fashioned way
    int cur = 0;
    for (int i = 0; i < 2; i++)
      {
        for (int j =0 ; j < _biggestdim; j++)
          {
            _plane_cache[cur++].calced = 0;
          }
      }
#endif

  _list_head = _list_tail = NULL;
}


void PlaneCache::plane_from_point_normal(const Vec4 &p, const Vec4 &n, Plane &H)
{
  H = Plane(n.x(), n.y(), n.z(), -n.Dot3(p));
}

Vec4 PlaneCache::ComputeEyeRayDirection(int x, int y)
{

#ifdef NO_EYE_RAY_CACHE
  //extra + 1. because we really want to do
  //2. * (x+.5) + 1
  //which gives us the ray through the middle of the (x,y) pixel 
  Vec4 origin = _eye + _zhat			// SOLN
    + ((2. * x + 1. + 1.) / _width - 1.) * _xhat		// SOLN
    + ((2. * y + 1. + 1.) / _height - 1.) * _yhat;		// SOLN
  Vec4 val = origin - _eye;
  val.FastMakeUnit3();			// SOLN
  return val;
#else
  //look up the coeff's in the cache
  if ((x >=0) && (y >= 0) && (y < _height) && (x < _width))
    {
      Vec4 *coeff = _eye_ray_cache + y * _biggestdim + x;
      return coeff->x() * _xhat + coeff->y() * _yhat + coeff->z() * _zhat;
    }
  else
    {
      cerr << "asked for wild eye ray " << x << ", " << y << endl;
      cerr << "width is " << _width << ", height is " << _height << endl;
      assert (0);
      return Vec4 (0,0,0);
    }
#endif
}
void PlaneCache::ComputeEyeRayDirection(int x, int y, Vec4 &dir) {
  // AAA - Calculate ray from eye through pixel

#ifdef NO_EYE_RAY_CACHE
  //extra + 1. because we really want to do
  //2. * (x+.5) + 1
  //which gives us the ray through the middle of the (x,y) pixel 
  Vec4 origin = _eye + _zhat			// SOLN
    + ((2. * x + 1. + 1.) / _width - 1.) * _xhat		// SOLN
    + ((2. * y + 1. + 1.) / _height - 1.) * _yhat;		// SOLN
  dir = origin - _eye;			// SOLN
  dir.FastMakeUnit3();  				// SOLN
#else
  //look up the coeff's in the cache
  if ((x >=0) && (y >= 0) && (y < _height) && (x < _width))
    {
      Vec4 *coeff = _eye_ray_cache + y * _biggestdim + x;
      dir = coeff->x() * _xhat + coeff->y() * _yhat + coeff->z() * _zhat;
    }
  else
    {
      cerr << "asked for wild eye ray " << x << ", " << y << endl;
      cerr << "width is " << _width << ", height is " << _height << endl;
      assert (0);
    }
#endif
}




void PlaneCache::initialize (int width, int height)
  {
    if (_plane_cache)
      {
        delete[] _plane_cache;
        _plane_cache = NULL;
        _list_head = _list_tail = NULL;
      }
    if (_eye_ray_cache)
      {
        delete[] _eye_ray_cache;
        _eye_ray_cache = NULL;
      }
    if (_dot_product_cache)
      {
        delete[] _dot_product_cache;
        _dot_product_cache = NULL;
      }
   
   
    

    if (width > height)
      _biggestdim = width;
    else
      _biggestdim = height;

    //    assert (!_plane_cache);
    //    assert (!_eye_ray_cache);

    _plane_cache = new PlaneStruct [2 * _biggestdim];

    //    assert (_list_head == NULL);
    //    assert (_list_tail == NULL);
    int cur = 0;
    for (int i = 0; i < 2; i++)
      {
        for (int j =0 ; j < _biggestdim; j++)
          {
            _plane_cache[cur].dim= i;
            _plane_cache[cur].where = j;
            _plane_cache[cur].calced = 0;
            _plane_cache[cur].next = NULL;
            cur++;
          }
      }

#ifndef NO_EYE_RAY_CACHE
     _eye_ray_cache = new Vec4 [_biggestdim * _biggestdim];
     _dot_product_cache = new float [_biggestdim * _biggestdim];
#endif
  }


//returns 1 if we actually had to calc it.
int PlaneCache::calcPlane (int dim, int where, Plane &plane)
  {
    assert ((where >= 0) && (where < _biggestdim));
    if (truly_GS->_disabled_flags & DISABLE_PLANE_CACHE)
      {
	realCalcPlane (dim, where, plane);
	return 1;
      }

    PlaneStruct *daplane = accessPlane (dim, where);

    int havetocalc = !daplane->calced;
    if (havetocalc)
      {
        realCalcPlane (dim, where, daplane->plane);

#ifdef ONE_THREAD
	//XXX not MP-safe - just forgo the list and do a full clear, if you
	//have to.
        //add it to the list
        if (!_list_tail)
          {
            assert (!daplane->next);
            _list_head = _list_tail = daplane;
          }
        else
          {
            assert (_list_head);
            //if daplane is already in the list, don't add it again.
            if ( (!daplane->next) && (daplane != _list_tail))
              {
                _list_tail->next = daplane;
                _list_tail = daplane;
              }
          }
#endif
        daplane->calced = 1; //set this at end, so it's MP-safe.  If multiple people calc this at the same time, fine.
      }
    plane = daplane->plane;
    return havetocalc;
  }
 
PlaneCache::~PlaneCache() 
{
  delete[] _plane_cache;
  delete[] _eye_ray_cache;
  delete[] _dot_product_cache;
}

float PlaneCache::ComputeViewVecT (int x, int y, float rayt)
{
      float *coeff = _dot_product_cache + y * _biggestdim + x;
      return rayt * (*coeff);
}

