#ifdef _WINDOWS
#include <windows.h>
#include "float.h"
#define MAXFLOAT FLT_MAX
#endif
#include <GL/gl.h>
#include <GL/glu.h>
#include "camera.h"


Camera::Camera() {

	_x_pixels = _y_pixels = 0;

//	cerr << "Error: Camera default constructor called" << endl;
}

Camera::Camera(const Vec4 &R, const Vec4 &D, const Vec4 &U, 
	       real dist, real nearp, real farp, 
	       int x, int y, 
	       float fovx, float fovy ) {

	_R = R;
	_D = D;
	_U = U;

	_dist = dist;
	_near_plane = nearp;
	_far_plane = farp;

	// really part of the viewport, not the camera
	_x_pixels = x;
	_y_pixels = y;

	_fovx = fovx;
	_fovy = fovy;

	UpdateCorners();
}

Camera::~Camera() {
}

Camera& Camera::CopyFrom(const Camera &C) {

	_R = C._R;
	_D = C._D;
	_U = C._U;

	_dist = C._dist;
	_near_plane = C._near_plane;
	_far_plane = C._far_plane;

	_c00 = C._c00;
	_c10 = C._c10;
	_c01 = C._c01;
	_c11 = C._c11;

	_x_pixels = C._x_pixels;
	_y_pixels = C._y_pixels;

	_fovx = C._fovx;
	_fovy = C._fovy;

	return *this;
}

ostream& operator<<(ostream &co, const Camera &C) {

	co << "Camera:" << endl;
	co << "  size (in pixels): " << C._x_pixels << ", " << C._y_pixels << endl;
	co << "  x fov (radians) : " << C._fovx << endl;
	co << "  y fov (radians) : " << C._fovy << endl;
	co << "  eye point       : " << C._R << endl;
	co << "  direction       : " << C._D << endl;
	co << "  up vector       : " << C._U << endl;

	return co;
}

void Camera::CalcSamplePoint(float s, float t, Vec4 &v) {

	Vec4 v0, v1;

	Vec3_FastWeightedSum(v0, _c00, (1.0f - s), _c10, s);
	Vec3_FastWeightedSum(v1, _c01, (1.0f - s), _c11, s);

	Vec3_FastWeightedSum(v, v0, (1.0f - t), v1, t);
}

void Camera::CalcPixelPoint(int x, int y, Vec4 &v) {

	if ((x < 0) || (x >= _x_pixels) || (y < 0) || (y >= _y_pixels)) {
		cerr << "Error in Camera::CalcPixelPoint(): out of bounds!" << endl;
		cerr << "Point: (" << x << ',' << y << ')' << endl;
		cerr << "Range: (" << _x_pixels << ',' << _y_pixels << ')' << endl;
		exit(-1);
	}

	float s = (float(x) + 0.5f) / float(_x_pixels);
	float t = (float(y) + 0.5f) / float(_y_pixels);

	CalcSamplePoint(s, t, v);
}

void Camera::CalcPixelPointWithJitter(int x, int y, Vec4 &v) {

	if ((x < 0) || (x >= _x_pixels) || (y < 0) || (y >= _y_pixels)) {
		cerr << "Error in Camera::CalcPixelPoint(): out of bounds!" << endl;
		exit(-1);
	}

#ifdef _WINDOWS
	float s = (float(x) + rand()) / float(_x_pixels);
	float t = (float(y) + rand()) / float(_y_pixels);
#else
	float s = (float(x) + drand48()) / float(_x_pixels);
	float t = (float(y) + drand48()) / float(_y_pixels);
#endif

	CalcSamplePoint(s, t, v);
}

