/****************************************************************************
* VCGLib                                                            o o     *
* Visual and Computer Graphics Library                            o     o   *
*                                                                _   O  _   *
* Copyright(C) 2004-2021                                           \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* 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 (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#ifndef __VCGLIB_BOX2
#define __VCGLIB_BOX2

#include <vcg/math/base.h>
#include <vcg/space/point2.h>

namespace vcg {


// needed prototype;
template <class SegScalarType> class Segment2;

/** \addtogroup space */
/*@{*/
/**
	Templated class for a 2D bounding box. It is stored just as two Point2
	@param BoxScalarType (Template Parameter) Specifies the scalar field.
*/
template <class BoxScalarType>
class Box2
{
public:
	/// The scalar type
	typedef BoxScalarType ScalarType;
	typedef Point2<BoxScalarType> PointType;

	/// min coordinate point
	PointType min;
	/// max coordinate point
	PointType max;
	/// Standard constructor
	inline  Box2() { min.X()= 1; max.X()= -1; min.Y()= 1; max.Y()= -1; }
	/// Min Max constructor
	inline  Box2(const Point2<BoxScalarType> & mi, const Point2<BoxScalarType> & ma ) { min = mi; max = ma; }

	inline Box2(const Point2<BoxScalarType> & center, const BoxScalarType & radius)
	{
		min = center-Point2<BoxScalarType>(radius,radius);
		max = center+Point2<BoxScalarType>(radius,radius);
	}

	/// Operator to compare two bounding box
	inline bool operator == ( Box2 const & p ) const
	{
		return min==p.min && max==p.max;
	}
	/// Initializing the bounding box with a point
	void Set( const PointType & p )
	{
		min = max = p;
	}

	Point2<BoxScalarType> P(int i) const
	{
		return Point2<BoxScalarType>(
					min[0]+ (i%2) * DimX(),
				min[1]+ ((i / 2)%2) * DimY());
	}

	// Initializing with the values
	inline void Set( ScalarType minx, ScalarType miny, ScalarType maxx, ScalarType maxy )
	{
		min[0] = minx;
		min[1] = miny;
		max[0] = maxx;
		max[1] = maxy;
	}
	/// Set the bounding box to a null value
	void SetNull()
	{
		min.X()= 1; max.X()= -1; min.Y()= 1; max.Y()= -1;
	}
	/** @brief Function to add two bounding box
	 * the bounding box expand to include the other bounding box (if necessary)
	 * @param b The bounding box to be added
	 */
	void Add( Box2 const & b )
	{
		if(IsNull())
		{
			min=b.min;
			max=b.max;
		}
		else
		{
			if(min.X() > b.min.X()) min.X() = b.min.X();
			if(min.Y() > b.min.Y()) min.Y() = b.min.Y();

			if(max.X() < b.max.X()) max.X() = b.max.X();
			if(max.Y() < b.max.Y()) max.Y() = b.max.Y();
		}
	}
	/**
	 * @brief Adds a point to the bouning box.
	 * the bounding box expand to include the new point (if necessary)
	 * @param p The point 2D
	 */
	void Add( const PointType & p )
	{
		if(IsNull()) Set(p);
		else
		{
			if(min.X() > p.X()) min.X() = p.X();
			if(min.Y() > p.Y()) min.Y() = p.Y();

			if(max.X() < p.X()) max.X() = p.X();
			if(max.Y() < p.Y()) max.Y() = p.Y();
		}
	}

	/**
	 * @brief Varies the dimension of the bounding box.
	 * @param delta The size delta (if positive, box is enlarged)
	 */
	void Offset(ScalarType s)
	{
		Offset(PointType(s, s));
	}

	/**
	 * @brief Varies the dimension of the bounding box.
	 * @param delta The size delta per dimension (if positive, box is enlarged)
	 */
	void Offset(const PointType & delta)
	{
		min -= delta;
		max += delta;
	}

