#include <assert.h>

#include "ray.h"
#include "rc_globals.h"
#include "bounds3d.h"

#ifdef OPENGL
#ifdef _WINDOWS
#include <windows.h>
#endif
#include <GL/gl.h>
#include <GL/glu.h>
#endif

Ray::Ray() {
}

Ray::Ray(const Vec4 &R, const Vec4 &D) {

	_R = R;
	SetD (D);
	_major_plane = MP_UNDEFINED;
	
	_id = -1;
	_t = _max_t = MAXFLOAT;
}

Ray::~Ray() {
}

Ray& Ray::CopyFrom(const Ray &R) {
  
  _id = R._id;
	_R = R._R;
	_D = R._D;
	_a = R._a;
	_b = R._b;
	_c = R._c;
	_d = R._d;

	_t = R._t;
	_max_t = R._max_t;

	_major_plane = R._major_plane;		// LS_* defined in "lintree.h"

	primary_axis = R.primary_axis;
	_direction[0] = R._direction[0];
	_direction[1] = R._direction[1];
	_direction[2] = R._direction[2];

	return *this;
}

void Ray::SetRay_4Dto3D(int major_plane, real a, real b, real c, real d,
						const Bounds3d &B) {

	_a = a;
	_b = b;
	_c = c;
	_d = d;

	switch (_major_plane = major_plane) {

		case MP_XY:
			_R.set_x(B.x1() * (1.0f - a) + B.x2() * a);
			_R.set_y(B.y1() * (1.0f - b) + B.y2() * b);
			_R.set_z(B.z2()); // XY reversed
			_D.set_x(B.x1() * (1.0f - c) + B.x2() * c);
			_D.set_y(B.y1() * (1.0f - d) + B.y2() * d);
			_D.set_z(B.z1()); // XY reversed
			break;

		case MP_XZ:
			_R.set_x(B.x1() * (1.0f - a) + B.x2() * a);
			_R.set_y(B.y1());
			_R.set_z(B.z1() * (1.0f - b) + B.z2() * b);
			_D.set_x(B.x1() * (1.0f - c) + B.x2() * c);
			_D.set_y(B.y2());
			_D.set_z(B.z1() * (1.0f - d) + B.z2() * d);
			break;

		case MP_YZ:
			_R.set_x(B.x1());
			_R.set_y(B.y1() * (1.0f - b) + B.y2() * b);
			_R.set_z(B.z1() * (1.0f - a) + B.z2() * a);
			_D.set_x(B.x2());
			_D.set_y(B.y1() * (1.0f - d) + B.y2() * d);
			_D.set_z(B.z1() * (1.0f - c) + B.z2() * c);
			break;

		default:
			cerr << "Ray::SetRay_4Dto3D(): undefined major plane" << endl;
	}

	Vec4FastSub(_D, _D, _R);
	_D.MakeUnit();
	SetDirections();
}


//
// This should probably depend on a Bounds3d and Bounds4d...
//
void Ray::CalcParameters4D() {

	real dx = (_D.x() >= 0) ? _D.x() : -_D.x();
	real dy = (_D.y() >= 0) ? _D.y() : -_D.y();
	real dz = (_D.z() >= 0) ? _D.z() : -_D.z();
	real t, x, y, z;

	// I appologize for the next line of code...

	switch (_major_plane = (dx > dy) ?
			((dx > dz) ? MP_YZ : MP_XY) : ((dy > dz) ? MP_XZ : MP_XY)) {

		case MP_XY:
			t = (1.0f - _R.z()) / _D.z();
			x = _R.x() + _D.x() * t;
			y = _R.y() + _D.y() * t;
			_a = (x + 1.0f) * 0.5f;
			_b = (y + 1.0f) * 0.5f;
			t = (-1.0f - _R.z()) / _D.z();
			x = _R.x() + _D.x() * t;
			y = _R.y() + _D.y() * t;
			_c = (x + 1.0f) * 0.5f;
			_d = (y + 1.0f) * 0.5f;
			break;

		case MP_XZ:
			t = (-1.0f - _R.y()) / _D.y();
			x = _R.x() + _D.x() * t;
			z = _R.z() + _D.z() * t;
			_a = (x + 1.0f) * 0.5f;
			_b = (z + 1.0f) * 0.5f;
			t = (1.0f - _R.y()) / _D.y();
			x = _R.x() + _D.x() * t;
			z = _R.z() + _D.z() * t;
			_c = (x + 1.0f) * 0.5f;
			_d = (z + 1.0f) * 0.5f;
			break;

		case MP_YZ:
			t = (-1.0f - _R.x()) / _D.x();
			y = _R.y() + _D.y() * t;
			z = _R.z() + _D.z() * t;
			_b = (y + 1.0f) * 0.5f;
			_a = (z + 1.0f) * 0.5f;
			t = (1.0f - _R.x()) / _D.x();
			y = _R.y() + _D.y() * t;
			z = _R.z() + _D.z() * t;
			_d = (y + 1.0f) * 0.5f;
			_c = (z + 1.0f) * 0.5f;
			break;

		default:
			cerr << "Ray::CalcParameters4D(): undefined major_plane" << endl;
			exit(-1);
	}
}

