#ifndef _JL_Bounds3d_H_
#define _JL_Bounds3d_H_

#include "assert.h"
#include <stdlib.h>
#include <iostream.h>

#include "vec4.h"

class Ray;

const double T_FUDGE = 0.0001f;

#ifdef _WINDOWS
#include "float.h"
#define MAXFLOAT FLT_MAX
#endif

class Bounds3d {

	private:
                Vec4  _p[2]; //min/max x,y,z

	public: 
		Bounds3d()
			{ _p[0].Set (MAXFLOAT, MAXFLOAT, MAXFLOAT); _p[1].Set (-MAXFLOAT, -MAXFLOAT, -MAXFLOAT); }
		Bounds3d(real x1, real x2, real y1, real y2, real z1, real z2) {
		  _p[0].Set(x1,y1,z1);
		  _p[1].Set(x2,y2,z2);
}
		Bounds3d(const Vec4 &p1, const Vec4 &p2) {
_p[0] = p1; _p[1] = p2;}

		Bounds3d(const Bounds3d &B) { CopyFrom(B); }
		~Bounds3d() { }

		void SetToPoint(const Vec4 &p) {
		  _p[0] = _p[1] = p;
			}

		void IncludePoint(const Vec4 &p) {
				if (p.x() < _p[0].x()) _p[0].set_x(p.x()); if (p.x() > _p[1].x()) _p[1].set_x (p.x());
				if (p.y() < _p[0].y()) _p[0].set_y(p.y()); if (p.y() > _p[1].y()) _p[1].set_y (p.y());
				if (p.z() < _p[0].z()) _p[0].set_z(p.z()); if (p.z() > _p[1].z()) _p[1].set_z (p.z());
			}

		Bounds3d& CopyFrom(const Bounds3d &B) {
				_p[0] = B._p[0];
				_p[1] = B._p[1];
				return *this;
			}

		Bounds3d& operator=(const Bounds3d &B) { return CopyFrom(B); }

		friend ostream& operator<<(ostream&, const Bounds3d&);

		void Set(real x1, real x2, real y1, real y2, real z1, real z2)
			{ _p[0].Set (x1, y1, z1); _p[1].Set (x2,y2,z2);}

                void Set (const Vec4 &p1, const Vec4 &p2) {_p[0] = p1; _p[1] = p2;}

		real x1() const { return _p[0].x(); }
		real x2() const { return _p[1].x(); }
		real y1() const { return _p[0].y(); }
		real y2() const { return _p[1].y(); }
		real z1() const { return _p[0].z(); }
		real z2() const { return _p[1].z(); }

		real xmin() const { return _p[0].x(); }
		real xmax() const { return _p[1].x(); }
		real ymin() const { return _p[0].y(); }
		real ymax() const { return _p[1].y(); }
		real zmin() const { return _p[0].z(); }
		real zmax() const { return _p[1].z(); }
		
                const Vec4 &operator [](int hilo) const {assert ((hilo == 0) || (hilo == 1)); return _p[hilo];} 
                Vec4 &operator [](int hilo) {assert ((hilo == 0) || (hilo == 1)); return _p[hilo];} 

  real get (int dim, int hilo) const {
    switch (dim) {
    case 0:
      return (_p[hilo].x());
    case 1:
      return (_p[hilo].y());
    case 2:
      return (_p[hilo].z());
      
    default:
      cerr << "bounds3d get only works for 3 dimensions, no dim " << dim << endl;
      return 0.;
    }

		}

		int TestPlane (int dim, real plane) const {
		  switch (dim)
		    {
		    case 0:
		      return TestPlaneX (plane);
		    case 1:
		      return TestPlaneY (plane);
		    case 2:
		      return TestPlaneZ (plane);
		    default:
		      assert (1==0);
		      return -1;
		    }
		}
		//clips us so we fit inside the given bbox
		void ClipToBbox (const Bounds3d &insideme);
		void ClipToFrontOfRay (const Vec4 &pt, const Vec4 &dir);
		      
		int TestPlaneX(real x) const { return (x< _p[0].x() ? 1 : (x> _p[1].x()) ? -1 : 0); }
		int TestPlaneY(real y) const { return (y< _p[0].y() ? 1 : (y> _p[1].y()) ? -1 : 0); }
		int TestPlaneZ(real z) const { return (z< _p[0].z() ? 1 : (z> _p[1].z()) ? -1 : 0); }

		real DiagonalDistSquared() const {
		  Vec4 diagonal = _p[1] - _p[0];
		  return diagonal.Dot3 (diagonal);
			}

		void CalcPointOnSurface(int major_plane, real a, real b, Vec4 &V,
								int back) const;

		int Intersect(const Ray &ray, real &hit, int &face, int &direction) const;

		void whereLeft (const Ray &ray, real &tleft, int &face, int &direction, Vec4 *ptleft = NULL) const;
		int whereEntered (const Ray &ray, real &tentered, int &face, int &direction, Vec4 &ptentered) const;

		int IncludesBox (const Bounds3d &box) const;
		int IncludesPoint (const Vec4 &pt) const;

#define OPENGL
#ifdef OPENGL
                void drawWireFrame ( void ) const;
                void drawFilled ( void ) const;
		void DrawXOnFace (int face, int direction) const;
#endif

		// fill in halfspaces corresponding to box
		void FillHalfspaces ( Plane halves[6] ) const;
};

#endif