	/**
	 * @brief Computes intersection between this and another  bounding box
	 * @param b The other bounding box
	 */
	void Intersect( const Box2 & b )
	{
		if(min.X() < b.min.X()) min.X() = b.min.X();
		if(min.Y() < b.min.Y()) min.Y() = b.min.Y();

		if(max.X() > b.max.X()) max.X() = b.max.X();
		if(max.Y() > b.max.Y()) max.Y() = b.max.Y();

		if(min.X()>max.X() || min.Y()>max.Y()) SetNull();
	}

	/**
	 * @brief Translate the bounding box by a vector
	 * @param p The translation vector
	 */
	void Translate( const PointType & p )
	{
		min += p;
		max += p;
	}
	/**
	 * @brief Checks whether a 2D point p is inside the box
	 * @param p The point 2D
	 * @return True iff p inside
	 */
	bool IsIn( const PointType & p ) const
	{
		return (
			min.X() <= p.X() && p.X() <= max.X() &&
			min.Y() <= p.Y() && p.Y() <= max.Y());
	}
	/**
	 * @brief Checks whether a 2D point p is inside the box, closed at min but open at max
	 * @param p The point in 2D
	 * @return True iff p inside
	 */
	bool IsInEx( const PointType & p ) const
	{
		return (
			min.X() <= p.X() && p.X() < max.X() &&
			min.Y() <= p.Y() && p.Y() < max.Y());
	}
	/**
	 * @brief Check bbox collision.
	 * Note: just adjiacent bbox won't collide
	 * @param b A bounding box
	 * @return True iff collision
	 */
	bool Collide( const Box2 &b ) const
	{
		Box2 bb=*this;
		bb.Intersect(b);
		return bb.IsValid();
	}
	/**
	 * Check if empty.
	 * @return True iff empty
	 */
	inline bool IsNull() const { return min.X()>max.X() || min.Y()>max.Y(); }

	/**
	 * Check consistency.
	 * @return True iff consistent
	 */
	inline bool IsValid() const { return min.X()<max.X() && min.Y()<max.Y(); }

	/** Check if empty.
	  @return True iff empty
		*/
	inline bool IsEmpty() const { return min==max; }

	/// Computes length of diagonal
	ScalarType Diag() const
	{
		return Distance(min,max);
	}
	/// Computes box center
	PointType Center() const
	{
		return (min+max)/2;
	}
	/// Computes area
	inline ScalarType Area() const
	{
		return (max[0]-min[0])*(max[1]-min[1]);
	}
	/// computes dimension on X axis.
	inline ScalarType DimX() const { return max.X()-min.X(); }
	/// computes dimension on Y axis.
	inline ScalarType DimY() const { return max.Y()-min.Y(); }

	/// Computes sizes (as a vector)
	inline PointType Dim() const { return max-min; }

	/// Deprecated: use GlobalToLocal
	inline void Normalize( PointType & p )
	{
		p -= min;
		p[0] /= max[0]-min[0];
		p[1] /= max[1]-min[1];
	}

	/// Returns global coords of a local point expressed in [0..1]^2
	PointType LocalToGlobal(const PointType & p) const{
		return PointType(
					min[0] + p[0]*(max[0]-min[0]),
				min[1] + p[1]*(max[1]-min[1]));
	}
	/// Returns local coords expressed in [0..1]^2 of a point in R^2
	PointType GlobalToLocal(const PointType & p) const{
		return PointType(
					(p[0]-min[0])/(max[0]-min[0]),
				(p[1]-min[1])/(max[1]-min[1])
				);
	}

	/// Turns the bounding box into a square (conservatively)
	void MakeSquare(){
		ScalarType radius = max( DimX(), DimY() ) / 2;
		PointType c = Center();
		max = c + PointType(radius, radius);
		min = c - PointType(radius, radius);
	}

	inline unsigned char MaxDim() const {
		int i=1;
		PointType diag = max-min;
		if (diag[0] > diag[1])
			i=0;
		return i;
	}