// computes _cXY, four corners of view plane at distance _dist from eye
void Camera::SetPosition(Vec4 &Eye, Vec4 &LookAt, Vec4 &Up) {
	// get dir vector
	Vec4 Dir(LookAt, Eye);
	Dir.MakeUnit();

	// get up vector
	Vec4 localUp = Up;
	localUp.MakeUnit();

	// Right == Dir x Up
	Vec4 vpRight;
	Vec4FastCross3(vpRight, Dir, localUp);
	vpRight.MakeUnit();

	// vpUp == vpRight x Dir
	Vec4 vpUp;
	Vec4FastCross3( vpUp, vpRight, Dir );

	// compute right, up on viewplane (at d == _dist)
	vpRight *= (tanf(_fovx * 0.5f) * _dist);
	vpUp *= (tanf(_fovy * 0.5f) * _dist);

	// viewplane "corners" in world coordinates
	Vec4FastAddScale( _c00, Eye, Dir, _dist ); // _c00 = center
	Vec4FastAddScale( _c00, Eye, Dir, _dist ); // _c10 = center

	Vec4FastSub(_c00, LookAt, vpRight); // _c00 = left edge
	Vec4FastAdd(_c10, LookAt, vpRight); // _c10 = right edge

	Vec4FastAdd(_c01, _c00, vpUp); // upper left
	Vec4FastAdd(_c11, _c10, vpUp); // upper right

	Vec4FastSub(_c00, _c00, vpUp); // lower left
	Vec4FastSub(_c10, _c10, vpUp); // lower right
}

void Camera::UpdateCorners() {

	Vec4 LookAt;

	// recompute "lookat" point as R + d * D
	Vec4FastAddScale(LookAt, _R, _D, _dist);

	// reset
	SetPosition( _R, LookAt, _U );
}

void Camera::SetOpenGLFrustum() {
      glMatrixMode (GL_PROJECTION); // could do this and lights once at B.O.T.
      glLoadIdentity();

      // deduce suitable near, far planes
      // necessary since RC camera model may have near==0, far == MAXFLOAT
      float nearp = (_near_plane > 0.f) ? _near_plane : _dist * 0.1f;
      float farp = (_far_plane < MAXFLOAT) ? _far_plane : _dist * 10.f;

      // inform gl of camera frustum settings	
      glFrustum (-nearp * tanf(_fovx * 0.5), nearp * tanf(_fovx * 0.5), // left, right
		 -nearp * tanf(_fovy * 0.5), nearp * tanf(_fovy * 0.5), // bottom, top
		  nearp, farp );
}

void Camera::SetOpenGLLeftFrustum() {
      glMatrixMode (GL_PROJECTION); // could do this and lights once at B.O.T.
      glLoadIdentity();

      // deduce suitable near, far planes
      // necessary since RC camera model may have near==0, far == MAXFLOAT
      float nearp = (_near_plane > 0.f) ? _near_plane : _dist * 0.1f;
      float farp = (_far_plane < MAXFLOAT) ? _far_plane : _dist * 10.;

      float left = -nearp * tanf(_fovx * 0.5);
      float right =  nearp * tanf(_fovx * 0.5);
      // ugly, but required by justin's choice to have an odd # of pixels
      right = (left + (_x_pixels/2)/((float)_x_pixels) * (right - left));

      // inform gl of camera frustum settings
      glFrustum (left, right,
		 -nearp * tanf(_fovy * 0.5), nearp * tanf(_fovy * 0.5), // bottom, top
		  nearp, farp );
}

void Camera::SetOpenGLViewMatrix() {
  glMatrixMode (GL_MODELVIEW);  // must do this once per frame
  glLoadIdentity();
  gluLookAt ( _R.x(), _R.y(), _R.z(), 
	      _R.x() + _D.x(), _R.y() + _D.y(), _R.z() + _D.z(), 
	      _U.x(), _U.y(), _U.z() );
}

void Camera::OpenGLPushMatrices() {
      glMatrixMode (GL_PROJECTION);
      glPushMatrix();
      glMatrixMode (GL_MODELVIEW);
      glPushMatrix();
}

void Camera::OpenGLPopMatrices() {
      glMatrixMode (GL_PROJECTION);
      glPopMatrix();
      glMatrixMode (GL_MODELVIEW);
      glPopMatrix();
}


