/* ImLib3D
 * Copyright (c) 2001, ULP-IPB Strasbourg.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
/*! \file RectZone3D.hpp */

#ifndef _RectZone3D_hpp
#define _RectZone3D_hpp

#include<math.h>
#include<ImLib3D/Vect3D.hpp>
#include<ImLib3D/Size3D.hpp>
#include<ImLib3D/CppTools.hpp>

template<class PType> class PropertyWrapPtr;

//! rectangular zone, INCLUDES BOTH points x0 y0 z0, x1 y1 z1
template<class RectType,class Real=RectType>
class RectZone3D
{
	friend class PropertyWrapPtr<RectZone3D<RectType,Real> >;
protected:
public:
	typedef Vect3D<RectType,Real> Vect3DType;

	RectType x0,y0,z0;
	RectType x1,y1,z1;

  	RectType Width()  const {return x1-x0;}
  	RectType Height() const {return y1-y0;}
  	RectType Depth()  const {return z1-z0;}
	Vect3DType SizeV(){return Vect3DType(Width(),Height(),Depth());}
	RectType Volume() {return Width()*Height()*Depth();}
	Vect3DType GetP0() const {return Vect3DType(x0,y0,z0);}
	Vect3DType GetP1() const {return Vect3DType(x1,y1,z1);}
	void SetP0(const Vect3DType &p0)  {x0=p0.x;y0=p0.y;z0=p0.z;}
	void SetP1(const Vect3DType &p1)  {x1=p1.x;y1=p1.y;z1=p1.z;}

	Vect3Df SelfCoords(const Vect3DType &P) const
	{
		return Vect3Df((P.x-x0)/(float)(x1-x0),
					   (P.y-y0)/(float)(y1-y0),
					   (P.z-z0)/(float)(z1-z0) );
	}

	RectZone3D<RectType,Real> Intersection(const RectZone3D<RectType,Real> &other) const
	{
		RectZone3D<RectType,Real> res(other);
		res.x0=max(x0,other.x0);
		res.y0=max(y0,other.y0);
		res.z0=max(z0,other.z0);

		res.x1=min(x1,other.x1);
		res.y1=min(y1,other.y1);
		res.z1=min(z1,other.z1);
		
		return res;
	}
	bool IsOk() const {return x0<=x1 && y0<=y1 && z0<=z1;}
	void Translate(const Vect3DType& vec){Translate(vec.x, vec.y, vec.z);}
	void Translate(const RectType x,const RectType y,const RectType z)
	{
		x0+=x;
		y0+=y;
		z0+=z;
		x1+=x;
		y1+=y;
		z1+=z;
	}
	inline void ToStart(RectType &x,RectType &y,RectType &z) const {x=x0;y=y0;z=z0;}
	bool IsInside(const Vect3DType & vect) const {return IsInside(vect.x, vect.y, vect.z);}
	bool IsInside(RectType x,RectType y,RectType z) const 
	{
		if( x<x0 ) {return false;}
		if( y<y0 ) {return false;}
		if( z<z0 ) {return false;}
		if( x>x1 ) {return false;}
		if( y>y1 ) {return false;}
		if( z>z1 ) {return false;}
		return true;
	}

	bool IsOnSurface(const Vect3DType & vect) const {return IsOnSurface(vect.x, vect.y, vect.z);}
	bool IsOnSurface(RectType x,RectType y,RectType z) const 
	{
		return (x==x0 || y==y0 || z==z0 || x==x1 || y==y1 || z==z1) && IsInside(x,y,z);
	}
	Vect3DType ClosestPoint(const Vect3DType &p)
	{
		if(IsInside(p)){return p;}
		Vect3DType res=p;
		if(res.x<x0){res.x=x0;}
		if(res.y<y0){res.y=y0;}
		if(res.z<z0){res.z=z0;}
		if(res.x>x1){res.x=x1;}
		if(res.y>y1){res.y=y1;}
		if(res.z>z1){res.z=z1;}
		return res;
	}

	bool operator==(const RectZone3D<RectType> &other) const
	{
		return 
			x0==other.x0 &&
			y0==other.y0 &&
			z0==other.z0 &&
			x1==other.x1 &&
			y1==other.y1 &&
			z1==other.z1     ;
	}
	RectZone3D( const Vect3DType &p0,const Vect3DType &p1):
		x0(p0.x),y0(p0.y),z0(p0.z),
		x1(p1.x),y1(p1.y),z1(p1.z)
	{
	}

	RectZone3D( RectType _x0, RectType _y0, RectType _z0,
				RectType _x1, RectType _y1, RectType _z1 ) :
		x0(_x0),y0(_y0),z0(_z0),
		x1(_x1),y1(_y1),z1(_z1)
	{
	}