	inline unsigned char MinDim() const {
		int i=1;
		PointType diag =  max-min;
		if (diag[0] < diag[1])
			i=0;
		return i;
	}

}; // end class definition

template <class ScalarType> 
ScalarType DistancePoint2Box2(const Point2<ScalarType> &test,
							  const Box2<ScalarType> &bbox)
{
	///test possible position respect to bounding box
	if (!bbox.IsIn(test)){
		if ((test.X()<=bbox.min.X())&&(test.Y()<=bbox.min.Y()))
			return ((test-bbox.min).Norm());
		else
			if ((test.X()>=bbox.min.X())&&
					(test.X()<=bbox.max.X())&&
					(test.Y()<=bbox.min.Y()))
				return (bbox.min.Y()-test.Y());
			else
				if ((test.X()>=bbox.max.X())&&
						(test.Y()<=bbox.min.Y()))
					return ((test-vcg::Point2<ScalarType>(bbox.max.X(),bbox.min.Y())).Norm());
				else
					if ((test.Y()>=bbox.min.Y())&&
							(test.Y()<=bbox.max.Y())&&
							(test.X()>=bbox.max.X()))
						return (test.X()-bbox.max.X());
					else
						if ((test.X()>=bbox.max.X())&&(test.Y()>=bbox.max.Y()))
							return ((test-bbox.max).Norm());
						else
							if ((test.X()>=bbox.min.X())&&
									(test.X()<=bbox.max.X())&&
									(test.Y()>=bbox.max.Y()))
								return (test.Y()-bbox.max.Y());
							else
								if ((test.X()<=bbox.min.X())&&
										(test.Y()>=bbox.max.Y()))
									return ((test-vcg::Point2<ScalarType>(bbox.min.X(),bbox.max.Y())).Norm());
								else
									if ((test.X()<=bbox.min.X())&&
											(test.Y()<=bbox.max.Y())&&
											(test.Y()>=bbox.min.Y()))
										return (bbox.min.X()-test.X());
	}
	else
	{
		//return minimum distance
		ScalarType dx=std::min<ScalarType>(fabs(test.X()-bbox.min.X()),fabs(bbox.max.X()-test.X()));
		ScalarType dy=std::min<ScalarType>(fabs(test.Y()-bbox.min.Y()),fabs(bbox.max.Y()-test.Y()));
		return(std::min<ScalarType>(dx,dy));
	}
}

template <class ScalarType> 
Point2<ScalarType> ClosestPoint2Box2(const Point2<ScalarType> &test,
									 const Box2<ScalarType> &bbox)
{
	Segment2<ScalarType> Segs[4];
	Segs[0].P0() = bbox.min;
	Segs[0].P1() = vcg::Point2<ScalarType>(bbox.max.X(), bbox.min.Y());

	Segs[1].P0() = Segs[0].P1();
	Segs[1].P1() = bbox.max;

	Segs[2].P0() = Segs[1].P1();
	Segs[2].P1() = vcg::Point2<ScalarType>(bbox.min.X(), bbox.max.Y());

	Segs[3].P0() = Segs[2].P1();
	Segs[3].P1() = bbox.min;
	
	Point2<ScalarType> closest = ClosestPoint(Segs[0], test);
	ScalarType minDist = (closest-test).Norm();
	for (int i = 1; i < 4; i++)
	{
		Point2<ScalarType> point = ClosestPoint(Segs[i], test);
		ScalarType dist = (test - point).Norm();
		if (dist < minDist)
		{
			minDist = dist;
			closest = point;
		}
	}
	return closest;
}

/// Specificazione di box of short
typedef Box2<short>  Box2s;
/// Specificazione di box of int
typedef Box2<int>	 Box2i;
/// Specificazione di box of float
typedef Box2<float>  Box2f;
/// Specificazione di box of double
typedef Box2<double> Box2d;

/*@}*/
} // end namespace


#endif