ostream& operator<<(ostream &co, const Ray &R) {

	co << "< " << R._R << " -> " << R._D << " >";

	return co;
}

#ifdef OPENGL

static float cr = 0.2, cg = 0.5, cb = 0.8;

static void CycleColors ( void )
{
  cr += 0.13579; if ( cr > 0.8f ) cr = 0.2f;
  cg += 0.19753; if ( cg > 0.8f ) cg = 0.2f;
  cb += 0.17391; if ( cb > 0.8f ) cb = 0.2f;
}

void Ray::Draw( float size ) const
{
#if defined (XOLAS) || defined (JLLIB_LINUX)
static GLUquadricObj *glq;
#else
static GLUquadric *glq;
#endif

  if (!glq) {
    glq = gluNewQuadric();
    assert (glq);
  }

  glColor3f( cr, cg, cb );
  CycleColors ();

  // draw sphere at eye point
  glPushMatrix ();
  glTranslatef ( _R.x(), _R.y(), _R.z() );
  gluSphere ( glq, size * 0.5f, 5, 5 );
  glPopMatrix ();

  // draw the ray, out to size * direction
  glBegin(GL_LINES);
  glVertex3f( _R.x(), _R.y(), _R.z() );
  glVertex3f( _R.x() + size * _D.x(), _R.y() + size * _D.y(), _R.z() + size * _D.z() );
  glEnd();
}

void Ray::DrawToT( real t ) const
{
  glColor3f( cr, cg, cb );
  CycleColors ();

  // draw the ray, out to t * dir
  glBegin(GL_LINES);
  glVertex3f( _R.x(), _R.y(), _R.z() );
  glVertex3f( _R.x() + t * _D.x(), _R.y() + t * _D.y(), _R.z() + t * _D.z() );
  glEnd();
}

void Ray::DrawT1T2 ( real t1, real t2 ) const
{
  glColor3f( cr, cg, cb );
  CycleColors ();

  DrawT1T2NoColor (t1, t2);
}

void Ray::DrawT1T2NoColor ( real t1, real t2 ) const
{
  // draw the ray, from t1 to t2
  glBegin(GL_LINES);
  glVertex3f( _R.x() + t1 * _D.x(), _R.y() + t1 * _D.y(), _R.z() + t1 * _D.z() );
  glVertex3f( _R.x() + t2 * _D.x(), _R.y() + t2 * _D.y(), _R.z() + t2 * _D.z() );
  glEnd();
}
#endif 

//sets up _direction
void Ray::SetDirections (void)
{
  real maxaxis = -MAXFLOAT;
  real current;
  for (int i = 0; i < 3; i++)
    {
      current = _D[i];

      if (current > 0.)
	{
	  if (current > maxaxis)
	    {
	      maxaxis = current;
	      primary_axis = 2*i + 1;
	    }
	_direction[i] = 1;
	}
      else if (current < 0.)
	{
	  if ((-current) > maxaxis)
	    {
	      maxaxis = -current;
	      primary_axis = 2*i;
	    }
	_direction[i] = -1;
	}
      else
	_direction[i] = 0;
    }

  if (maxaxis == -MAXFLOAT)
    {
      cerr << "direction set to be the zero vector" << endl;
      primary_axis = 0;
    }
  
}