	RectZone3D() :
		x0(0),y0(0),z0(0),
		x1(-1),y1(-1),z1(-1)
	{;}
};

typedef RectZone3D<float> RectZone3Df;

class RectZone3Di : public RectZone3D<int,float>
{
public:
	int Width()  const {return x1-x0+1;}
	int Height() const {return y1-y0+1;}
	int Depth()  const {return z1-z0+1;}
	int Volume() {return Width()*Height()*Depth();}
	Size3D Size()  const {return SizeV();}
	Vect3Di SizeV() const {return Vect3Di(Width(),Height(),Depth());}
	inline int  ToNext(int &x,int &y,int &z) const
	{
		x++;
		if(x>x1)
		{
			x=x0;
			y++;
			if(y>y1)
			{
				y=y0;
				z++;
				if(z>z1){return 3;}
				return 2;
			}
			return 1;
		}
		else { return 0;}
	}
	inline int  ToPrev(int &x,int &y,int &z) const
	{
		x--;
		if(x<x0)
		{
			x=x1;
			y--;
			if(y<y0)
			{
				y=y1;
				z--;
				if(z<z0){return 3;}
				return 2;
			}
			return 1;
		}
		else { return 0;}
	}

	inline int  ToNextOnSurface(int &x,int &y,int &z) const
	{
		if(z==z0){return ToNext(x,y,z);}
		else
		if(z==z1){return ToNext(x,y,z);}
		else
		if(y==y0){return ToNext(x,y,z);}
		else
		if(y==y1){return ToNext(x,y,z);}
		else
		if(x==x1){y++;x=x0;return 1;}
		else
		if(x==x0){x=x1;return 1;}
		else
		{ThrowError("RectZone3Di::ToNextOnSurface: initial point not on surface?");}
		return 1;
	}
	inline int  ToPrevOnSurface(int &x,int &y,int &z) const
	{
		x--;
		if(x<x0)
		{
			x=x1;
			y--;
			if(y<y0)
			{
				y=y1;
				z--;
				if(z<z0){return 3;}
				return 2;
			}
			return 1;
		}
		else { return 0;}
	}

	RectZone3Di Intersection(const RectZone3Di &other) const
	{
		RectZone3Di res(other);
		res.x0=max(x0,other.x0);
		res.y0=max(y0,other.y0);
		res.z0=max(z0,other.z0);

		res.x1=min(x1,other.x1);
		res.y1=min(y1,other.y1);
		res.z1=min(z1,other.z1);
		
		return res;
	}
	
	
	RectZone3Di ( const RectZone3Df & , int );
	RectZone3Di ( const Vect3Di &p0,const Size3D &size):
		RectZone3D<int,float>(p0,p0+size.GetV()-Vect3Di(1,1,1))
	{
	}
	RectZone3Di (const Vect3Di &p0,const Vect3Di &p1):
		RectZone3D<int,float>(p0,p1)
	{
		;
	}
	RectZone3Di( int _x0, int _y0, int _z0,
				 int _x1, int _y1, int _z1 ) :
		RectZone3D<int,float>(_x0,_y0,_z0,_x1,_y1,_z1)
	{
		;
	}
	RectZone3Di( ) : RectZone3D<int,float>()
	{
		;
	}

};


inline RectZone3Di::RectZone3Di( const RectZone3Df &s0, int type)
{
	if(type==0)
	{
		x0= (int)ceil (s0.x0);
		y0= (int)ceil (s0.y0);
		z0= (int)ceil (s0.z0);
		x1= (int)floor(s0.x1);
		y1= (int)floor(s0.y1);
		z1= (int)floor(s0.z1);
	}
	else
	{
		x0= (int)floor(s0.x0);
		y0= (int)floor(s0.y0);
		z0= (int)floor(s0.z0);
		x1= (int)ceil (s0.x1);
		y1= (int)ceil (s0.y1);
		z1= (int)ceil (s0.z1);
	}
}

template<class RectType,class Real>
inline ostream& operator<< (ostream& s, const RectZone3D<RectType, Real>& rect)
{
	s << rect.GetP0() << " " << rect.GetP1() << " ";
	return s;
}

template<class RectType,class Real>
inline istream& operator>> (istream& s, RectZone3D<RectType, Real>& rect)
{
	Vect3Di p0,p1;
	s >> p0 >> p1;
	rect.SetP0(p0);
	rect.SetP1(p1);
	return s;
}

#ifdef  USE_EXPLICIT_INSTANTIATION
// explicit instantiation of common templates, only for faster compilation
extern template class Vect3D<float,float>;
extern template class RectZone3D<int,float>;
extern template class vector<Vect3Di>;
extern template class vector<Vect3Df>;
extern template class Vect3D<int, float>;
#endif // USE_EXPLICIT_INSTANTIATION

#endif // _RectZone3D_hpp
